diff options
Diffstat (limited to 'Build/source/texk/web2c/ctiedir/ctie.w')
-rw-r--r-- | Build/source/texk/web2c/ctiedir/ctie.w | 1280 |
1 files changed, 1280 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/ctiedir/ctie.w b/Build/source/texk/web2c/ctiedir/ctie.w new file mode 100644 index 00000000000..33abf6312c8 --- /dev/null +++ b/Build/source/texk/web2c/ctiedir/ctie.w @@ -0,0 +1,1280 @@ +% This is ctie.w +% Copyright (c) 2002,2003 by Julian Gilbey +% All rights reserved. +% +% This program is distributed WITHOUT ANY WARRANTY, express or implied. +% +% Permission is granted to make and distribute verbatim copies of this +% program provided that the copyright notice and this permission notice +% are preserved on all copies. +% +% Permission is granted to copy and distribute modified versions of this +% program under the conditions for verbatim copying, provided that the +% entire resulting derived work is distributed under the terms of a +% permission notice identical to this one. +% +% This program is based heavily on tie.w and common.w, part of the +% CWEB source. + +\def\title{The CTIE processor} +\def\botofcontents{% +\vfill +$\copyright$ 2002,2003 Julian Gilbey + +All rights reserved. + +This program is distributed WITHOUT ANY WARRANTY, express or implied. + +Permission is granted to make and distribute verbatim copies of this +program provided that the copyright notice and this permission notice +are preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +program under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. +} + + +@* Introduction. +Whenever a programmer wants to change a given \.{WEB} or \.{CWEB} +program (referred to as a \.{WEB} program throughout this program) +because of system dependencies, she or he will create a new change +file. In addition there may be a second change file to modify system +independent modules of the program. But the \.{WEB} file cannot be +tangled and weaved with more than one change file simultaneously. +The \.{TIE} program was designed to merge a \.{WEB} file +and several change files producing a new \.{WEB} file, and since the +input files are tied together, the program was called \.{TIE}. +Furthermore, the program could be used to merge several change files +giving a new single change file. This method seems to be more +important because it doesn't modify the original source file. + +However, the introduction of \.{CWEB} has meant that \.{TIE} is not +quite able to perform its task correctly any longer: \.{CWEB} +introduced the idea of include files, which are input into \.{CWEB} +files using the \.{@@i} command, and \.{TIE} is unable to handle such +constructs if the change files modify lines included in those files. +The present program, \.{CTIE}, is designed to overcome this lack. +Like \.{TIE}, upon which it is based, it can either output a single +master \.{WEB} file or a master change file. However, in both cases, +any include commands will be totally expanded and the files included +in the output rather than the \.{@@i} commands being left; this makes +this code feasible, which it would not necessarily be otherwise. +Other than this difference, \.{CTIE} should function identically to +\.{TIE} on files which do not involve any \.{CWEB} include commands. + +The algorithm used is essentially the same as that of \.{TIE}, with +modifications to check for and handle \.{@@i} commands. Thus, as with +\.{TIE}, the method used only needs one buffer line for each input +file. Thus the storage requirement of \.{CTIE} does not depend on the +sizes of the input files but only on their number. + +The program is written in \CEE/ and has few system dependencies. + +The ``banner line'' defined here should be changed whenever +\.{CTIE} is modified. We also keep the version number here separately +for ease; it is used below. + +@d version_number "1.1" +@d banner "This is CTIE, Version 1.1" +@d copyright + "Copyright 2002,2003 Julian Gilbey. All rights reserved. There is no warranty.\n\ +Run with the --version option for other important information." + + +@ The main outline of the program is now given. +This can be used more or less for any \CEE/ program. + +@c +@<Global \&{\#include}s@>@; +@<Global types@>@; +@<Predeclaration of functions@>@; +@<Global variables@>@; +@<Error handling functions@>@; +@<Internal functions@>@; +@<The main function@>@; + +@ And this is the structure of the |main| function: this is where +\.{CTIE} starts, and where it ends. + +@<The main function@>= +main(argc, argv) + int argc; string *argv; +{ + @<Initialise parameters@>; + @<Scan the parameters@>@; + @<Print the banners@>; + @<Get the master file started@>@; + @<Prepare the change files@>@; + @<Prepare the output file@>@; + @<Process the input@>@; + @<Check that all changes have been read@>@; + exit(wrap_up()); +} + +@ We include the additional types |boolean| and |string|. \.{CTIE} +replaces the complex \.{TIE} character set handling (based on that of +the original \.{WEB} system) with the standard \.{CWEB} behaviour, and +so uses the |char| type for input and output. + +@d false 0 +@d true 1 + +@<Global types@>= +typedef int boolean; +typedef char* string; + + +@ We predeclare some standard string-handling functions here instead of +including their system header files, because the names of the header files +are not as standard as the names of the functions. (There's confusion +between \.{<string.h>} and \.{<strings.h>}.) + +@<Predecl...@>= +extern int strlen(); /* length of string */ +extern char* strcpy(); /* copy one string to another */ +extern int strncmp(); /* compare up to $n$ string characters */ +extern char* strncpy(); /* copy up to $n$ string characters */ +extern char *strerror(); + + +@ The following parameters should be sufficient for most +applications of \.{CTIE}. +@^system dependencies@> + +@d buf_size 1024 /* maximum length of one input line */ +@d max_file_index 32 +/* we don't think that anyone needs more than 32 change files */ +@d xisupper(c) (isupper(c)&&((unsigned char)c<0200)) + + +@ We introduce a history variable that allows us to set a +return code if the operating system can use it. +First we introduce the coded values for the history. +This variable must be initialized. +(We do this even if the value given may be the default for +variables, just to document the need for the initial value.) +@d spotless 0 +@d troublesome 1 +@d fatal 2 + +@<Global variables@>= +int history=spotless; + + +@* Input and output. +Standard output for the user is done by writing on |stdout|. Error +messages are written to |stderr|. Terminal input is not needed in +this version of \.{CTIE}. |stdin|, |stdout| and |stderr| are +predefined as we include the \.{stdio.h} definitions. + +@<Global \&{\#include}s@>= +#include <stdio.h> + + +@ And we need dynamic memory allocation. +This should cause no trouble in any \CEE/ program. +@^system dependencies@> + +@<Global \&{\#include}s@>= +#ifdef __STDC__ +#include <stdlib.h> +#else +#include <malloc.h> +#endif + + +@* Data structures. +The multiple primary input files (master file and change +files) are treated the same way. To organize the +simultaneous usage of several input files, we introduce the +data type \&{in\_file\_modes}. + +The mode |search| indicates that \.{CTIE} searches for a +match of the input line with any line of an input file in +|reading| mode. |test| is used whenever a match is found +and it has to be tested if the next input lines do match +also. |reading| describes that the lines can be read without +any check for matching other lines. |ignore| denotes +that the file cannot be used. This may happen because an +error has been detected or because the end of the file has +been found. + +\leavevmode |file_types| is used to describe whether a file +is a master file or a change file. The value |unknown| is added +to this type to set an initial mode for the output file. +This enables us to check whether any option was used to select +the kind of output. (this would even be necessary if we +would assume a default action for missing options.) + +@<Global types@>= +#define search 0 +#define test 1 +#define reading 2 +#define ignore 3 +typedef int in_file_modes; /* should be |enum(search, test, reading, ignore)| */ +#define unknown 0 +#define master 1 +#define chf 2 +typedef int file_types; /* should be |enum(unknown, master, chf)| */ + + +@ A variable of type |out_md_type| will tell us in what state the output +change file is during processing. |normal| will be the state, when we +did not yet start a change, |pre| will be set when we write the lines +to be changes and |post| will indicate that the replacement lines are +written. + +@<Global types@>= +#define normal 0 +#define pre 1 +#define post 2 +typedef int out_md_type; /* should be |enum(normal, pre, post)| */ + + +@ The next type will indicate variables used as an index into the file +table. + +@<Global types@>= +typedef int file_index; /* |-1..max_file_index+1| */ + + +@ This is the data structure in which we collect information about +each include file. + +@<Global types@>= +typedef struct _indsc { + char file_name[max_file_name_length]; + long line; + FILE *the_file; + struct _indsc *parent; +} include_description; + + +@ The following data structure contains all of the information needed +to use these input files. +%`line' is a normal identifier throughout this program +@f line dummy +@<Global types@>= +typedef struct _idsc{ + string file_name; + char buffer[buf_size]; + in_file_modes mode; + long line; + file_types type_of_file; + include_description *current_include; + char *buffer_end; + char *limit; + char *loc; + FILE *the_file; + int dont_match; + } input_description; + + +@ Every one of the primary input files might include another file +using the \.{@@i} include mechanism. In turn, each of these might +include other files, and so on. We allow a limited number of these +files to be opened simultaneously, and we store information about the +currently open include files as a linked list attached to each primary +file. + +@d max_include_files 20 + /* maximum number of include files open simultaneously */ +@d max_file_name_length 60 + +@<Global variables@>= +int total_include_files = 0; /* count 'em */ + +@ The following variables refer to the files in action, the number of +change files, the mode of operation and the current output state. + +@<Global variables@>= +file_index actual_input, test_input, no_ch; +file_types prod_chf=unknown; +out_md_type out_mode; + +@ And the |actual_input| and |out_mode| variables need to be +initialised sensibly. + +@<Initialise parameters@>= +actual_input=0; +out_mode=normal; + +@ All primary input files (including the master file) are recorded +in the following structure. +The components are usually accessed through a local pointer variable, +requiring only a one-time-computation of the index expression. + +@<Global variables@>= +input_description *input_organisation[max_file_index+1]; + + +@* File I/O. +The basic function |get_line| can be used to get a line from +an input file. The line is stored in the |buffer| part of the +descriptor. The components |limit| and |line| are updated. If the +end of the file is reached |mode| is set to |ignore|. On some systems +it might be useful to replace tab characters by a proper number of +spaces since several editors used to create change files insert tab +characters into a source file not under control of the user. So it +might be a problem to create a matching change file. +@^tab character expansion@> + +We define |get_line| to read a line from a file specified by the +corresponding file descriptor. This function returns |true| if it is +successful and |false| if the end of the file has been reached. + +@<Internal functions@>= +boolean get_line(i, do_includes) + file_index i; boolean do_includes; +{ + register input_description *inp_desc=input_organisation[i]; + register FILE *fp; + + if (inp_desc->mode==ignore) return false; + + restart: + if (inp_desc->current_include != NULL) { + register include_description *inc_desc=inp_desc->current_include; + + fp=inc_desc->the_file; + @<Get include line into buffer or |goto restart| if end of file@>@; + } + else { + fp=inp_desc->the_file; + @<Get line into buffer, |return false| if end of file@>@; + } + + if (do_includes) + @<Check for \.{@@i} in newly read line, |goto restart| if + include fails@>@; + return true; +} + + +@ Lines must fit into the buffer completely. +We read all characters sequentially until an end of line is found +(but do not forget to check for |EOF|!). +Too long input lines will be truncated. +This will result in a damaged output if they occur in the +replacement part of a change file, or in an incomplerte check if the +matching part is concerned. +Tab character expansion might be done here. +@^tab character expansion@> + +@<Get line into buffer,...@>= +{ + register int c; /* the actual character read */ + register char *k; /* where the next character goes */ + + if (feof(fp)) + @<Handle end of file and return@>@; + + inp_desc->limit = k = inp_desc->buffer; /* beginning of buffer */ + while (k<=inp_desc->buffer_end && (c=getc(fp)) != EOF && c!='\n') + if ((*(k++) = c) != ' ') inp_desc->limit = k; + if (k>inp_desc->buffer_end) + if ((c=getc(fp))!=EOF && c!='\n') { + ungetc(c, fp); inp_desc->loc=inp_desc->buffer; + err_print(i, "! Input line too long"); +@.Input line too long@> + } + if (c==EOF && inp_desc->limit==inp_desc->buffer) + @<Handle end of file...@>@; + + @<Increment the line number and print a progess report at + certain times@>@; +} + + +@ End of file is special if this file is the master file. +Then we set the global flag variable |input_has_ended|. + +@<Handle end of file ...@>= +{ + inp_desc->mode=ignore; + inp_desc->limit=NULL; /* mark end-of-file */ + if (inp_desc->type_of_file==master) input_has_ended=true; + fclose(fp); + return false; +} + + +@ This variable must be declared for global access. + +@<Global variables@>= +boolean input_has_ended=false; + + +@ This section does what its name says. Every 100 lines +in the master file we print a dot, every 500 lines the number +of lines is shown. + +@<Increment the line number and print ...@>= +inp_desc->line++; +if (inp_desc->type_of_file==master && inp_desc->line % 100==0) { + if (inp_desc->line % 500 == 0) printf("%ld", inp_desc->line); + else putchar('.'); + fflush(stdout); +} + + +@ The following is very similar to the above, but for the case where +we are reading from an include file. + +@<Get include line into buffer or |goto restart| if end of file@>= +{ + register int c; /* the actual character read */ + register char *k; /* where the next character goes */ + + if (feof(fp)) + @<Handle end of include file and |goto restart|@>@; + + inp_desc->limit = k = inp_desc->buffer; /* beginning of buffer */ + while (k<=inp_desc->buffer_end && (c=getc(fp)) != EOF && c!='\n') + if ((*(k++) = c) != ' ') inp_desc->limit = k; + if (k>inp_desc->buffer_end) + if ((c=getc(fp))!=EOF && c!='\n') { + ungetc(c, fp); inp_desc->loc=inp_desc->buffer; + err_print(i, "! Input line too long"); +@.Input line too long@> + } + if (c==EOF && inp_desc->limit==inp_desc->buffer) + @<Handle end of include file...@>@; + + inc_desc->line++; +} + +@ We don't bail out if we find the end of an include file, we just +return to the parent file. + +@<Handle end of include file and |goto restart|@>= +{ + include_description *temp=inc_desc->parent; + + fclose(fp); + free(inc_desc); + total_include_files--; + inp_desc->current_include=temp; + goto restart; +} + + +@ Usually, we have to check the line we have just read to see whether +it begins with \.{@@i} and therefore needs expanding. + +@<Check for \.{@@i} in newly read line...@>= +{ + inp_desc->loc=inp_desc->buffer; + *inp_desc->limit=' '; + if (*inp_desc->buffer=='@@' && + (inp_desc->buffer[1]=='i' || inp_desc->buffer[1]=='I')) { + inp_desc->loc=inp_desc->buffer+2; + *inp_desc->limit='"'; /* this will terminate the search in all cases */ + while (*inp_desc->loc==' '||*inp_desc->loc=='\t') + inp_desc->loc++; + if (inp_desc->loc>=inp_desc->limit) { + err_print(i, "! Include file name not given"); +@.Include file name ...@> + goto restart; + } + if (total_include_files>=max_include_files) { + err_print(i, "! Too many nested includes"); +@.Too many nested includes@> + goto restart; + } + total_include_files++; /* push input stack */ + @<Try to open include file, abort push if unsuccessful, go to |restart|@>; + } +} + + +@ When an \.{@@i} line is found in the file, we must temporarily +stop reading it and start reading from the named include file. The +\.{@@i} line should give a complete file name with or without +double quotes. +If the environment variable \.{CWEBINPUTS} is set, or if the compiler flag +of the same name was defined at compile time, +\.{CWEB} will look for include files in the directory thus named, if +it cannot find them in the current directory. +(Colon-separated paths are not supported.) +The remainder of the \.{@@i} line after the file name is ignored. + +@d too_long() {total_include_files--; free(new_inc); + err_print(i, "! Include file name too long"); goto restart;} + +@<Try to open...@>= +{ + include_description *new_inc; + char temp_file_name[max_file_name_length]; + char *file_name_end; + char *k, *kk; + int l; /* length of file name */ + + new_inc=(include_description *) malloc(sizeof(include_description)); + if (new_inc==NULL) + fatal_error(i, "! No memory for new include descriptor", ""); + new_inc->line=0; + k=new_inc->file_name; + file_name_end=k+max_file_name_length-1; + + if (*inp_desc->loc=='"') { + inp_desc->loc++; + while (*inp_desc->loc!='"' && k<=file_name_end) + *k++=*inp_desc->loc++; + if (inp_desc->loc==inp_desc->limit) + k=file_name_end+1; /* unmatched quote is `too long' */ + } else + while (*inp_desc->loc!=' '&&*inp_desc->loc!='\t'&& + *inp_desc->loc!='"'&&k<=file_name_end) *k++=*inp_desc->loc++; + if (k>file_name_end) too_long(); +@.Include file name ...@> + *k='\0'; + if ((new_inc->the_file=fopen(new_inc->file_name, "r"))!=NULL) { + new_inc->parent=inp_desc->current_include; /* link it in */ + inp_desc->current_include=new_inc; + goto restart; /* success */ + } + kk=getenv("CWEBINPUTS"); + if (kk!=NULL) { + if ((l=strlen(kk))>max_file_name_length-2) too_long(); + strcpy(temp_file_name, kk); + } + else { +#ifdef CWEBINPUTS + if ((l=strlen(CWEBINPUTS))>max_file_name_length-2) too_long(); + strcpy(temp_file_name, CWEBINPUTS); +#else + l=0; +#endif /* |CWEBINPUTS| */ + } + if (l>0) { + if (k+l+2>=file_name_end) too_long(); + for (; k>= new_inc->file_name; k--) *(k+l+1)=*k; + strcpy(new_inc->file_name, temp_file_name); + new_inc->file_name[l]='/'; /* \UNIX/ pathname separator */ + if ((new_inc->the_file=fopen(new_inc->file_name, "r"))!=NULL) { + new_inc->parent=inp_desc->current_include; /* link it in */ + inp_desc->current_include=new_inc; + goto restart; /* success */ + } + } + total_include_files--; + free(new_inc); + err_print(i, "! Cannot open include file"); +@.Cannot open include file@> + goto restart; +} + + + +@* Reporting errors to the user. +There may be errors if a line in a given change +file does not match a line in the master file or a +replacement in a previous change file. Such errors are +reported to the user by saying +$$ + \hbox{|err_print(file_no, "! Error message")|;} +$$ +where |file_no| is the number of the file which is concerned by the +error. Please note that no trailing dot is supplied in the error +message because it is appended by |err_print|. + +@<Predecl...@>= +void err_print(); + +@ Here is the outline of the |err_print| function. + +@<Error handling...@>= +void err_print(i, s) /* prints `\..' and location of error message */ +file_index i; char *s; +{ + char *k, *l; /* pointers into an appropriate |buffer| */ + fprintf(stderr, *s=='!'? "\n%s" : "%s", s); + if(i>=0) @<Print error location based on input buffer@>@; + else putc('\n', stderr); + fflush(stderr); + history=troublesome; +} + +@ The error locations can be indicated by using the variables +|loc|, |line| and |file_name| within the appropriate file description +structures, which tell respectively the first +unlooked-at position in the |buffer|, the current line number and the +current file. We can determine whether we are looking at an included +file or not by examining the |current_include| variable. +This routine should be modified on systems whose standard text editor +has special line-numbering conventions. +@^system dependencies@> + +@<Print error location based on input buffer@>= +{ + register input_description *inp_desc=input_organisation[i]; + register include_description *inc_desc=inp_desc->current_include; + + if (inc_desc!=NULL) { + fprintf(stderr, ". (l. %ld of include file %s", inc_desc->line, + inc_desc->file_name); + fprintf(stderr, " included from l. %ld of %s file %s)\n", + inp_desc->line, + inp_desc->type_of_file==master?"master":"change", + inp_desc->file_name); + } + else + fprintf(stderr, ". (l. %ld of %s file %s)\n", inp_desc->line, + inp_desc->type_of_file==master?"master":"change", + inp_desc->file_name); + l=(inp_desc->loc>=inp_desc->limit? inp_desc->limit: inp_desc->loc); + if (l>inp_desc->buffer) { + for (k=inp_desc->buffer; k<l; k++) + if (*k=='\t') putc(' ', stderr); + else putc(*k, stderr); /* print the characters already read */ + putc('\n', stderr); + for (k=inp_desc->buffer; k<l; k++) + putc(' ', stderr); /* space out the next line */ + } + for (k=l; k<inp_desc->limit; k++) + putc(*k, stderr); /* print the part not yet read */ + putc('\n', stderr); +} + + +@ Non recoverable errors are handled by calling |fatal_error| that +outputs a message and then calls `|wrap_up|' and exits. |err_print| +will print the error message followed by an indication of where the +error was spotted in the source files. |fatal_error| cannot state any +files because the problem is usually to access these. + +@d fatal_error(i, s, t) { + fprintf(stderr, "\n%s", s); + err_print(i, t); + history=fatal; + exit(wrap_up()); + } + + +@ Some implementations may wish to pass the |history| value to the +operating system so that it can be used to govern whether or not other +programs are started. Here, for instance, we pass the operating system +a status of~0 if and only if only harmless messages were printed. +@^system dependencies@> + +@<Internal func...@>= +int wrap_up() +{ + @<Print the job |history|@>; + if (history > spotless) return 1; + else return 0; +} + +@ Always good to prototype. + +@<Predecl...@>= +int wrap_up(); + +@ We report the history to the user, although this may not +be ``\UNIX/'' style---but we are in good company: \.{WEB} and +\TEX/ do the same. We put this on |stdout| rather than |stderr|, so +that users can easily filter this away if they wish. +@^system dependencies@> + +@<Print the job |history|@>= +switch (history) { + case spotless: + printf("\n(No errors were found.)\n"); break; + case troublesome: + printf("\n(Pardon me, but I think I spotted something wrong.)\n"); break; + case fatal: printf("(That was a fatal error, my friend.)\n"); +} /* there are no other cases */ + + +@ If there's a system error, we may be able to give the user more +information with the |pfatal_error| function. This prints out system +error information if it is available. + +@<Predecl...@>= +void pfatal_error(); + +@ @<Error handling...@>= +void pfatal_error(s, t) +char *s, *t; +{ + char *strerr=strerror(errno); + + fprintf(stderr, "\n%s%s", s, t); + if (strerr) fprintf(stderr, " (%s)\n", strerr); + else fprintf(stderr, "\n"); + history=fatal; + exit(wrap_up()); +} + + +@ We need an include file for the above. + +@<Global \&{\#include}s@>= +#include <errno.h> + + +@* Handling multiple change files. +In the standard version we take the name of the +files from the command line. +It is assumed that filenames can be used as given in the +command line without changes. + +First there are some sections to open all files. +If a file is not accessible, the run will be aborted. +Otherwise the name of the open file will be displayed. + +@<Prepare the output file@>= +{ + out_file=fopen(out_name, "w"); + if (out_file==NULL) { + pfatal_error("! Cannot open/create output file", ""); +@.Cannot open/create output file@> + } +} + + +@ The name of the file and the file desciptor are stored in +global variables. + +@<Global variables@>= +FILE *out_file; +string out_name; + + +@ For the master file we start by reading its first line into the +buffer, if we could open it. + +@<Get the master file started@>= +{ + input_organisation[0]->the_file= + fopen(input_organisation[0]->file_name, "r"); + + if (input_organisation[0]->the_file==NULL) + pfatal_error("! Cannot open master file ", + input_organisation[0]->file_name); +@.Cannot open master file@> + printf("(%s)\n", input_organisation[0]->file_name); + input_organisation[0]->type_of_file=master; + get_line(0, true); +} + + +@ For the change files we must skip any comment part and see whether +there are any changes in it. This is done by |init_change_file|. + +@<Prepare the change files@>= +{ + file_index i; + + i=1; + while (i<no_ch) { + input_organisation[i]->the_file= + fopen(input_organisation[i]->file_name, "r"); + if (input_organisation[i]->the_file==NULL) + pfatal_error("! Cannot open change file ", + input_organisation[i]->file_name); +@.Cannot open change file@> + printf("(%s)\n", input_organisation[i]->file_name); + init_change_file(i); + i++; + } +} + + +@*Input/output organisation. +Here's a simple function that checks if two lines +are different. + +@<Internal functions@>= +boolean lines_dont_match(i, j) + file_index i, j; +{ + register input_description *iptr=input_organisation[i], + *jptr=input_organisation[j]; + + if (iptr->limit-iptr->buffer != jptr->limit-jptr->buffer) + return true; + return strncmp(iptr->buffer, jptr->buffer, iptr->limit-iptr->buffer); +} + + +@ Function |init_change_file(i)| is used to ignore all +lines of the input file with index~|i| until the next change +module is found. + +@<Internal functions@>= +void init_change_file(i) + file_index i; +{ + register input_description *inp_desc=input_organisation[i]; + char ccode; + + inp_desc->limit=inp_desc->buffer; + @<Skip over comment lines; |return| if end of file@>@; + @<Skip to the next nonblank line; |return| if end of file@>@; + inp_desc->dont_match=0; +} + + +@ While looking for a line that begins with \.{@@x} in the change +file, we allow lines that begin with \.{@@}, as long as they don't +begin with \.{@@y}, \.{@@z} or \.{@@i} (which would probably mean that +the change file is fouled up). + +@<Skip over comment lines...@>= +while(1) { + if (!get_line(i, false)) return; /* end of file reached */ + if (inp_desc->limit<inp_desc->buffer+2) continue; + if (inp_desc->buffer[0]!='@@') continue; + ccode=inp_desc->buffer[1]; + if (xisupper(ccode)) ccode=tolower(ccode); + if (ccode=='x') break; + if (ccode=='y' || ccode=='z' || ccode=='i') { + inp_desc->loc=inp_desc->buffer+2; + err_print(i, "! Missing @@x in change file"); +@.Missing @@x...@> + } +} + +@ Here we are looking at lines following the \.{@@x}. + +@<Skip to the next nonblank line...@>= +do { + if (!get_line(i, true)) { + err_print(i, "! Change file ended after @@x"); +@.Change file ended...@> + return; + } +} while (inp_desc->limit==inp_desc->buffer); + + +@ The |put_line| function is used to write a line from +input buffer |j| to the output file. + +@<Internal functions@>= +void put_line(j) + file_index j; +{ + char *ptr=input_organisation[j]->buffer; + char *lmt=input_organisation[j]->limit; + + while(ptr<lmt) putc(*ptr++, out_file); + putc('\n', out_file); +} + + +@ The function |e_of_ch_module| returns true if the input +line from file |i| starts with \.{@@z}. + +@<Internal functions@>= +boolean e_of_ch_module(i) + file_index i; +{ + register input_description *inp_desc=input_organisation[i]; + + if (inp_desc->limit==NULL) { + err_print(i, "! Change file ended without @@z"); +@.Change file ended without @@z@> + return true; + } else if (inp_desc->limit>=inp_desc->buffer+2) + if (inp_desc->buffer[0]=='@@' && + (inp_desc->buffer[1]=='Z' || inp_desc->buffer[1]=='z')) + return true; + return false; +} + + +@ The function |e_of_ch_preamble| returns |true| if the input +line from file~|i| starts with \.{@@y}. + +@<Internal functions@>= +boolean e_of_ch_preamble(i) + file_index i; +{ + register input_description *inp_desc=input_organisation[i]; + + if (inp_desc->limit>=inp_desc->buffer+2 && inp_desc->buffer[0]=='@@') + if (inp_desc->buffer[1]=='Y'||inp_desc->buffer[1]=='y') { + if (inp_desc->dont_match>0) { + inp_desc->loc=inp_desc->buffer+2; + fprintf(stderr, "\n! Hmm... %d ", inp_desc->dont_match); + err_print(i, "of the preceding lines failed to match"); + } + return true; + } + return false; +} + + + +@ To process the input file the next section reads a line of the +current (actual) input file and updates the |input_organisation| for +all files with index greater than |actual_input|. + +@<Process a line, |break| when end of source reached@>= +{ + file_index test_file; + + @<Check the current files for any ends of changes@>@; + if (input_has_ended && actual_input==0) break; /* all done */ + @<Scan all other files for changes to be done@>@; + @<Handle output@>@; + @<Step to next line@>@; +} + + +@ Any of the current change files may have reached the end of the +current change. In such a case, intermediate lines must be skipped +and the next start of change is to be found. This may make a change +file become inactive if the end of the file is reached. + +@<Check the...@>= +{ + register input_description *inp_desc; + while (actual_input>0 && e_of_ch_module(actual_input)) { + inp_desc=input_organisation[actual_input]; + if (inp_desc->type_of_file==master) { + /* emergency exit, everything mixed up!*/ + fatal_error(-1, "! This can't happen: change file is master file", ""); +@.This can't happen...@> + } + inp_desc->mode=search; + init_change_file(actual_input); + while ((input_organisation[actual_input]->mode!=reading + && actual_input>0)) + actual_input--; + } +} + + +@ Now we will set |test_input| to the first change file that is being +tested against the current line. If no other file is testing, then +|actual_input| refers to a line to write and |test_input| is set to +|none|. + +@d none (-1) + +@<Scan all other files...@>= +test_input=none; +test_file=actual_input; +while (test_input==none && test_file<no_ch-1){ + test_file++; + switch (input_organisation[test_file]->mode) { + case search: + if (lines_dont_match(actual_input, test_file)==false) { + input_organisation[test_file]->mode=test; + test_input=test_file; + } + break; + case test: + if (lines_dont_match(actual_input, test_file)) { + /* error, sections do not match; just note at this point */ + input_organisation[test_file]->dont_match++; + } + test_input=test_file; + break; + case reading: /* this can't happen */ + break; + case ignore: /* nothing to do */ + break; + } +} + + +@ For the output we must distinguish between whether we are creating a +new change file or a new master file. Change file creation requires +closer inspection because we may be before a change, in the pattern +(match) part or in the replacement part. For master file creation, we +simply have to write the line from the current (actual) input. + +@<Handle output@>= +if (prod_chf==chf) { + while(1) { + @<Test for |normal|, |break| when done@>@; + @<Test for |pre|, |break| when done@>@; + @<Test for |post|, |break| when done@>@; + } +} else + if (test_input==none) put_line(actual_input); + + +@ Check whether we have to start a change file entry. +Without a match nothing needs to be done. + +@<Test for |normal|...@>= +if (out_mode==normal) { + if (test_input!=none) { + fprintf(out_file, "@@x\n"); + out_mode=pre; + } else break; +} + + +@ Check whether we have to start the replacement text. This is the +case when we are in |pre| mode but have no more matching lines. +Otherwise the master file source line must be copied to the change +file. + +@<Test for |pre|...@>= +if (out_mode==pre) { + if (test_input==none) { + fprintf(out_file, "@@y\n"); + out_mode=post; + } else { + if (input_organisation[actual_input]->type_of_file==master) + put_line(actual_input); + break; + } +} + + +@ Check whether an entry from a change file is complete. If the +current input is from a change file which is not being tested against +a later change file, then this change file line must be written. If +the actual input has been reset to the master file, we can finish this +change. + +@<Test for |post|...@>= +if (out_mode==post) { + if (input_organisation[actual_input]->type_of_file==chf) { + if (test_input==none) put_line(actual_input); + break; + } else { + fprintf(out_file, "@@z\n\n"); + out_mode=normal; + } +} + + +@ If we had a change, we must proceed in the actual file +to be changed and in the change file in effect. + +@<Step to next line@>= +get_line(actual_input, true); +if (test_input!=none) { + get_line(test_input, true); + if (e_of_ch_preamble(test_input)==true) { + get_line(test_input, true); /* update current changing file */ + input_organisation[test_input]->mode=reading; + actual_input=test_input; + test_input=none; + } +} + + +@ To create the new output file we have to scan the whole +master file and all changes in effect when it ends. +At the very end it is wise to check for all changes +to have completed, in case the last line of the master file +was to be changed. + +@<Process the input@>= +actual_input=0; +input_has_ended=false; +while (input_has_ended==false||actual_input!=0) + @<Process a line...@>@; +if (out_mode==post) /* last line has been changed */ + fprintf(out_file, "@@z\n"); + + +@ At the end of the program, we will tell the user if the +change file had a line that didn't match any relevant line +in the master file or any of the change files. + +@<Check that all changes have been read@>= +{ + file_index i; + + for (i=1;i<no_ch;i++) { /* all change files */ + if (input_organisation[i]->mode!=ignore) { + input_organisation[i]->loc=input_organisation[i]->buffer; + err_print(i, "! Change file entry did not match"); +@.Change file entry ...@> + } + } +} + + +@ We want to tell the user about our command line options if they made +a mistake. This is done by the |usage_error()| function. It contains +merely the necessary print statements and exits afterwards. + +@<Intern...@>= +void usage_error() +{ + @<Print the banners@>; + fprintf(stderr, "Usage: ctie -[mc] outfile master changefile(s)\n"); + fprintf(stderr, "Type ctie --help for more information\n"); + exit(1); +} + + +@ Printing our welcome banners; we only do this if we are not asked +for version or help information. + +@<Print the banners@>= +printf("%s\n", banner); /* print a ``banner line'' */ +printf("%s\n", copyright); /* include the copyright notice */ + + +@ We must scan through the list of parameters, given in |argv|. The +number is in |argc|. We must pay attention to the flag parameter. We +need at least 3~parameters (\.{-m} or \.{-c}, an output file and a +master file) and can handle up to |max_file_index| change files. The +names of the file parameters will be inserted into the structure of +|input_organisation|. The first file is special. It indicates the +output file. When we allow flags at any position, we must find out +which name is for what purpose. The master file is already part of +the |input_organisation| structure (index~0). As long as the number +of files found (counted in |no_ch|) is |-1| we have not yet found the +output file name. + +@<Scan the parameters@>= +{ + if (argc>max_file_index+5-1) usage_error(); + no_ch = -1; /* fill this part of |input_organisation| */ + while (--argc>0) { + argv++; + if (strcmp("-help", *argv)==0 || strcmp("--help", *argv)==0) + @<Display help message and exit@>; + if (strcmp("-version", *argv)==0 || strcmp("--version", *argv)==0) + @<Display version information and exit@>; + if (**argv=='-') @<Set a flag@>@; + else @<Get a file name@>@; + } + if (no_ch<=0|| prod_chf==unknown) usage_error(); +} + + +@ The flag is about to determine the processing mode. +We must make sure that this flag has not been set before. +Further flags might be introduced to avoid/force overwriting of +output files. +Currently we just have to set the processing flag properly. + +@<Set a flag@>= +if (prod_chf!=unknown) usage_error(); +else + switch (*(*argv+1)) { + case 'c': case 'C': prod_chf=chf; break; + case 'm': case 'M': prod_chf=master; break; + default: usage_error(); + } + + +@ We have to distinguish whether this is the very first file name +(which is the case if |no_ch==(-1)|) or if the next element of +|input_organisation| must be filled. + +@<Get a file name@>= +{ + if (no_ch==(-1)) { + out_name=*argv; + } else { + register input_description *inp_desc; + + inp_desc=(input_description *) malloc(sizeof(input_description)); + if (inp_desc==NULL) + fatal_error(-1, "! No memory for input descriptor", ""); +@.No memory for descriptor@> + inp_desc->mode=search; + inp_desc->line=0; + inp_desc->type_of_file=chf; + inp_desc->limit=inp_desc->buffer; + inp_desc->buffer[0]=' '; + inp_desc->loc=inp_desc->buffer+1; + inp_desc->buffer_end=inp_desc->buffer+buf_size-2; + inp_desc->file_name=*argv; + inp_desc->current_include=NULL; + input_organisation[no_ch]=inp_desc; + } + no_ch++; +} + + +@ Modules for dealing with help messages and version info. We follow +the \.{kpathsea} standard code here, so that we can easily adapt this +to work with \.{kpathsea}. + +@<Display help message and exit@>= +usage_help(); +@.--help@> + + +@ +@<Display version information and exit@>= +{ + print_version_and_exit("CTIE", version_number); +@.--version@> +} + + +@ Here is the usage information for \.{--help}. + +@<Global variables@>= +string CTIEHELP[] = { + "Usage: ctie -[mc] outfile master changefile(s)", + " Create a new master file or change file from the given", + " master (C)WEB file and changefiles.", + " All filenames are taken literally; no suffixes are added.", + "", + "-m create a new master file from original (C)WEB and change file(s)", + "-c create a master change file for original (C)WEB file from changefile(s)", + "--help display this help and exit", + "--version display version information and exit", + NULL +}; + + +@ @<Predec...@>= +void usage_help(); +void print_version_and_exit(); + + +@ @c +void usage_help() +{ + string *message=CTIEHELP; + + while (*message) { + fputs(*message, stdout); + putchar('\n'); + ++message; + } + putchar('\n'); + exit(0); +} + + +@ @c +void print_version_and_exit(name, version) + string name, version; +{ + printf ("%s %s\n", name, version); + + puts ("Copyright (C) 2002,2003 Julian Gilbey."); + + puts ("There is NO warranty. This is free software. See the source"); + puts ("code of CTIE for redistribution conditions."); + + exit (0); +} + + +@* System-dependent changes. +This section should be replaced, if necessary, by +changes to the program that are necessary to make \.{CTIE} +work at a particular installation. It is usually best to +design your change file so that all changes to previous +modules preserve the module numbering; then everybody's +version will be consistent with the printed program. More +extensive changes, which introduce new modules, can be +inserted here; then only the index itself will get a new +module number. +@^system dependencies@> + + +@* Index. |