summaryrefslogtreecommitdiff
path: root/web/c_cpp/cweb/examples/wmerge.w
diff options
context:
space:
mode:
Diffstat (limited to 'web/c_cpp/cweb/examples/wmerge.w')
-rw-r--r--web/c_cpp/cweb/examples/wmerge.w717
1 files changed, 717 insertions, 0 deletions
diff --git a/web/c_cpp/cweb/examples/wmerge.w b/web/c_cpp/cweb/examples/wmerge.w
new file mode 100644
index 0000000000..616172d042
--- /dev/null
+++ b/web/c_cpp/cweb/examples/wmerge.w
@@ -0,0 +1,717 @@
+% Modified 16 Jan 2002 to agree with COMMON version 3.64
+
+\def\9#1{} % this hack is explained in CWEB manual Appendix F11
+
+@* Introduction. This file contains the program \.{wmerge},
+which takes two or more files and merges them according
+to the conventions of \.{CWEB}. Namely, it takes an ordinary \.{.w}
+file and and optional \.{.ch} file and sends the corresponding
+\.{.w}-style file to standard output (or to a named file),
+expanding all ``includes''
+that might be specified by \.{@@i} in the original \.{.w} file.
+(A more precise description appears in the section on ``command line
+arguments'' below.)
+
+@c
+#include <stdio.h>
+#include <stdlib.h> /* declaration of |getenv| */
+#include <ctype.h> /* definition of |isalpha|, |isdigit| and so on */
+@<Definitions@>@;
+@<Predeclarations of functions@>@;
+@<Functions@>@;
+main (ac,av)
+int ac; char **av;
+{
+ argc=ac; argv=av;
+ @<Set the default options@>;
+ @<Scan arguments and open output file@>;
+ reset_input();
+ while (get_line())
+ put_line();
+ fflush(out_file);
+ check_complete();
+ fflush(out_file);
+ return wrap_up();
+}
+
+@ @<Definitions@>=
+typedef short boolean;
+typedef unsigned char eight_bits;
+typedef char ASCII; /* type of characters inside \.{WEB} */
+
+@ 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 size_t 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 */
+
+@ @<Predec...@>=
+
+@ The lowest level of input to the \.{WEB} programs
+is performed by |input_ln|, which must be told which file to read from.
+The return value of |input_ln| is 1 if the read is successful and 0 if
+not (generally this means the file has ended).
+The characters of the next line of the file
+are copied into the |buffer| array,
+and the global variable |limit| is set to the first unoccupied position.
+Trailing blanks are ignored. The value of |limit| must be strictly less
+than |buf_size|, so that |buffer[buf_size-1]| is never filled.
+
+Some of the routines use the fact that it is safe to refer to
+|*(limit+2)| without overstepping the bounds of the array.
+
+@d buf_size 4096
+
+@<Definitions...@>=
+ASCII buffer[buf_size]; /* where each line of input goes */
+ASCII *buffer_end=buffer+buf_size-2; /* end of |buffer| */
+ASCII *limit; /* points to the last character in the buffer */
+ASCII *loc; /* points to the next character to be read from the buffer */
+
+@ In the unlikely event that your standard I/O library does not
+support |feof|, |getc| and |ungetc|, you may have to change things here.
+@^system dependencies@>
+
+Incidentally, here's a curious fact about \.{CWEB} for those of you
+who are reading this file as an example of \.{CWEB} programming.
+The file \.{stdio.h} includes a typedef for
+the identifier |FILE|, which is not, strictly speaking, part of \CEE/.
+It turns out \.{CWEAVE} knows that |FILE| is a reserved word (after all,
+|FILE| is almost as common as |int|); indeed, \.{CWEAVE} knows all
+the types of the ISO standard \CEE/ library. But
+if you're using other types like {\bf caddr\_t},
+@:caddr_t}{\bf caddr\_t@>
+which is defined in \.{/usr/include/sys/types.h}, you should let
+\.{WEAVE} know that this is a type, either by including the \.{.h} file
+at \.{WEB} time (saying \.{@@i /usr/include/sys/types.h}), or by
+using \.{WEB}'s format command (saying \.{@@f caddr\_t int}). Either of
+these will make {\bf caddr\_t} be treated in the same way as |int|.
+
+@<Func...@>=
+input_ln(fp) /* copies a line into |buffer| or returns 0 */
+FILE *fp; /* what file to read from */
+{
+ register int c=EOF; /* character read; initialized so some compilers won't complain */
+ register char *k; /* where next character goes */
+ if (feof(fp)) return(0); /* we have hit end-of-file */
+ limit = k = buffer; /* beginning of buffer */
+ while (k<=buffer_end && (c=getc(fp)) != EOF && c!='\n')
+ if ((*(k++) = c) != ' ') limit = k;
+ if (k>buffer_end)
+ if ((c=getc(fp))!=EOF && c!='\n') {
+ ungetc(c,fp); loc=buffer; err_print("! Input line too long");
+@.Input line too long@>
+ }
+ if (c==EOF && limit==buffer) return(0); /* there was nothing after
+ the last newline */
+ return(1);
+}
+
+@ Now comes the problem of deciding which file to read from next.
+Recall that the actual text that \.{CWEB} should process comes from two
+streams: a |web_file|, which can contain possibly nested include
+commands \.{@@i}, and a |change_file|, which might also contain
+includes. The |web_file| together with the currently open include
+files form a stack |file|, whose names are stored in a parallel stack
+|file_name|. The boolean |changing| tells whether or not we're reading
+from the |change_file|.
+
+The line number of each open file is also kept for error reporting.
+
+@f line x /* make |line| an unreserved word */
+@d max_include_depth 10 /* maximum number of source files open
+ simultaneously, not counting the change file */
+@d max_file_name_length 60
+@d cur_file file[include_depth] /* current file */
+@d cur_file_name file_name[include_depth] /* current file name */
+@d cur_line line[include_depth] /* number of current line in current file */
+@d web_file file[0] /* main source file */
+@d web_file_name file_name[0] /* main source file name */
+
+@<Definitions...@>=
+int include_depth; /* current level of nesting */
+FILE *file[max_include_depth]; /* stack of non-change files */
+FILE *change_file; /* change file */
+char file_name[max_include_depth][max_file_name_length];
+ /* stack of non-change file names */
+char change_file_name[max_file_name_length]; /* name of change file */
+char alt_web_file_name[max_file_name_length]; /* alternate name to try */
+int line[max_include_depth]; /* number of current line in the stacked files */
+int change_line; /* number of current line in change file */
+int change_depth; /* where \.{@@y} originated during a change */
+boolean input_has_ended; /* if there is no more input */
+boolean changing; /* if the current line is from |change_file| */
+boolean web_file_open=0; /* if the web file is being read */
+
+@ When |changing=0|, the next line of |change_file| is kept in
+|change_buffer|, for purposes of comparison with the next
+line of |cur_file|. After the change file has been completely input, we
+set |change_limit=change_buffer|,
+so that no further matches will be made.
+
+Here's a shorthand expression for inequality between the two lines:
+
+@d lines_dont_match (change_limit-change_buffer != limit-buffer ||
+ strncmp(buffer, change_buffer, limit-buffer))
+
+@<Def...@>=
+char change_buffer[buf_size]; /* next line of |change_file| */
+char *change_limit; /* points to the last character in |change_buffer| */
+
+@ Procedure |prime_the_change_buffer| sets |change_buffer| in
+preparation for the next matching operation. Since blank lines in the change
+file are not used for matching, we have
+|(change_limit==change_buffer && !changing)| if and only if
+the change file is exhausted. This procedure is called only when
+|changing| is 1; hence error messages will be reported correctly.
+
+@<Func...@>=
+void
+prime_the_change_buffer()
+{
+ change_limit=change_buffer; /* this value is used if the change file ends */
+ @<Skip over comment lines in the change file; |return| if end of file@>;
+ @<Skip to the next nonblank line; |return| if end of file@>;
+ @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>;
+}
+
+@ 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 in the change file...@>=
+while(1) {
+ change_line++;
+ if (!input_ln(change_file)) return;
+ if (limit<buffer+2) continue;
+ if (buffer[0]!='@@') continue;
+ if (isupper(buffer[1])) buffer[1]=tolower(buffer[1]);
+ if (buffer[1]=='x') break;
+ if (buffer[1]=='y' || buffer[1]=='z' || buffer[1]=='i') {
+ loc=buffer+2;
+ err_print("! Missing @@x in change file");
+@.Missing @@x...@>
+ }
+}
+
+@ Here we are looking at lines following the \.{@@x}.
+
+@<Skip to the next nonblank line...@>=
+do {
+ change_line++;
+ if (!input_ln(change_file)) {
+ err_print("! Change file ended after @@x");
+@.Change file ended...@>
+ return;
+ }
+} while (limit==buffer);
+
+@ @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>=
+{
+ change_limit=change_buffer+(limit-buffer);
+ strncpy(change_buffer,buffer,limit-buffer+1);
+}
+
+@ The following procedure is used to see if the next change entry should
+go into effect; it is called only when |changing| is 0.
+The idea is to test whether or not the current
+contents of |buffer| matches the current contents of |change_buffer|.
+If not, there's nothing more to do; but if so, a change is called for:
+All of the text down to the \.{@@y} is supposed to match. An error
+message is issued if any discrepancy is found. Then the procedure
+prepares to read the next line from |change_file|.
+
+This procedure is called only when |buffer<limit|, i.e., when the
+current line is nonempty.
+
+@<Func...@>=
+void
+check_change() /* switches to |change_file| if the buffers match */
+{
+ int n=0; /* the number of discrepancies found */
+ if (lines_dont_match) return;
+ while (1) {
+ changing=1; change_line++;
+ if (!input_ln(change_file)) {
+ err_print("! Change file ended before @@y");
+@.Change file ended...@>
+ change_limit=change_buffer; changing=0;
+ return;
+ }
+ if (limit>buffer+1 && buffer[0]=='@@') {
+ char xyz_code=isupper(buffer[1])? tolower(buffer[1]): buffer[1];
+ @<If the current line starts with \.{@@y},
+ report any discrepancies and |return|@>;
+ }
+ @<Move |buffer| and |limit|...@>;
+ changing=0; cur_line++;
+ while (!input_ln(cur_file)) { /* pop the stack or quit */
+ if (include_depth==0) {
+ err_print("! CWEB file ended during a change");
+@.CWEB file ended...@>
+ input_has_ended=1; return;
+ }
+ include_depth--; cur_line++;
+ }
+ if (lines_dont_match) n++;
+ }
+}
+
+@ @<If the current line starts with \.{@@y}...@>=
+if (xyz_code=='x' || xyz_code=='z') {
+ loc=buffer+2; err_print("! Where is the matching @@y?");
+@.Where is the match...@>
+ }
+else if (xyz_code=='y') {
+ if (n>0) {
+ loc=buffer+2;
+ fprintf(stderr,"\n! Hmm... %d ",n);
+ err_print("of the preceding lines failed to match");
+@.Hmm... n of the preceding...@>
+ }
+ change_depth=include_depth;
+ return;
+}
+
+@ The |reset_input| procedure gets the program ready to read the
+user's \.{WEB} input.
+
+@<Func...@>=
+void
+reset_input()
+{
+ limit=buffer; loc=buffer+1; buffer[0]=' ';
+ @<Open input files@>;
+ include_depth=0; cur_line=0; change_line=0;
+ change_depth=include_depth;
+ changing=1; prime_the_change_buffer(); changing=!changing;
+ limit=buffer; loc=buffer+1; buffer[0]=' '; input_has_ended=0;
+}
+
+@ The following code opens the input files.
+@^system dependencies@>
+
+@<Open input files@>=
+if ((web_file=fopen(web_file_name,"r"))==NULL) {
+ strcpy(web_file_name,alt_web_file_name);
+ if ((web_file=fopen(web_file_name,"r"))==NULL)
+ fatal("! Cannot open input file ", web_file_name);
+}
+@.Cannot open input file@>
+@.Cannot open change file@>
+web_file_open=1;
+if ((change_file=fopen(change_file_name,"r"))==NULL)
+ fatal("! Cannot open change file ", change_file_name);
+
+@ The |get_line| procedure is called when |loc>limit|; it puts the next
+line of merged input into the buffer and updates the other variables
+appropriately. A space is placed at the right end of the line.
+This procedure returns |!input_has_ended| because we often want to
+check the value of that variable after calling the procedure.
+
+@<Fun...@>=
+int get_line() /* inputs the next line */
+{
+ restart:
+ if (changing && include_depth==change_depth)
+ @<Read from |change_file| and maybe turn off |changing|@>;
+ if (! changing || include_depth>change_depth) {
+ @<Read from |cur_file| and maybe turn on |changing|@>;
+ if (changing && include_depth==change_depth) goto restart;
+ }
+ if (input_has_ended) return 0;
+ loc=buffer; *limit=' ';
+ if (buffer[0]=='@@' && (buffer[1]=='i' || buffer[1]=='I')) {
+ loc=buffer+2; *limit='"';
+ while (*loc==' '||*loc=='\t') loc++;
+ if (loc>=limit) {
+ err_print("! Include file name not given");
+@.Include file name ...@>
+ goto restart;
+ }
+ if (include_depth>=max_include_depth-1) {
+ err_print("! Too many nested includes");
+@.Too many nested includes@>
+ goto restart;
+ }
+ include_depth++; /* push input stack */
+ @<Try to open include file, abort push if unsuccessful, go to |restart|@>;
+ }
+ return 1;
+}
+
+void put_line()
+{
+ char *ptr=buffer;
+ while (ptr<limit) putc(*ptr++,out_file);
+ putc('\n',out_file);
+}
+
+@ When an \.{@@i} line is found in the |cur_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() {include_depth--;
+ err_print("! Include file name too long"); goto restart;}
+
+@ @<Try to open...@>= {
+ char temp_file_name[max_file_name_length];
+ char *cur_file_name_end=cur_file_name+max_file_name_length-1;
+ char *k=cur_file_name, *kk;
+ int l; /* length of file name */
+
+ if (*loc=='"') {
+ loc++;
+ while (*loc!='"' && k<=cur_file_name_end) *k++=*loc++;
+ if (loc==limit) k=cur_file_name_end+1; /* unmatched quote is `too long' */
+ } else
+ while (*loc!=' '&&*loc!='\t'&&*loc!='"'&&k<=cur_file_name_end) *k++=*loc++;
+ if (k>cur_file_name_end) too_long();
+@.Include file name ...@>
+ *k='\0';
+ if ((cur_file=fopen(cur_file_name,"r"))!=NULL) {
+ cur_line=0;
+ 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>=cur_file_name_end) too_long();
+@.Include file name ...@>
+ for (; k>= cur_file_name; k--) *(k+l+1)=*k;
+ strcpy(cur_file_name,temp_file_name);
+ cur_file_name[l]='/'; /* \UNIX/ pathname separator */
+ if ((cur_file=fopen(cur_file_name,"r"))!=NULL) {
+ cur_line=0;
+ goto restart; /* success */
+ }
+ }
+ include_depth--; err_print("! Cannot open include file"); goto restart;
+}
+
+@ @<Read from |cur_file|...@>= {
+ cur_line++;
+ while (!input_ln(cur_file)) { /* pop the stack or quit */
+ if (include_depth==0) {input_has_ended=1; break;}
+ else {
+ fclose(cur_file); include_depth--;
+ if (changing && include_depth==change_depth) break;
+ cur_line++;
+ }
+ }
+ if (!changing && !input_has_ended)
+ if (limit-buffer==change_limit-change_buffer)
+ if (buffer[0]==change_buffer[0])
+ if (change_limit>change_buffer) check_change();
+}
+
+@ @<Read from |change_file|...@>= {
+ change_line++;
+ if (!input_ln(change_file)) {
+ err_print("! Change file ended without @@z");
+@.Change file ended...@>
+ buffer[0]='@@'; buffer[1]='z'; limit=buffer+2;
+ }
+ if (limit>buffer) { /* check if the change has ended */
+ *limit=' ';
+ if (buffer[0]=='@@') {
+ if (isupper(buffer[1])) buffer[1]=tolower(buffer[1]);
+ if (buffer[1]=='x' || buffer[1]=='y') {
+ loc=buffer+2;
+ err_print("! Where is the matching @@z?");
+@.Where is the match...@>
+ }
+ else if (buffer[1]=='z') {
+ prime_the_change_buffer(); changing=!changing;
+ }
+ }
+ }
+}
+
+@ 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 |web_file|.
+
+@<Funct...@>=
+void
+check_complete(){
+ if (change_limit!=change_buffer) { /* |changing| is 0 */
+ strncpy(buffer,change_buffer,change_limit-change_buffer+1);
+ limit=buffer+(int)(change_limit-change_buffer);
+ changing=1; change_depth=include_depth; loc=buffer;
+ err_print("! Change file entry did not match");
+ @.Change file entry did not match@>
+ }
+}
+
+@* Reporting errors to the user.
+A global variable called |history| will contain one of four values
+at the end of every run: |spotless| means that no unusual messages were
+printed; |harmless_message| means that a message of possible interest
+was printed but no serious errors were detected; |error_message| means that
+at least one error was found; |fatal_message| means that the program
+terminated abnormally. The value of |history| does not influence the
+behavior of the program; it is simply computed for the convenience
+of systems that might want to use such information.
+
+@d spotless 0 /* |history| value for normal jobs */
+@d harmless_message 1 /* |history| value when non-serious info was printed */
+@d error_message 2 /* |history| value when an error was noted */
+@d fatal_message 3 /* |history| value when we had to stop prematurely */
+@d mark_harmless {if (history==spotless) history=harmless_message;}
+@d mark_error history=error_message
+
+@<Definit...@>=
+int history=spotless; /* indicates how bad this run was */
+
+@ The command `|err_print("! Error message")|' will report a syntax error to
+the user, by printing the error message at the beginning of a new line and
+then giving an indication of where the error was spotted in the source file.
+Note that no period follows the error message, since the error routine
+will automatically supply a period. A newline is automatically supplied
+if the string begins with |"!"|.
+
+The actual error indications are provided by a procedure called |error|.
+
+@<Predecl...@>=
+void err_print();
+
+@
+@<Functions...@>=
+void
+err_print(s) /* prints `\..' and location of error message */
+char *s;
+{
+ char *k,*l; /* pointers into |buffer| */
+ fprintf(stderr,*s=='!'? "\n%s" : "%s",s);
+ if(web_file_open) @<Print error location based on input buffer@>@;
+ else putc('\n',stderr);
+ update_terminal; mark_error;
+}
+
+@ The error locations can be indicated by using the global variables
+|loc|, |cur_line|, |cur_file_name| and |changing|,
+which tell respectively the first
+unlooked-at position in |buffer|, the current line number, the current
+file, and whether the current line is from |change_file| or |cur_file|.
+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@>=
+{if (changing && include_depth==change_depth)
+ fprintf(stderr,". (l. %d of change file)\n", change_line);
+else if (include_depth==0) fprintf(stderr,". (l. %d)\n", cur_line);
+ else fprintf(stderr,". (l. %d of include file %s)\n", cur_line, cur_file_name);
+l= (loc>=limit? limit: loc);
+if (l>buffer) {
+ for (k=buffer; k<l; k++)
+ if (*k=='\t') putc(' ',stderr);
+ else putc(*k,stderr); /* print the characters already read */
+ putc('\n',stderr);
+ for (k=buffer; k<l; k++) putc(' ',stderr); /* space out the next line */
+}
+for (k=l; k<limit; k++) putc(*k,stderr); /* print the part not yet read */
+ putc('\n',stderr);
+}
+
+@ When no recovery from some error has been provided, we have to wrap
+up and quit as graciously as possible. This is done by calling the
+function |wrap_up| at the end of the code.
+
+@d fatal(s,t) {
+ fprintf(stderr,s); err_print(t);
+ history=fatal_message; 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@>
+
+@<Func...@>=
+wrap_up() {
+ @<Print the job |history|@>;
+ if (history > harmless_message) return(1);
+ else return(0);
+}
+
+@ @<Print the job |history|@>=
+switch (history) {
+case spotless: if (show_happiness) fprintf(stderr,"(No errors were found.)\n"); break;
+case harmless_message:
+ fprintf(stderr,"(Did you see the warning message above?)\n"); break;
+case error_message:
+ fprintf(stderr,"(Pardon me, but I think I spotted something wrong.)\n"); break;
+case fatal_message: fprintf(stderr,"(That was a fatal error, my friend.)\n");
+} /* there are no other cases */
+
+@* Command line arguments.
+The user calls \.{wmerge} with arguments on the command line.
+These are either file names or flags to be turned off (beginning with |"-"|)
+or flags to be turned on (beginning with |"+"|.
+The following globals are for communicating the user's desires to the rest
+of the program. The various file name variables contain strings with
+the names of those files. Most of the 128 flags are undefined but available
+for future extensions.
+
+@d show_banner flags['b'] /* should the banner line be printed? */
+@d show_happiness flags['h'] /* should lack of errors be announced? */
+
+@<Defin...@>=
+int argc; /* copy of |ac| parameter to |main| */
+char **argv; /* copy of |av| parameter to |main| */
+char out_file_name[max_file_name_length]; /* name of |out_file| */
+boolean flags[128]; /* an option for each 7-bit code */
+
+@ The |flags| will be initially 1.
+
+@<Set the default options@>=
+show_banner=show_happiness=1;
+
+@ We now must look at the command line arguments and set the file names
+accordingly. At least one file name must be present: the \.{WEB}
+file. It may have an extension, or it may omit it to get |'.w'|
+added.
+
+If there is another file name present among the arguments, it is the
+change file, again either with an extension or without one to get |'.ch'|
+An omitted change file argument means that |'/dev/null'| should be used,
+when no changes are desired.
+@^system dependencies@>
+
+If there's a third file name, it will be the output file.
+
+@<Pred...@>=
+void scan_args();
+
+@
+@<Function...@>=
+void
+scan_args()
+{
+ char *dot_pos; /* position of |'.'| in the argument */
+ register char *s; /* register for scanning strings */
+ boolean found_web=0,found_change=0,found_out=0;
+ /* have these names have been seen? */
+ boolean flag_change;
+
+ while (--argc > 0) {
+ if (**(++argv)=='-' || **argv=='+') @<Handle flag argument@>@;
+ else {
+ s=*argv;@+dot_pos=NULL;
+ while (*s) {
+ if (*s=='.') dot_pos=s++;
+ else if (*s=='/') dot_pos=NULL,++s;
+ else s++;
+ }
+ if (!found_web) @<Make |web_file_name|@>@;
+ else if (!found_change) @<Make |change_file_name| from |fname|@>@;
+ else if (!found_out) @<Override output file name@>@;
+ else @<Print usage error message and quit@>;
+ }
+ }
+ if (!found_web) @<Print usage error message and quit@>;
+ if (!found_change) strcpy(change_file_name,"/dev/null");
+}
+
+@ We use all of |*argv| for the |web_file_name| if there is a |'.'| in it,
+otherwise we add |".w"|. If this file can't be opened, we prepare an
+|alt_web_file_name| by adding |"web"| after the dot.
+The other file names come from adding other things
+after the dot. We must check that there is enough room in
+|web_file_name| and the other arrays for the argument.
+
+@<Make |web_file_name|...@>=
+{
+ if (s-*argv > max_file_name_length-5)
+ @<Complain about argument length@>;
+ if (dot_pos==NULL)
+ sprintf(web_file_name,"%s.w",*argv);
+ else {
+ strcpy(web_file_name,*argv);
+ *dot_pos=0; /* string now ends where the dot was */
+ }
+ sprintf(alt_web_file_name,"%s.web",*argv);
+ *out_file_name='\0'; /* this will print to stdout */
+ found_web=1;
+}
+
+@ @<Make |change_file_name|...@>=
+{
+ if (s-*argv > max_file_name_length-4)
+ @<Complain about argument length@>;
+ if (dot_pos==NULL)
+ sprintf(change_file_name,"%s.ch",*argv);
+ else strcpy(change_file_name,*argv);
+ found_change=1;
+}
+
+@ @<Override...@>=
+{
+ if (s-*argv > max_file_name_length-5)
+ @<Complain about argument length@>;
+ if (dot_pos==NULL) sprintf(out_file_name,"%s.out",*argv);
+ else strcpy(out_file_name,*argv);
+ found_out=1;
+}
+
+@ @<Handle flag...@>=
+{
+ if (**argv=='-') flag_change=0;
+ else flag_change=1;
+ for(dot_pos=*argv+1;*dot_pos>'\0';dot_pos++)
+ flags[*dot_pos]=flag_change;
+}
+
+@ @<Print usage error message and quit@>=
+{
+ fatal("! Usage: wmerge webfile[.w] [changefile[.ch] [outfile[.out]]]\n","")@;
+}
+
+@ @<Complain about arg...@>= fatal("! Filename too long\n", *argv);
+
+@* Output. Here is the code that opens the output file:
+@^system dependencies@>
+
+@<Defin...@>=
+FILE *out_file; /* where output goes */
+
+@ @<Scan arguments and open output file@>=
+scan_args();
+if (out_file_name[0]=='\0') out_file=stdout;
+else if ((out_file=fopen(out_file_name,"w"))==NULL)
+ fatal("! Cannot open output file ", out_file_name);
+@.Cannot open output file@>
+
+@ The |update_terminal| procedure is called when we want
+to make sure that everything we have output to the terminal so far has
+actually left the computer's internal buffers and been sent.
+@^system dependencies@>
+
+@d update_terminal fflush(stderr) /* empty the terminal output buffer */
+
+@* Index.