summaryrefslogtreecommitdiff
path: root/web/funnelAC/fwACsrc/command.c
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /web/funnelAC/fwACsrc/command.c
Initial commit
Diffstat (limited to 'web/funnelAC/fwACsrc/command.c')
-rw-r--r--web/funnelAC/fwACsrc/command.c2147
1 files changed, 2147 insertions, 0 deletions
diff --git a/web/funnelAC/fwACsrc/command.c b/web/funnelAC/fwACsrc/command.c
new file mode 100644
index 0000000000..e3cdf22b33
--- /dev/null
+++ b/web/funnelAC/fwACsrc/command.c
@@ -0,0 +1,2147 @@
+/*##############################################################################
+
+FUNNNELWEB COPYRIGHT
+====================
+FunnelWeb is a literate-programming macro preprocessor.
+
+Copyright (C) 1992 Ross N. Williams.
+
+ Ross N. Williams
+ ross@spam.adelaide.edu.au
+ 16 Lerwick Avenue, Hazelwood Park 5066, Australia.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of Version 2 of the GNU General Public License as
+published by the Free Software Foundation.
+
+This program is distributed WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See Version 2 of the GNU General Public License for more details.
+
+You should have received a copy of Version 2 of the GNU General Public
+License along with this program. If not, you can FTP the license from
+prep.ai.mit.edu/pub/gnu/COPYING-2 or write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Section 2a of the license requires that all changes to this file be
+recorded prominently in this file. Please record all changes here.
+
+Programmers:
+ RNW Ross N. Williams ross@spam.adelaide.edu.au
+ ABC Anthony B. Coates coates@physics.uq.edu.au
+
+Changes:
+ 07-May-1992 RNW Program prepared for release under GNU GPL V2.
+ 16-Sep-1993 ABC Changed all constants *_MAX to *_FWMAX to avoid
+ conflict with some standard UNIX constants.
+ 18-Sep-1994 ABC Changed startup message to reflect my changes.
+ 01-Jan-1995 ABC Modified initial message to reflect first
+ release of FunnelWeb 3.0AC.
+ 20-Jul-1995 ABC Modified initial message to reflect
+ release of FunnelWeb 3.01AC.
+ 08-Aug-1995 ABC Changed include file name from "memory.h"
+ to "fwmem.h" to avoid a clash with the C++
+ standard header file "memory.h".
+ 08-Aug-1995 ABC Modified initial message to reflect
+ release of FunnelWeb 3.02AC.
+ 10-Aug-1995 ABC Modified initial message to reflect
+ release of FunnelWeb 3.03AC.
+ 16-Aug-1995 ABC Added support for `+Qb' batch mode option and
+ matching boolean `batchQ'.
+ 16-Aug-1995 ABC Modified initial message to reflect
+ release of FunnelWeb 3.04AC.
+ 19-Sep-1995 ABC Modified initial message to reflect
+ release of FunnelWeb 3.05AC.
+
+##############################################################################*/
+
+
+/******************************************************************************/
+/* COMMAND.C */
+/******************************************************************************/
+
+#include <ctype.h>
+#include "style.h"
+
+#include "analyse.h"
+#include "as.h"
+#include "command.h"
+#include "data.h"
+#include "dump.h"
+#include "lister.h"
+#include "fwmem.h"
+#include "mapper.h"
+#include "misc.h"
+#include "option.h"
+#include "parser.h"
+#include "scanner.h"
+#include "tangle.h"
+#include "weave.h"
+
+/******************************************************************************/
+
+/* Because FunnelWeb has so many options ,it is convenient to allow the user */
+/* to construct a startup file. This is its name. */
+#define INITFILE "fwinit.fws"
+
+/******************************************************************************/
+
+/* Three variables hold options information in FunnelWeb. */
+/* 'p_comopt' holds the options conveyed by the original raw command line. */
+/* 'p_defopt' holds the options that are default in the FunnelWeb shell. */
+/* 'option' is global and holds the options that have been specified for the */
+/* current invocation of FunnelWeb proper. */
+/* 'option' is global so here we see only 'p_comopt' and 'p_defopt'. */
+LOCVAR op_t *p_comopt; /* Initial command line options. */
+LOCVAR op_t *p_defopt; /* Default shell options. */
+
+/* The FunnelWeb shell interpreter has many different commands and it is */
+/* worth them sharing the same basic command line scanner. These two */
+/* variables hold the numer of arguments and a pointer to strings holding */
+/* copies of each argument. */
+/* Note: The first argument is placed in arr_arg[0]. */
+#define ARG_FWMAX 5
+LOCVAR uword arg_num;
+LOCVAR char *arg_arr[ARG_FWMAX+1]; /* Is this +1 necessary? */
+
+/* The FunnelWeb command interpreter allows 10 substitution strings. */
+/* These strings are stored in the following array. */
+#define NUM_SUBS (10+26) /* 0..9 and A..Z */
+LOCVAR char *subval[NUM_SUBS];
+
+LOCVAR ulong old_war;
+LOCVAR ulong old_err;
+LOCVAR ulong old_sev;
+
+/* The FunnelWeb script interpreter can echo (trace) each command before it */
+/* executes it. Whether it does is determined by this variable. */
+LOCVAR bool tracing;
+
+/* If this variable is set to true then the interpreter will not abort if the */
+/* very next command (and that one only) generates an error or severe error. */
+LOCVAR bool noabort;
+
+/* If the following boolean is true, the interpreter skips lines until it */
+/* hits the HERE command. */
+LOCVAR bool skipping;
+
+/* The following variables count how many times DIFF is invoked and how many */
+/* times it succeeded. This allows us to output a summary at the end of the */
+/* processing of the test suite. */
+LOCVAR ulong difftotl = 0;
+LOCVAR ulong diffsucc = 0;
+
+/******************************************************************************/
+
+/* Here are the prototypes for recursive or out-of-order functions. */
+LOCAL void interstr P_((char *));
+LOCAL void do_set P_((char *));
+
+/******************************************************************************/
+
+LOCAL char *sing P_((ulong,char *,char *));
+LOCAL char *sing(num,sinstr,plustr)
+/* Return one or other string depending on whether first argument is singular.*/
+ulong num;
+char *sinstr;
+char *plustr;
+{
+ if (num==1)
+ return sinstr;
+ else
+ return plustr;
+}
+
+/******************************************************************************/
+
+LOCAL uword numpos P_((ulong,ulong,ulong));
+LOCAL uword numpos(a,b,c)
+/* Returns the number of its arguments that are positive. */
+ulong a,b,c;
+{
+ uword result=0;
+ if (a>0) result++;
+ if (b>0) result++;
+ if (c>0) result++;
+ return result;
+}
+
+/******************************************************************************/
+
+LOCAL void errsum P_((ulong,ulong,ulong,ulong));
+LOCAL void errsum(fat,sev,err,war)
+/* Supply as arguments, the number of various kinds of diagnostics. */
+/* Places in linet1 a string describing the diagnostics. */
+ulong fat,sev,err,war;
+{
+ char linet2[100];
+
+ if (fat+sev+err+war==0)
+ {strcpy(linet1,"SUCCESS: No diagnostics."); return;}
+
+ strcpy(linet1,"There ");
+
+ /* "Was" or "were" depending on the plurality of highest level diagnostic. */
+ if (fat>0) strcat(linet1,sing(fat,"was","were"));
+ else if (sev>0) strcat(linet1,sing(sev,"was","were"));
+ else if (err>0) strcat(linet1,sing(err,"was","were"));
+ else if (war>0) strcat(linet1,sing(war,"was","were"));
+ else as_bomb("errsum: Error hierarchy failed!");
+
+ strcat(linet1," ");
+
+ /* Fatal errors. */
+ if (fat>0)
+ {
+ sprintf(linet2,"%1lu Fatal error",fat); strcat(linet1,linet2);
+ strcat(linet1,sing(fat,"","s"));
+ }
+
+ /* Joiner stuff. */
+ if (fat>0 && numpos(sev,err,war)>=2) strcat(linet1,", ");
+ if (fat>0 && sev>0 && err==0 && war==0) strcat(linet1," and ");
+
+ /* Severe errors. */
+ if (sev>0)
+ {
+ sprintf(linet2,"%1lu Severe error",sev); strcat(linet1,linet2);
+ strcat(linet1,sing(sev,"","s"));
+ }
+
+ /* Joiner stuff. */
+ if (fat+sev>0 && err>0 && war >0) strcat(linet1,", ");
+ if (fat+sev>0 && err>0 && war==0) strcat(linet1," and ");
+
+ /* Errors. */
+ if (err>0)
+ {
+ sprintf(linet2,"%1lu Error",err); strcat(linet1,linet2);
+ strcat(linet1,sing(err,"","s"));
+ }
+
+ /* Joiner stuff. */
+ if (fat+sev+err>0 && war>0) strcat(linet1," and ");
+
+ /* Warnings. */
+ if (war > 0)
+ {
+ sprintf(linet2,"%1lu Warning",war); strcat(linet1,linet2);
+ strcat(linet1,sing(war,"","s"));
+ }
+
+ /* The final full stop! */
+ strcat(linet1,".");
+}
+
+/******************************************************************************/
+
+LOCAL void allocarg P_((void));
+LOCAL void allocarg()
+/* Some compilers don't allow much room for statics and so it is necessary to */
+/* declare some variables as pointers and allocate them explicitly. This */
+/* function allocates the command line argument array to point to strings. */
+{
+ uword i;
+ for (i=0;i<=ARG_FWMAX;i++)
+ arg_arr[i]=(char *) mm_perm(sizeof(cl_t));
+
+ /* Also initialize the substitution strings. */
+ for (i=0;i<NUM_SUBS;i++)
+ {
+ subval[i]=(char *) mm_perm(sizeof(cl_t));
+ subval[i][0]=EOS;
+ }
+ p_comopt=(p_op_t) mm_perm(sizeof(op_t));
+ p_defopt=(p_op_t) mm_perm(sizeof(op_t));
+}
+
+/******************************************************************************/
+
+LOCAL void zerdia P_((void));
+LOCAL void zerdia()
+{
+ old_war=num_war;
+ old_err=num_err;
+ old_sev=num_sev;
+ num_sev=num_err=num_war=0;
+}
+
+LOCAL void sumdia P_((void));
+LOCAL void sumdia()
+{
+ sum_sev+=num_sev;
+ sum_err+=num_err;
+ sum_war+=num_war;
+}
+
+LOCAL void restdia P_((void));
+LOCAL void restdia()
+{
+ num_sev+=old_sev;
+ num_err+=old_err;
+ num_war+=old_war;
+}
+
+/******************************************************************************/
+
+LOCAL void explode P_((char *));
+LOCAL void explode(p)
+/* Parses the string p into a set of non-blank sequences. Copies each */
+/* distinct run of non-blanks into successive values of arg_arr. Places the */
+/* number of runs of non-blanks into arg_num. DOESN'T generate an error if */
+/* there are too many arguments. */
+char *p;
+{
+ arg_num=0;
+ while (TRUE)
+ {
+ char *x;
+
+ /* Skip to the next argument. */
+ while (*p==' ') p++;
+
+ /* Exit if we have hit the end of the string. */
+ if (*p==EOS) break;
+
+ /* Exit if we are already full up with arguments. */
+ if (arg_num==ARG_FWMAX) break;
+
+ /* Copy the next argument. */
+ x=arg_arr[arg_num];
+ while (*p!=' ' && *p!=EOS) *x++ = *p++;
+ *x=EOS;
+ arg_num++;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void dollsub P_((char *));
+LOCAL void dollsub(p_comlin)
+/* Assumes that it's string argument is in a command line object. */
+/* Performs dollar substitutions. */
+char *p_comlin;
+{
+ char *p=p_comlin;
+ cl_t cl;
+ char *t;
+
+ t = &cl[0];
+
+ /* Copy the unexpanded command line to the temporary from which we expand it */
+ /* back from whence it came. */
+ strcpy(t,p);
+
+ /* Work through the unexpanded command line expanding. */
+ while (*t != EOS)
+ {
+ /* Complain if there is not room for one more character. */
+ if ((p-p_comlin) == COMLINE_FWMAX) goto toobig;
+
+ /* If no dollar sign, no tricks. Just copy the character over. */
+ if (*t!='$') {*p++ = *t++; continue;}
+
+ /* We have seen a dollar sign. Move onto the next character. */
+ t++;
+
+ /* The only legal escapes are decimal digits, slash, and the dollar sign. */
+ if (!isdigit(*t) && !isupper(*t) && *t!='$' && *t!='/')
+ {
+ wl_sj("S: Illegal dollar subtitution sequence in command line.");
+ wl_sj("Legal sequences are $0..$9, $A..$Z, $/, and $$.");
+ num_sev++;
+ strcpy(p,t);
+ return;
+ }
+
+ /* A dollar escape is easy to process. */
+ if (*t=='$') {*p++ = *t++; continue;}
+
+ /* A slash escape is easy to process. */
+ if (*t=='/') {*p++ = FN_DELIM; t++; continue;}
+
+ /* Substitutions have to be expanded. */
+ if (isdigit(*t) || isupper(*t))
+ {
+ ubyte num;
+ char *q;
+ if (isdigit(*t))
+ num = *t-'0';
+ else
+ num = 10+(*t-'A');
+ as_cold(num<NUM_SUBS,"dollsub: num is too bug!");
+ q=subval[num];
+ if ((p-p_comlin)+strlen(subval[num]) > COMLINE_FWMAX) goto toobig;
+ while (*q!=EOS) *p++ = *q++;
+ t++;
+ }
+ }
+ *p=EOS;
+ return;
+
+ toobig:
+ wl_sj("S: Expanded (i.e. after $1 etc) command line is too long.");
+ num_sev++;
+ strcpy(p,t);
+ return;
+}
+
+/******************************************************************************/
+
+LOCAL void fwonerun P_((void));
+LOCAL void fwonerun()
+/* Performs a single run of FunnelWeb proper, using the global variable */
+/* 'options' as the input command line. */
+{
+ fn_t lisnam;
+
+ /* The following clocks record the time taken by various parts of FunnelWeb. */
+ ck_t mappck;
+ ck_t scanck;
+ ck_t parsck;
+ ck_t analck;
+ ck_t dumpck;
+ ck_t lstrck;
+ ck_t tangck;
+ ck_t weavck;
+ ck_t totlck;
+
+ /* Intialize/zero all the clocks. */
+ ck_ini(&mappck);
+ ck_ini(&scanck);
+ ck_ini(&parsck);
+ ck_ini(&analck);
+ ck_ini(&dumpck);
+ ck_ini(&lstrck);
+ ck_ini(&tangck);
+ ck_ini(&weavck);
+ ck_ini(&totlck);
+
+ /* Start the total time clock ticking. A total time clock is used to gather */
+ /* up the gaps between the invocations of the other clocks. */
+ ck_start(&totlck);
+
+ ck_start(&lstrck);
+
+ /* Establish the listing file output stream. */
+ strcpy(lisnam,""); /* Start with an empty string. */
+ fn_ins(lisnam,option.op_f_s); /* Insert input file name. */
+ fn_ins(lisnam,".lis"); /* Insert constant extension. */
+ fn_ins(lisnam,option.op_l_s); /* Insert command line spec. */
+ wf_ini(&f_l,option.op_l_b); /* Initialize the stream. */
+ wf_ope(&f_l,lisnam); /* Create the file. */
+ if (option.op_l_b && wf_err(&f_l))
+ {
+ sprintf(linet1,"S: Error creating listing file \"%s\".",lisnam);
+ wl_sj(linet1);
+ wl_sj("Aborting...");
+ num_sev++;
+ return;
+ }
+
+ wl_l("FUNNELWEB LISTING FILE");
+ wl_l("======================");
+ wl_l("");
+
+ /* Initialize the lister for this run. */
+ lr_ini();
+
+ ck_stop(&lstrck);
+
+ /* Scanner comes first. */
+ ck_start(&scanck);
+ scanner(&mappck,&scanck);
+ ck_stop(&scanck);
+
+ /* Dump the line and token lists if requested. The scanner supplies sensible */
+ /* lists even if it encounters errors, so there is no danger here. */
+ ck_start(&dumpck);
+ if (option.op_b2_b) dm_lnls(&f_l);
+ if (option.op_b3_b) dm_tkls(&f_l);
+ ck_stop(&dumpck);
+
+ /* Invoke the parser if there were no serious scanner errors. */
+ if (num_sev+num_err==0)
+ {
+ ck_start(&parsck);
+ parser();
+ ck_stop(&parsck);
+ /* Only perform post parser dumps if the parser was run. */
+ ck_start(&dumpck);
+ if (option.op_b4_b) dm_matb(&f_l);
+ if (option.op_b5_b) dm_dcls(&f_l);
+ ck_stop(&dumpck);
+ }
+ else
+ {
+ if (option.op_b4_b)
+ wl_l("Macro table dump skipped (Parser was not invoked).");
+ if (option.op_b5_b)
+ wl_l("Document list dump skipped (Parser was not invoked).");
+ }
+
+ /* Invoke the macro structure analyser if still no errors. */
+ if (num_sev+num_err==0)
+ {
+ ck_start(&analck);
+ analyse();
+ ck_stop(&analck);
+ }
+
+ /* The scanner, parser, and analyser send errors to the lister package. */
+ /* Send sorted listing to the listing file (and screen if desired). */
+ ck_start(&lstrck);
+ if (option.op_l_b) lr_gen(&f_l,option.op_c_i);
+ if (option.op_s_b) lr_gen(&f_s,option.op_s_i);
+ if (option.op_s_b) lr_gen(&f_j,option.op_s_i);
+ ck_stop(&lstrck);
+
+ /* If the first stages went OK, invoke tangle and weave. */
+ if (num_sev+num_err==0)
+ {
+ if (option.op_o_b)
+ {
+ ck_start(&tangck);
+ tangle();
+ ck_stop(&tangck);
+ }
+ if (option.op_t_b)
+ {
+ ck_start(&weavck);
+ weave();
+ ck_stop(&weavck);
+ }
+ /* Leave output lines from Tangle and Weave joined, but separate them */
+ /* from any further output. */
+ if (option.op_t_b || option.op_o_b)
+ wl_sjl("");
+ }
+ else
+ {
+ /* Otherwise tell the user that back-end phases will be skipped. */
+ if ( option.op_o_b || option.op_t_b)
+ {
+ if (num_sev+num_err==1)
+ wr_sjl("Error caused ");
+ else
+ wr_sjl("Errors caused ");
+ }
+ if ( option.op_o_b && option.op_t_b) wr_sjl("tangle and weave phases");
+ if ( option.op_o_b && !option.op_t_b) wr_sjl("tangle phase");
+ if (!option.op_o_b && option.op_t_b) wr_sjl("weave phase");
+ if ( option.op_o_b || option.op_t_b)
+ {wl_sjl(" to be skipped."); wl_sjl("");}
+ }
+
+ ck_stop(&totlck);
+
+ /* If requested write out a summary of the time taken. */
+ if (option.op_b6_b)
+ dm_times(&f_l,
+ &mappck,&scanck,&parsck,&analck,
+ &dumpck,&lstrck,&tangck,&weavck,&totlck);
+
+ /* Write out a line summarizing the diagnostics for this run. */
+ errsum(0L,num_sev,num_err,num_war); wl_sjl(linet1);
+
+ /* Close the listing file. */
+ if (!option.op_l_b) goto finishoff;
+ if (wf_err(&f_l))
+ {
+ wl_sj("S: Error writing to listing file. Aborting...");
+ num_sev++;
+ goto finishoff;
+ }
+ wf_clo(&f_l);
+ if (wf_err(&f_l))
+ {
+ wl_sj("S: Error flushing and closing listing file. Aborting...");
+ num_sev++;
+ }
+
+ finishoff:
+ /* VERY IMPORTANT: Ask the memory management package to free up all the */
+ /* temporary memory (allocated using mm_temp) that has been allocated. This */
+ /* ensures that the memory allocated for this FunnelWeb run will be recycled.*/
+ mm_zapt();
+
+} /* End of fwonerun */
+
+/******************************************************************************/
+
+LOCAL void do_absen P_((void));
+LOCAL void do_absen ()
+{
+ if (arg_num != 2)
+ {
+ wl_sj("S: The ABSENT command requires exactly one argument.");
+ num_sev++;
+ return;
+ }
+ if (fexists(arg_arr[1]))
+ {
+ sprintf(linet1,"S: ABSENT found \"%s\".",arg_arr[1]);
+ wl_sj(linet1);
+ num_sev++;
+ return;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void do_cody P_((void));
+LOCAL void do_cody ()
+/* The CODIFY command takes an input text file and generates an output text */
+/* file containing C code to write out the input text file. The need for this */
+/* command springs from the weaver. Experience with FunnelWeb showed that use */
+/* of a separate header file, to be included, while apparently sensible, */
+/* caused no end of problems. In particular, problems with portably */
+/* specifying where the header file should be found. In the end, it was */
+/* decided that it would be better to write the header file out with the */
+/* weave output. As the header file is quite long, it is best to automate the */
+/* process of converting the file from text to C code to write out the text. */
+/* That is what the CODIFY command does. */
+{
+ FILE *file1;
+ FILE *file2;
+
+#define MAXHACK 1000
+ char hackline[MAXHACK+1];
+ uword lineno;
+
+ if (arg_num != 3)
+ {
+ wl_sj("S: The CODIFY command requires exactly two arguments.");
+ num_sev++;
+ return;
+ }
+
+ /* Open the input file for text reading. */
+ file1=fopen(arg_arr[1],"r");
+ if (file1 == FOPEN_F)
+ {
+ wl_sj("S: Error opening the input file.");
+ num_sev++;
+ return;
+ }
+ file2=fopen(arg_arr[2],"w");
+ if (file2 == FOPEN_F)
+ {
+ fclose(file1);
+ wl_sj("S: Error creating the output file.");
+ num_sev++;
+ return;
+ }
+
+ lineno=0;
+
+ /* PROCESS A SINGLE LINE PER ITERATION. */
+ while (TRUE)
+ {
+ uword linelength;
+ uword i;
+
+ /* Read in a line of input and terminate loop if there are no more lines. */
+ fgets(hackline,MAXHACK,file1);
+ if (ferror(file1))
+ {wl_sj("S: Error reading the input file.");num_sev++;return;}
+ if (feof(file1)) break;
+ lineno++;
+
+ /* Complain if the input line is too long. */
+ if (strlen(hackline)>81)
+ {
+ sprintf(linet1,"Line %lu of input file is too long.",
+ (ulong) strlen(hackline));
+ wl_sj(linet1);
+ wl_sj("The maximum allowable length is 80 characters.");
+ num_sev++;
+ return;
+ }
+
+ /* Write the start-of-line string. */
+ if (fputs(" WX(\"",file2) == FPUTS_F) goto write_failure;
+
+ /* Write out the line in sanitized form. */
+ linelength=strlen(hackline);
+ for (i=0;i<linelength;i++)
+ {
+ char ch = hackline[i];
+ if (ch==EOL)
+ ; /* Ignore this. */
+ else
+ if (ch=='\"')
+ {if (fputs("\\\"",file2) == FPUTS_F) goto write_failure;}
+ else
+ if (ch=='\\')
+ {if (fputs("\\\\",file2) == FPUTS_F) goto write_failure;}
+ else
+ {if (fputc(ch,file2) == FPUTC_F) goto write_failure;}
+ }
+
+ /* Write the end of line string. */
+ if (fputs("\");\n",file2) == FPUTS_F) goto write_failure;
+ }
+
+ if (fflush(file2) != FFLUSH_S)
+ {wl_sj("S: Error flushing the output file.");num_sev++;return;}
+ if (fclose(file1) == FCLOSE_F)
+ {wl_sj("S: Error closing the input file.");num_sev++;return;}
+ if (fclose(file2) == FCLOSE_F)
+ {wl_sj("S: Error closing the output file.");num_sev++;return;}
+ return;
+
+ write_failure:
+ wl_sj("S: Error writing the output file.");num_sev++;return;
+}
+
+/******************************************************************************/
+
+LOCAL void do_comp P_((void));
+LOCAL void do_comp ()
+/* The compare command should have two arguments being file names. It */
+/* compares the two files and raises a fatal error if they differ. */
+{
+ char *errstr;
+ bool result;
+ if (arg_num != 3)
+ {
+ wl_sj("S: COMPARE command must be given exactly two arguments.");
+ num_sev++;
+ return;
+ }
+ errstr=eq_files(arg_arr[1],arg_arr[2],&result);
+ if (errstr!=NULL)
+ {
+ wl_sj("S: COMPARE command ran into a problem:");
+ wl_sj(errstr);
+ num_sev++;
+ return;
+ }
+ if (!result)
+ {
+ wl_sj("S: A comparison FAILED. Two files compared were not identical!");
+ sprintf(linet1," File1: \"%s\".",arg_arr[1]); wl_sj(linet1);
+ sprintf(linet1," File2: \"%s\".",arg_arr[2]); wl_sj(linet1);
+ wl_sj(" FunnelWeb has just FAILED a regression test.");
+ wl_sj(" You should now inspect the two files to see how the result of");
+ wl_sj(" this run of FunnelWeb differed from the \"correct answer\" in");
+ wl_sj(" the answer directory.");
+ num_sev++;
+ return;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void do_defin P_((char *));
+LOCAL void do_defin (p_comlin)
+/* The define command associates a string with a $n. */
+char *p_comlin;
+{
+ char *p;
+ ubyte defnum;
+
+ /* p is our scanning pointer and starts at the start of the command line. */
+ p=p_comlin;
+
+ /* Skip past the DEFINE command onto the second argument. */
+ while ((*p!=' ') && (*p!=EOS)) p++;
+ while (*p==' ') p++;
+
+ /* There should be a single digit or upper case letter there. */
+ if (!isdigit(*p) && !isupper(*p))
+ {wl_sj("S: The first argument to DEFINE must be a decimal digit or");
+ wl_sj(" upper case letter.");
+ wl_sj(" Example: define 3 \"A Walrus in Spain is a Walrus in Vain.\"");
+ num_sev++;return;}
+ if (isdigit(*p))
+ defnum = *p-'0';
+ else
+ defnum = 10+(*p-'A');
+ as_cold(defnum<NUM_SUBS,"do_defin: num is too bug!");
+
+ /* Move past the digit. */
+ p++;
+
+ /* Skip blanks to get to the next argument. */
+ while (*p==' ') p++;
+
+ /* Complain if there is no second argument. */
+ if (*p==EOS)
+ {wl_sj("S: The DEFINE command expected a second argument.");
+ num_sev++; return;}
+
+ /* Otherwise make sure that we have a double quoted string. */
+ if (*p!='"' || *(p+1)==EOS || p[strlen(p)-1]!='"')
+ {wl_sj("S: Second argument to DEFINE must be in double quotes.");
+ num_sev++;return;}
+
+ /* All is checked. Now it is safe to copy over the string. */
+ p++;
+ strcpy(subval[defnum],p);
+ subval[defnum][strlen(subval[defnum])-1]=EOS;
+
+ /* TRACE: All the definitions.
+ {
+ ubyte i;
+ for (i=0;i<NUM_SUB;i++)
+ printf("$%u=\"%s\"\n",(unsigned) i,subval[i]);
+ }
+ */
+}
+
+/******************************************************************************/
+
+LOCAL void do_diff P_((void));
+/* COMMAND FORMAT: diff file1 file2 logfile [abort] */
+/* This function/command performs a proper text differences on two files. */
+/* This function is long and messy because of C's lack of nested functions. */
+/* I don't want to create global variables here, and defining subfunctions */
+/* that do not have access to the variables of this function would provoke */
+/* too wide an interface to be worth the trouble. So it's code city. */
+/* How I wish that I had FunnelWeb to help me write this function! */
+LOCAL void do_diff()
+{
+ bool diffabort; /* TRUE iff we should abort if files are different. */
+ bool is_same = FALSE; /* True iff files are proven to be the same. */
+ FILE *logfile; /* File to write result of comparison. */
+ bool badwrite = FALSE; /* TRUE if we couldn't write to the logfile. */
+ char *p_file1; /* Pointer to mapping of first file to compare. */
+ char *p_file2; /* Pointer to mapping of second file to compare. */
+ ulong len_file1; /* Number of bytes in mapping of first file. */
+ ulong len_file2; /* Number of bytes in mapping of second file. */
+ char *mess1; /* Error message from mapper for first file. */
+ char *mess2; /* Error message from mapper for second file. */
+ bool is_image; /* TRUE iff mapped images are identical. */
+ bool anydiff = FALSE; /* TRUE iff any differences detected during loop. */
+
+ /* Check that the number of arguments is correct. */
+ if (arg_num < 4 || arg_num > 5)
+ {
+ wl_sj("S: The DIFF command must be given either 3 or 4 arguments.");
+ wl_sj(" Usage: diff f1 f2 logfile [abort]");
+ num_sev++;
+ return;
+ }
+
+ /* Check that the fourth argument, if present, is legal. */
+ diffabort=FALSE;
+ if (arg_num == 5)
+ if (strcmp(arg_arr[4],"ABORT")==0 || strcmp(arg_arr[4],"abort")==0)
+ diffabort=TRUE;
+ else
+ {
+ wl_sj(
+ "S: The DIFF command's fourth argument, if present, must be ABORT.");
+ wl_sj(" Usage: diff f1 f2 logfile [abort]");
+ num_sev++;
+ return;
+ }
+
+ /* Now open the log file to append result of compare. */
+ logfile=fopen(arg_arr[3],"a");
+ if (logfile == FOPEN_F)
+ {
+ wl_sj("S: DIFF: Error opening the log file (to append result of compare).");
+ num_sev++;
+ return;
+ }
+
+ /* The following define simplifies writing to the log file. */
+#define LOGLINE {if (fputs(linet1,logfile) == FPUTS_F) badwrite=TRUE;}
+#define LOGSTR(STR) {if (fputs((STR),logfile) == FPUTS_F) badwrite=TRUE;}
+#define LOGCHAR(CH) {if (fputc((CH),logfile) == FPUTC_F) badwrite=TRUE;}
+
+ /* Write the header for this comparison to the log file. */
+ sprintf(linet1,"\n\n" ); LOGLINE;
+ sprintf(linet1,"Comparing \"%s\"\n" ,arg_arr[1]); LOGLINE;
+ sprintf(linet1," with \"%s\".\n",arg_arr[2]); LOGLINE;
+
+ /* Now map in the two files to be compared. */
+ /* Once this is done, we MUST do a mm_zapt later or memory will leak. */
+ /* We attempt to map the second file, even if the first mapping has failed */
+ /* as, if the first file is absent, there is a good chance that the second */
+ /* is absent too, and it is useful to the user to know this. */
+ mess1=map_file(arg_arr[1],&p_file1,&len_file1);
+ mess2=map_file(arg_arr[2],&p_file2,&len_file2);
+ if (mess1 != NULL)
+ {
+ sprintf(linet1,"Error mapping \"%s\".\n",arg_arr[1]); LOGLINE;
+ wr_sj("E: DIFF: "); wr_sj(linet1);
+ sprintf(linet1," %s\n",mess1); LOGLINE; wr_sj(linet1);
+ num_err++;
+ }
+ if (mess2 != NULL)
+ {
+ sprintf(linet1,"Error mapping \"%s\".\n",arg_arr[2]); LOGLINE;
+ wr_sj("E: DIFF: "); wr_sj(linet1);
+ sprintf(linet1," %s\n",mess2); LOGLINE; wr_sj(linet1);
+ num_err++;
+ }
+ if ((mess1 != NULL) || (mess2 != NULL))
+ goto frombadmap;
+
+ /* At this point the two files to be compared are sitting in memory and we */
+ /* have a ready-for-writing log file. We are now ready to compare. */
+
+ /* First perform a binary image comparison as a check for later. */
+ /* We could do this later, but it is better to do this now, in case the */
+ /* complicated comparison code somehow corrupts one of the images. */
+ is_image= ((len_file1 == len_file2) &&
+ (memcmp(p_file1,p_file2,(size_t) len_file1) == 0));
+
+ /* This anonymous block performs the actual comparison. */
+ {
+ /* The comparison is performed by scrolling the two input files through two */
+ /* fixed-length line buffers (buf1 and buf2 - see below). To avoid copying, */
+ /* the buffers are made circular. Processing takes place by comparing the */
+ /* first line of each buffer. If the line is the same, the buffers are */
+ /* scrolled by one line. If they are different, then we have encountered a */
+ /* DIFFERENCES SECTION and we have to compare lines near the top of the */
+ /* buffers to find a match. When a match is found, each buffer is scrolled */
+ /* down to its match point and processing continues. */
+
+/* LBUFSIZ is the number of number of lines that each buffer can hold. */
+/* Lines are indexed from [0,LBUFSIZ-1]. */
+/* WARNING: LBUFSIZ must be a power of two corresponding to WRAP(X). */
+/* WARNING: Totally different input files will provoke O(LBUFSIZ^2) */
+/* checksum comparisons per LBUFSIZ input lines. */
+/* WRAP(X) is a macro that performs wraparound of buffer indices. */
+/* GAP is the number of lines that have to match to end a diff section. */
+/* MAXDIST is the maximum "distance" that is tested when matching. */
+#define LBUFSIZ 64
+#define WRAP(X) ((X) & 0x3F)
+#define GAP 3
+#define MAXDIST (LBUFSIZ-GAP)
+
+/* The following macro compares two lines in the buffers. */
+/* The arguments are absolute buffer indices, not relative ones. */
+/* We assume that checksums will mismatch more often than line lengths. */
+#define COMPLINE(INDA,INDB) \
+ ((buf1[INDA].c_line == buf2[INDB].c_line) && \
+ (buf1[INDA].l_line == buf2[INDB].l_line) && \
+ (memcmp(buf1[INDA].p_line,buf2[INDB].p_line, \
+ (size_t) buf1[INDA].l_line)==0))
+
+ /* The two line buffers buf1 and buf2 (see below) don't actually store */
+ /* lines. Instead they store line structures which store pointers to the */
+ /* lines in the mapped images of the files. They also store the length of */
+ /* each line, and a checksum of the line. The checksum is useful for */
+ /* speeding up the comparisons between lines when processing a differences */
+ /* section. */
+ typedef
+ struct
+ {
+ char *p_line; /* Pointer to first byte in the line. */
+ ulong l_line; /* Number of bytes in the line. */
+ uword c_line; /* Checksum of the line. */
+ } line_t;
+
+ char *p_next1 = p_file1; /* Points to next line in file1. */
+ char *p_next2 = p_file2; /* Points to next line in file2. */
+ char *p_post1 = p_file1+len_file1; /* Byte following image of file1. */
+ char *p_post2 = p_file2+len_file2; /* Byte following image of file2. */
+ line_t buf1[LBUFSIZ]; /* Comparison buffer for first file. */
+ line_t buf2[LBUFSIZ]; /* Comparison buffer for second file. */
+ ulong buf1top = 0; /* Index of first line in first buffer. */
+ ulong buf2top = 0; /* Index of first line in second buffer. */
+ ulong buf1fil = 0; /* Number of lines in first buffer. */
+ ulong buf2fil = 0; /* Number of lines in second buffer. */
+ ulong topnum1 = 1; /* Line number of first line of first buffer. */
+ ulong topnum2 = 1; /* Line number of first line of second buffer. */
+
+ /* The following loop compares the line(s) at the top of the two buffers */
+ /* and processes (lines1,lines2) lines of each. */
+ while (TRUE)
+ {
+ ulong lines1; /* Lines of file1 processed during this loop iteration. */
+ ulong lines2; /* Lines of file2 processed during this loop iteration. */
+ ulong d,g; /* Used in comparison loops. */
+
+ /* The first thing we do is to fill each buffer as full as possible. At */
+ /* the end of the next two lumps of code, the only reason that a file's */
+ /* is not full is that we have reached the end of the file. */
+
+ /* Fill the first buffer as full as possible. */
+ while ((buf1fil < LBUFSIZ) && (p_next1 != p_post1))
+ {
+ ulong ind = WRAP(buf1top + buf1fil);
+ ulong len = 0;
+ uword csum = 0;
+ char *p_lin = p_next1;
+ while (TRUE)
+ {
+ if (p_next1 == p_post1) break;
+ len++;
+ csum=(csum+*p_next1++) & 0xFFFF;
+ if (*(p_next1-1) == EOL) break;
+ }
+ buf1[ind].p_line = p_lin;
+ buf1[ind].l_line = len;
+ buf1[ind].c_line = csum;
+ buf1fil++;
+ }
+
+ /* Fill the second buffer as full as possible. */
+ while ((buf2fil < LBUFSIZ) && (p_next2 != p_post2))
+ {
+ ulong ind = WRAP(buf2top + buf2fil);
+ ulong len = 0;
+ uword csum = 0;
+ char *p_lin = p_next2;
+ while (TRUE)
+ {
+ if (p_next2 == p_post2) break;
+ len++;
+ csum=(csum+*p_next2++) & 0xFFFF;
+ if (*(p_next2-1) == EOL) break;
+ }
+ buf2[ind].p_line = p_lin;
+ buf2[ind].l_line = len;
+ buf2[ind].c_line = csum;
+ buf2fil++;
+ }
+
+ /* If the buffers are empty then we must be at the end of each file. */
+ if (buf1fil==0 && buf2fil==0)
+ break;
+
+ /* Try to peel a pair of matching lines off the top of the buffer. */
+ /* If we succeed, zip down to the end of the loop and flush them. */
+ /* We can't integrate this code into the next part because the next part */
+ /* requires GAP matches, whereas here we require just one. */
+ if ((buf1fil>0) && (buf2fil>0) && COMPLINE(buf1top,buf2top))
+ {lines1=lines2=1; goto flushlines;}
+
+ /* At this point, we know we have a differences section. */
+ anydiff=TRUE;
+
+ /* We now compare the top lines of the two buffers for a match. A match */
+ /* is only considered to have been found if we match GAP consecutive */
+ /* lines. The best match minimizes the DISTANCE which is the sum of the */
+ /* offsets (lines1,lines2) (in lines) from the top of each buffer where */
+ /* the match starts. Even better matches minimize abs(lines1-lines2) as */
+ /* well. All these nested loops are to ensure that we search best first. */
+ for (d=1;d<=MAXDIST;d++)
+ {
+ /* Calculate half distance on the high side. */
+ long half = (d/2)+1;
+ long off;
+ long sign_v;
+ /* Explore up and down simultaneously from the halfway mark. */
+ for (off=0;off<=half;off++)
+ for (sign_v= -1;sign_v<2;sign_v+=2)
+ {
+ long x = half + sign_v*off;
+ /* The following test allows the above loops to be sloppy. */
+ if (0<=x && x<=d)
+ {
+ lines1=x;
+ lines2=d-lines1;
+ /* We now know that we want to test at (lines1,lines2). */
+ /* So compare the GAP lines starting at those positions. */
+ /* Note: lines1 and lines2, as well as being the number */
+ /* of lines processed, are also the offset to the first */
+ /* match line in our match gap. */
+ for (g=0;g<GAP;g++)
+ { /* Note: R for relative, A for absolute. */
+ ulong t1r = lines1 + g;
+ ulong t2r = lines2 + g;
+ ulong t1a,t2a;
+
+ /* If both files have run out at this point, it's a match!*/
+ if ((t1r>=buf1fil) && (t2r>=buf2fil)) continue;
+
+ /* If just one of the files has run out it's a mismatch. */
+ if ((t1r>=buf1fil) || (t2r>=buf2fil)) goto gapfail;
+
+ /* We now know that we have two real lines. Compare them. */
+ /* Variables are to avoid big nested macro expansions. */
+ t1a = WRAP(buf1top+t1r);
+ t2a = WRAP(buf2top+t2r);
+ if (!COMPLINE(t1a,t2a)) goto gapfail;
+ }
+ /* If we dropped out of the gap loop, we must have found */
+ /* GAP consecutive matches. So we can run off and write out */
+ /* the difference section. */
+ goto writediff;
+
+ /* Here's where we jump if we found a mismatch during gap */
+ /* looping. All we do is try next pair of offsets. */
+ gapfail:;
+ } /* End sloppy if. */
+ } /* End for sign_v. */
+ } /* End for distance loop. */
+
+ /* If we got to here then we must have dropped out of the search loop */
+ /* which means that there must have been no match at all between the */
+ /* buffers. The only thing to do is to write out what we have as */
+ /* a differences section. */
+ lines1=buf1fil;
+ lines2=buf2fil;
+
+ /* Write out the differences section (lines1,lines2) to the log file. */
+ writediff:
+ { /* Begin writediff */
+ ulong i,j;
+ LOGSTR("\n");
+ LOGSTR(" +-----\n");
+ for (i=0;i<lines1;i++)
+ {
+ ulong nline = WRAP(buf1top+i);
+ sprintf(linet1,"%05lu|",(ulong) topnum1+i); LOGLINE;
+ for (j=0;j<buf1[nline].l_line;j++)
+ LOGCHAR(*(buf1[nline].p_line+j));
+ }
+ LOGSTR(" +-----\n");
+ for (i=0;i<lines2;i++)
+ {
+ ulong nline = WRAP(buf2top+i);
+ sprintf(linet1,"%05lu|",(ulong) topnum2+i); LOGLINE;
+ for (j=0;j<buf2[nline].l_line;j++)
+ LOGCHAR(*(buf2[nline].p_line+j));
+ }
+ LOGSTR(" +-----\n");
+ } /* End writediff. */
+
+ /* Flush from buffer however many lines we ended up processing. */
+ flushlines:
+ buf1top=WRAP(buf1top+lines1); topnum1+=lines1; buf1fil-=lines1;
+ buf2top=WRAP(buf2top+lines2); topnum2+=lines2; buf2fil-=lines2;
+
+ } /* End the while loop that runs through the two files. */
+ } /* End of anonymous block for doing actual comparison. */
+
+ /* The anydiff flag tells us if the loop found any difference sections. */
+ is_same=!anydiff;
+
+ /* Target position if we couldn't map in the input files earlier. */
+ frombadmap:
+
+ /* Release the memory allocated by the mapper for the input files. */
+ /* Failure to do this will result in a memory leak! */
+ mm_zapt();
+
+ /* If the two files are identical, tell the log file. */
+ if (is_same)
+ LOGSTR("The two files are IDENTICAL.\n");
+
+ /* Invalidate this test in the log file, if inconsistent (see later). */
+ if (is_same != is_image)
+ LOGSTR("<<CONSISTENCY FAILURE: ABOVE COMPARISON INVALID>>\n");
+
+ /* If we had problems with the log file at any stage, kick up a fuss now. */
+ if (badwrite)
+ {wl_sj("S: DIFF: Error writing to log file."); num_sev++;}
+
+ /* Close the log file. */
+ if (fclose(logfile) == FCLOSE_F)
+ {wl_sj("S: DIFF: Error closing the log file."); num_sev++;}
+
+ /* The above code is quite tricky and there is a chance that it contains */
+ /* bugs. So, as a safety check we compare the results from the binary memory */
+ /* image comparison performed earlier and the more complicated text */
+ /* comparison above. If they differ, then it's time to go kaboom. */
+ if (is_image && !is_same)
+ as_bomb("do_diff: Image comparison succeeded, but text comparison failed.");
+ if (!is_image && is_same)
+ as_bomb("do_diff: Image comparison failed, but text comparison succeeded.");
+
+ /* If files are non-same and ABORT option is turned on, set severe status. */
+ if (!is_same && diffabort)
+ {
+ wl_sj(
+ "S: DIFF: Files have not been proven identical, and ABORT option is on.");
+ num_sev++;
+ }
+
+ /* Tell the console whether comparison succeeded. */
+ if (is_same)
+ wl_sj("The two files are IDENTICAL.");
+ else
+ wl_sj("The two files are DIFFERENT (see the differences file for the details).");
+
+ /* Increment the difference summary counters. */
+ difftotl++;
+ if (is_same)
+ diffsucc++;
+}
+
+/******************************************************************************/
+
+LOCAL void do_dsum P_((void));
+LOCAL void do_dsum ()
+{
+ sprintf(linet1,"Summary of Differences"); wl_sj(linet1);
+ sprintf(linet1,"----------------------"); wl_sj(linet1);
+ sprintf(linet1,"Identical = %lu.",(ulong) diffsucc); wl_sj(linet1);
+ sprintf(linet1,"Different = %lu.",(ulong) (difftotl-diffsucc)); wl_sj(linet1);
+ sprintf(linet1,"Total = %lu.",(ulong) difftotl); wl_sj(linet1);
+}
+
+/******************************************************************************/
+
+LOCAL void do_dzer P_((void));
+LOCAL void do_dzer ()
+/* Zaps difference counters. */
+{
+ difftotl = 0;
+ diffsucc = 0;
+}
+
+/******************************************************************************/
+
+LOCAL void do_eneo P_((void));
+LOCAL void do_eneo ()
+{
+ if (arg_num != 2)
+ {
+ wl_sj("S: The ENEO command must be given exactly one argument.");
+ num_sev++;
+ return;
+ }
+ if (fexists(arg_arr[1]))
+ if (remove(arg_arr[1]) != REMOVE_S)
+ {
+ sprintf(linet1,"S: ENEO failed to delete \"%s\".",arg_arr[1]);
+ wl_sj(linet1);
+ num_sev++;
+ return;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void do_exec P_((void));
+LOCAL void do_exec ()
+{
+ uword i;
+
+ if (arg_num < 2)
+ {
+ wl_sj("S: The EXECUTE command requires at least one argument.");
+ num_sev++;
+ return;
+ }
+ if (arg_num > 10)
+ {
+ wl_sj("S: The EXECUTE command can have at most nine arguments.");
+ num_sev++;
+ return;
+ }
+
+ /* Zap all the numeric arguments. */
+ for (i=0; i<10; i++)
+ subval[i][0]=EOS;
+
+ /* Copy the arguments over to the $1 $2 etc substitution variables. */
+ for (i=1;i<arg_num; i++)
+ strcpy(subval[i-1],arg_arr[i]);
+
+ /* Run up a new FunnelWeb shell and interpret the file. */
+ interstr(arg_arr[1]);
+}
+
+/******************************************************************************/
+
+LOCAL void do_exist P_((void));
+LOCAL void do_exist ()
+{
+ if (arg_num != 2)
+ {
+ wl_sj("S: The EXISTS command requires exactly one argument.");
+ num_sev++;
+ return;
+ }
+ if (!fexists(arg_arr[1]))
+ {
+ sprintf(linet1,"S: EXISTS failed to find \"%s\".",arg_arr[1]);
+ wl_sj(linet1);
+ num_sev++;
+ return;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void do_fix P_((void));
+LOCAL void do_fix ()
+/* When the test suite is moved from one machine to another, it is possible */
+/* that at some stage it will be moved using a BINARY transfer rather than a */
+/* text file transfer. The result is that the test files will contain lines */
+/* terminated with a sequence of control characters that the local buffered */
+/* IO library will not convert to '\n' upon reading in. The are a few */
+/* solutions to this problem, but one of the most direct is to have a command */
+/* such as this one that can convert the file over. */
+/* */
+/* Once we have identified an end of line, it is easy to write it out as we */
+/* can just send a '\n' and the local buffered IO library will write the */
+/* right codes for us. The tricky part is deciding what an EOL is in the */
+/* input stream. Well, I could have made the control characters for the */
+/* remote EOL a parameter of this command, but instead I decided to use a */
+/* simple algorithm that should cover nearly all cases... */
+/* ALGORITHM: Parse the input into alternating blocks of control characters */
+/* and non-control characters. Parse each block of control characters into */
+/* subblocks by parsing it from left to right and starting a new subblock the */
+/* moment a character of the subblock currently being parsed appears again. */
+/* This covers at least the following cases, and probably many more: */
+/* UNIX LF */
+/* MSDOS CR LF */
+/* Macintosh CR */
+{
+ /* Erk! But I found out the hard way, it doesn't work on a VAX! */
+ /* I'm making this do nothing on a VAX rather than generate an error as I */
+ /* want the scripts to work silently on the VAX without modification. */
+#if !VMS
+ FILE *infile; /* File variable for input file. */
+ FILE *tmpfile; /* File variable for temporary output file. */
+ char *p_target; /* Name of final (target) output file. */
+ STAVAR char *p_temp=NULL; /* Name of temporary output file. */
+
+ bool seen[256]; /* TRUE if char is in current control sequence. */
+ char undo[256]; /* undo[0..length-1] contains current control sequence. */
+ uword length; /* Number of control bytes in our buffer. */
+ uword i;
+ int status;
+
+ /* Allocate the temporary file name if not already allocated. */
+ if (p_temp==NULL) p_temp=(p_fn_t) mm_perm(sizeof(fn_t));
+
+ /* We can take one or two arguments. One argument means that we should */
+ /* fix up the input file, leaving the result in the input file. */
+ if (arg_num != 2 && arg_num != 3)
+ {
+ wl_sj("S: The FIXEOLS command requires one or two filename arguments.");
+ num_sev++;
+ return;
+ }
+
+ /* Change to two arguments if the input name is the same as output name. */
+ if (arg_num==3 && strcmp(arg_arr[1],arg_arr[2])==0) arg_num=2;
+
+ /* Work out what the target name is going to be. */
+ p_target=(arg_num==2) ? arg_arr[1] : arg_arr[2];
+
+ /* Generate a temporary filename for the output file. This is tricky because */
+ /* on many machines, one cannot rename across devices or directories. This */
+ /* means that the temporary file has to be created in the same directory as */
+ /* the file that we are going to rename it to later (the target file). */
+ strcpy(p_temp,p_target);
+ fn_ins(p_temp,fn_temp());
+
+ /* Open the input file for BINARY reading. */
+ infile=fopen(arg_arr[1],"rb");
+ if (infile == FOPEN_F)
+ {
+ sprintf(linet1,"S: FIXEOLS: Error opening \"%s\".",arg_arr[1]);
+ wl_sj(linet1);
+ num_sev++;
+ return;
+ }
+
+ /* Create the output file for TEXT writing. */
+ tmpfile=fopen(p_temp,"w");
+ if (tmpfile == FOPEN_F)
+ {
+ fclose(infile);
+ wl_sj("S: FIXEOLS: Error creating the temporary output file.");
+ num_sev++;
+ return;
+ }
+
+ /* Initialialize the control character buffer to empty. */
+ for (i=0;i<256;i++) seen[i]=FALSE;
+ length=0;
+
+ while (TRUE)
+ {
+ /* Read in the next character from the input file. */
+ char ch=getc(infile);
+ if (ferror(infile))
+ {wl_sj("S: FIXEOLS: Error reading the input file.");num_sev++;return;}
+ if (feof(infile)) break;
+
+ /* Does character terminate a non-empty buffer? If so, flush it. */
+ if (length>0 && (isascprn(ch) || seen[ch]))
+ {
+ if (fputc(EOL,tmpfile) == FPUTC_F) goto write_failure;
+ for (i=0;i<length;i++) seen[undo[i]]=FALSE;
+ length=0;
+ }
+
+ /* Now we can approach the character cleanly and freshly. */
+ if (isascprn(ch))
+ {if (fputc(ch,tmpfile) == FPUTC_F) goto write_failure;}
+ else
+ {undo[length++]=ch; seen[ch]=TRUE;}
+ }
+ if (length>0)
+ {if (fputc(EOL,tmpfile) == FPUTC_F) goto write_failure;}
+
+ if (fflush(tmpfile) != FFLUSH_S)
+ {wl_sj("S: FIXEOLS: Error flushing the temporary output file.");num_sev++;return;}
+ if (fclose(infile) == FCLOSE_F)
+ {wl_sj("S: FIXEOLS: Error closing the input file.");num_sev++;return;}
+ if (fclose(tmpfile) == FCLOSE_F)
+ {wl_sj("S: FIXEOLS: Error closing the temporary output file.");num_sev++;return;}
+
+ /* If renaming to the input file, we have to delete the input file first. */
+ if (arg_num==2)
+ {
+ status=remove(arg_arr[1]);
+ if (status != REMOVE_S)
+ {
+ wl_sj("S: FIXEOLS: Error deleting existing input file to replace it");
+ wl_sj(" with the temporary output file. Deleting temporary and aborting...");
+ remove(p_temp);
+ num_sev++;
+ return;
+ }
+ }
+
+ /* Rename the temporary file to the target output file. */
+ status=rename(p_temp,p_target);
+
+ /* Do the error checking. */
+ if (status != RENAME_S)
+ {
+ wl_sjl("S: FIXEOLS: Error renaming temporary output file to output file.");
+ sprintf(linet1,"Temporary file name was \"%s\".",p_temp);
+ wl_sjl(linet1);
+ sprintf(linet1,"Output file name was \"%s\".",p_target);
+ wl_sjl(linet1);
+ wl_sjl("FunnelWeb will leave both files intact so you can look at them.");
+ num_sev++;
+ return;
+ }
+
+ return;
+
+ write_failure:
+ wl_sj("S: FIXEOLS: Error writing the output file.");num_sev++;return;
+#endif
+}
+
+/******************************************************************************/
+
+LOCAL void do_fweb P_((char *));
+LOCAL void do_fweb(p_cl)
+/* This function performs a single run of FunnelWeb proper. */
+char *p_cl;
+{
+ op_t saveop;
+
+ /* Do set can do all the work for us. However, it operates on p_defopt so we */
+ /* have to do some juggling. */
+ ASSIGN(saveop,*p_defopt);
+ do_set(p_cl);
+ ASSIGN(option,*p_defopt);
+ ASSIGN(*p_defopt,saveop);
+ if (num_sev>0) return;
+
+ /* do_set ensures that the user hasn't specified any action parameters such */
+ /* as +X and +K, but it necessarily doesn't check to make sure that the user */
+ /* has actually specified an input file! */
+ if (!option.op_f_b)
+ {
+ wl_sj("S: No input file specified in FW command.");
+ num_sev++;
+ return;
+ }
+ fwonerun();
+}
+
+/******************************************************************************/
+
+LOCAL void do_help P_((void));
+LOCAL void do_help()
+{
+ uword messno;
+
+ if (arg_num == 1)
+ {
+ hel_wri(wr_sj,HL_MEN);
+ return;
+ }
+ if (arg_num > 2)
+ {
+ wl_sj("S: The HELP command takes at most one argument.");
+ num_sev++;
+ return;
+ }
+
+ /* Translate message name to number. */
+
+ messno=hel_num(arg_arr[1]);
+ if (messno == HL_ERR)
+ {
+ wl_sj(
+ "S: Unrecognised help message name. Try just \"help\" for a list of names.");
+ num_sev++;
+ return;
+ }
+
+ hel_wri(wr_sj,messno);
+}
+
+/******************************************************************************/
+
+LOCAL void do_set(p_comlin)
+/* The SET command allows the user to specify default FunnelWeb options. */
+char *p_comlin;
+{
+ op_t tmpopt;
+
+ /* Experiment with temporary options, not the real thing. */
+ ASSIGN(tmpopt,*p_defopt);
+
+ /* Now execute the effect of the command line on 'p_defopt'. */
+ if (!op_add(&tmpopt,p_comlin,wr_sj))
+ {
+ wl_s("This is a severe error (S). Aborting to FunnelWeb shell...");
+ num_sev++;
+ return;
+ }
+
+ /* Now make sure that the user didn't specify an option to do with the */
+ /* entire FunnelWeb run and not just this invocation of FunnelWeb proper. */
+ if (tmpopt.op_j_b)
+ {
+ wl_s("S: You cannot invoke FunnelWeb with +J from the FunnelWeb shell.");
+ wl_s(" To create a journal file, exit FunnelWeb and reinvoke with \"fw +j\".");
+ wl_s("This is a severe error. Aborting to FunnelWeb shell...");
+ num_sev++;
+ return;
+ }
+ if (tmpopt.op_x_b)
+ {
+ wl_s("S: You cannot invoke FunnelWeb with +X from the FunnelWeb shell.");
+ wl_s("Use the interactive command EXECUTE instead.");
+ wl_s("This is a severe error. Aborting to FunnelWeb shell...");
+ num_sev++;
+ return;
+ }
+ if (tmpopt.op_k_b)
+ {
+ wl_s("S: You cannot invoke FunnelWeb with +K from the FunnelWeb shell.");
+ wl_s("This is a severe error. Aborting to FunnelWeb shell...");
+ num_sev++;
+ return;
+ }
+ if (tmpopt.op_h_b)
+ {
+ wl_s("S: You cannot invoke FunnelWeb with +H from the FunnelWeb shell.");
+ wl_s("Use the interactive command HELP instead.");
+ wl_s("This is a severe error. Aborting to FunnelWeb shell...");
+ num_sev++;
+ return;
+ }
+
+ /* If we get to here, the options must be OK so we can set them as default. */
+ ASSIGN(*p_defopt,tmpopt);
+}
+
+/******************************************************************************/
+
+LOCAL void do_show P_((void));
+LOCAL void do_show()
+/* The SHOW command writes out the current options. */
+{
+ if (arg_num != 1)
+ {
+ wl_sj("S: The SHOW command does not take arguments.");
+ num_sev++;
+ return;
+ }
+ wl_sj("Here are the FunnelWeb command line options that are");
+ wl_sj("current in this FunnelWeb session:");
+ op_wri(p_defopt,wr_sj);
+}
+
+/******************************************************************************/
+
+LOCAL void do_stat P_((void));
+LOCAL void do_stat ()
+/* The status command checks the number of diagnostics generated by the run. */
+{
+ uword i;
+ char *thing;
+ ulong cnum;
+
+ if (arg_num<1 || arg_num>4)
+ {
+ wl_sj("S: The STATUS command requires zero to three arguments.");
+ num_sev++;
+ return;
+ }
+
+ /* Zero arguments just means write out status. */
+ if (arg_num == 1)
+ {
+ sprintf(linet1,"Last command: Severes=%lu, Errors=%lu, Warnings=%lu.",
+ (ulong) old_sev, (ulong) old_err, (ulong) old_war);
+ wl_sj(linet1);
+ sprintf(linet1,"Totals : Severes=%lu, Errors=%lu, Warnings=%lu.",
+ (ulong) sum_sev, (ulong) sum_err, (ulong) sum_war);
+ wl_sj(linet1);
+ return;
+ }
+
+ /* More than one argument means CHECK status. */
+ for (i=1;i<arg_num;i++)
+ {
+ char ch=toupper(arg_arr[i][0]);
+ unsigned num;
+ if (ch!='W' && ch!='E' && ch!='S')
+ {
+ sprintf(linet1,
+ "S: Argument %u of STATUS command has bad letter.",
+ (unsigned) i);
+ wl_sj(linet1);
+ wl_sj("Arguments must be of the form ('W'|'E'|'S')<decimalnumber>.");
+ num_sev++;
+ return;
+ }
+ if (sscanf(&arg_arr[i][1],"%u",&num) != 1)
+ {
+ sprintf(linet1,
+ "S: Argument %u of STATUS command has bad number.",
+ (unsigned) i);
+ wl_sj(linet1);
+ wl_sj(" Arguments must be of the form ('W'|'E'|'S')<decimalnumber>.");
+ num_sev++;
+ return;
+ }
+ switch(ch)
+ {
+ case 'W': cnum=old_war; thing="warnings"; break;
+ case 'E': cnum=old_err; thing="errors" ; break;
+ case 'S': cnum=old_sev; thing="severes" ; break;
+ default : as_bomb("do_stat: case defaulted.");
+ }
+ if (cnum != num)
+ {
+ sprintf(linet1,
+ "S: STATUS command detected wrong number of %s.",thing);
+ wl_sj(linet1);
+ sprintf(linet1, "Specifed %s=%u, Actual %s=%u.",
+ thing,(unsigned) num,thing,(unsigned) cnum);
+ wl_sj(linet1);
+ num_sev++;
+ }
+ }
+}
+
+/******************************************************************************/
+
+
+LOCAL void do_trace P_((void));
+LOCAL void do_trace()
+{
+ if (arg_num != 2) goto help;
+ strupper(arg_arr[1]);
+ if (strcmp(arg_arr[1],"OFF") == 0) {tracing=FALSE; return;}
+ if (strcmp(arg_arr[1], "ON") == 0) {tracing=TRUE ; return;}
+
+ help:
+ wl_sj("S: The TRACE command has two forms:");
+ wl_sj(" TRACE OFF");
+ wl_sj(" TRACE ON");
+ num_sev++;
+ return;
+}
+
+/******************************************************************************/
+
+LOCAL void do_write P_((char *));
+LOCAL void do_write(p)
+char *p;
+{
+ uword len;
+
+ /* Skip over the main command and the following blanks. */
+ while (*p!=' ' && *p!=EOS) p++;
+ while (*p==' ') p++;
+
+ /* Now make sure that the remaining string is delimited by double quotes. */
+ len=strlen(p);
+ if ((*p != '\"') || (p[len-1] != '\"') || len<2)
+ {
+ wl_sj("W: The argument to WRITE should be delimited by double quotes.");
+ wl_sj(p);
+ num_war++;
+ return;
+ }
+
+ /* Now temporarily hack out the quotes and write out the string. */
+ p[len-1]=EOS;
+ wl_sj(p+1);
+ p[len-1]='\"';
+}
+
+/******************************************************************************/
+
+LOCAL void do_writu P_((char *));
+LOCAL void do_writu(p)
+char *p;
+{
+ uword len;
+
+ /* Skip over the main command and the following blanks. */
+ while (*p!=' ' && *p!=EOS) p++;
+ while (*p==' ') p++;
+
+ /* Now make sure that the remaining string is delimited by double quotes. */
+ len=strlen(p);
+ if ((*p != '\"') || (p[len-1] != '\"') || len<2)
+ {
+ wl_sj("W: The argument to WRITEU should be delimited by double quotes.");
+ wl_sj(p);
+ num_war++;
+ return;
+ }
+
+ /* Now temporarily hack out the quotes and write out the string. */
+ p[len-1]=EOS;
+ wl_sj(p+1);
+ p[len-1]='\"';
+
+ /* Now write out another line underlining the above. */
+ {uword i; for (i=0;i<len-2;i++) wr_sj("-"); wl_sj("");}
+}
+
+/******************************************************************************/
+
+LOCAL bool do_command P_((char *));
+LOCAL bool do_command(p_command)
+/* Execute a single FunnelWeb shell command. */
+char *p_command;
+{
+ char *v;
+ bool result=FALSE;
+
+ zerdia();
+
+ /* Check the command for non-printables. */
+ {
+ char *s=p_command;
+ while (*s!=EOS)
+ {
+ if (!isascprn(*s))
+ {
+ sprintf(linet1,
+ "S: Command line has non-printable at column %u.",
+ (unsigned) (s-p_command)+1);
+ wl_sj(linet1);
+ num_sev++;
+ goto finished;
+ }
+ s++;
+ }
+ }
+
+ /* Perform substitutions. */
+ dollsub(p_command); if (num_sev>0) goto finished;
+
+ /* Ignore commands consisting entirely of blanks (or empty commands). */
+ {
+ char *s=p_command;
+ while (*s==' ') s++;
+ if (*s==EOS) goto finished;
+ }
+
+ /* Reject command lines beginning with a blank. */
+ if (p_command[0]==' ')
+ {
+ wl_sj("S: Leading blanks are not allowed in command lines.");
+ num_sev++;
+ goto finished;
+ }
+
+ /* Ignore command lines commencing with the comment character. */
+ if (p_command[0]=='!')
+ {restdia(); goto finished;}
+
+ /* Parse the command line into arguments. */
+ explode(p_command);
+
+ /* Complain if there is no command verb. */
+ as_cold(arg_num>0,"do_command: zero arguments!");
+
+ /* It's convenient to have v pointing to verb. */
+ v=arg_arr[0];
+
+ /* Convert the verb to upper case. */
+ strupper(v);
+
+
+ /* Execute the verb. */
+
+ if (strcmp(v,"HERE")==0) skipping=FALSE;
+ else if (!skipping)
+ if (strcmp(v,"ABSENT" )==0) do_absen();
+ else if (strcmp(v,"CODIFY" )==0) do_cody ();
+ else if (strcmp(v,"COMPARE" )==0) do_comp ();
+ else if (strcmp(v,"DEFINE" )==0) do_defin(&p_command[0]);
+ else if (strcmp(v,"DIFF" )==0) do_diff();
+ else if (strcmp(v,"DIFFSUMMARY")==0) do_dsum();
+ else if (strcmp(v,"DIFFZERO" )==0) do_dzer();
+ else if (strcmp(v,"ENEO" )==0) do_eneo ();
+ else if (strcmp(v,"EXECUTE" )==0) do_exec ();
+ else if (strcmp(v,"EXISTS" )==0) do_exist();
+ else if (strcmp(v,"FIXEOLS" )==0) do_fix ();
+ else if (strcmp(v,"FW" )==0) do_fweb (&p_command[0]);
+ else if (strcmp(v,"HELP" )==0) do_help ();
+ else if (strcmp(v,"QUIT" )==0) result=TRUE;
+ else if (strcmp(v,"SET" )==0) do_set (&p_command[0]);
+ else if (strcmp(v,"SHOW" )==0) do_show ();
+ else if (strcmp(v,"SKIPTO" )==0) skipping=TRUE;
+ else if (strcmp(v,"STATUS" )==0) do_stat ();
+ else if (strcmp(v,"TOLERATE" )==0) noabort=TRUE;
+ else if (strcmp(v,"TRACE" )==0) do_trace();
+ else if (strcmp(v,"WRITE" )==0) do_write(&p_command[0]);
+ else if (strcmp(v,"WRITEU" )==0) do_writu(&p_command[0]);
+ else
+ {
+ /* The following trace is likely to confuse beginners. */
+ /* sprintf(linet1,"Expanded command line=\"%s\".",p_command); */
+ /* wl_sj(linet1); */
+ wl_sj("S: Unknown command. Type HELP for a list of commands.");
+ num_sev++;
+ goto finished;
+ }
+
+ finished:
+ sumdia();
+ return result;
+}
+
+/******************************************************************************/
+
+LOCAL void interpret P_((FILE *,char *));
+LOCAL void interpret(p_file,filnam)
+/* p_file must be a file opened for reading. The file's name must be supplied */
+/* in filnam for error reporting reasons. The function reads each line from */
+/* the file and feeds it to the FunnelWeb interpreter command executer. */
+FILE *p_file;
+char *filnam;
+{
+ ulong lineno=0;
+ char *result;
+ bool b;
+ cl_t comline;
+ char *p_comline;
+
+ p_comline = &comline[0];
+
+ while (TRUE)
+ {
+ bool oldnoabort = noabort;
+ noabort=FALSE;
+
+ if (p_file == stdin || tracing)
+ wr_sj("FunnelWeb>");
+
+ result=fgets(p_comline,(int) COMLINE_FWMAX,p_file);
+ if (feof(p_file))
+ {
+ sprintf(linet1,"<End of Script File \"%s\">",filnam);
+ if (p_file == stdin || tracing)
+ wl_sj(linet1);
+ break;
+ }
+ if (ferror(p_file) || (result == FGETS_FE))
+ {
+ sprintf(linet1,"F: Error reading command file \"%s\".",filnam);
+ wl_sj(linet1);
+ wl_sj("Aborting...");
+ sum_fat++;
+ return;
+ }
+ if (p_file == stdin || tracing) wr_j(p_comline);
+ if (p_file != stdin && tracing) wr_s(p_comline);
+
+ lineno++;
+ if (strlen(p_comline)==COMLINE_FWMAX)
+ {
+ sprintf(linet1,"F: Line %lu of command file \"%s\" is too long.",
+ (unsigned long) lineno,filnam);
+ wl_sj(linet1);
+ wl_sj("Aborting...");
+ sum_fat++;
+ return;
+ }
+ as_cold(p_comline[strlen(p_comline)-1]==EOL,"interpret: NO NEWLINE!");
+ p_comline[strlen(p_comline)-1]=EOS;
+ as_cold(strlen(p_comline)<COMLINE_FWMAX,"interpret: Filename too long.");
+
+ b=do_command(p_comline);
+ if (b) break;
+
+ if (sum_fat>0) break;
+ if ((p_file != stdin) && (num_sev+num_err>0) && !oldnoabort)
+ {
+ wl_sj("Error caused termination of FunnelWeb shellscript.");
+ break;
+ }
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void interstr(filnam)
+/* The 'interpret' function (above) interprets each line of a file already */
+/* opened for reading. This function does a little more, opening and closing */
+/* the file before and after calling 'interpret'. */
+char *filnam;
+{
+ FILE *p_file;
+ fn_t fn;
+
+ /* Set up a default of ".fws" as a file extension. */
+ strcpy(&fn[0],".fws");
+
+ /* Inherit the actual filename. */
+ as_cold(strlen(filnam)<=FILENAME_FWMAX,"interstr: Filename blasted.");
+ fn_ins(&fn[0],filnam);
+
+ p_file=fopen(fn,"r");
+ if (p_file == FOPEN_F)
+ {
+ sprintf(linet1,"S: Error opening command file \"%s\".",fn);
+ wl_sj(linet1);
+ sum_sev++;
+ return;
+ }
+
+ interpret(p_file,&fn[0]); if (sum_fat>0) return;
+
+ if (fclose(p_file) == FCLOSE_F)
+ {
+ sprintf(linet1,"F: Error closing command file \"%s\".",fn);
+ wl_sj(linet1);
+ wl_sj("Aborting...");
+ sum_fat++;
+ return;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void chk_cline P_((void));
+LOCAL void chk_cline()
+/* Checks to make sure that the command line specifies exactly one action. */
+{
+ uword countopt=0;
+
+ /* Count the number of active action options are turned on. */
+ if (p_comopt->op_f_b) countopt++;
+ if (p_comopt->op_x_b) countopt++;
+ if (p_comopt->op_k_b) countopt++;
+ if (p_comopt->op_h_b) countopt++;
+
+ if (countopt == 0)
+ {
+ wl_sj("Your command line does not specify an action.");
+ wl_sj("Here some common ways of invoking FunnelWeb.");
+ wl_sj("");
+ wl_sj(" fw filename Tangle filename.web.");
+ wl_sj(" fw filename +t Tangle and weave filename.web.");
+ wl_sj(" fw +k Enter interactive mode.");
+ wl_sj(" fw +xfilename Execute FunnelWeb shellscript filename.fws.");
+ wl_sj(" fw +h Display help information about FunnelWeb.");
+ wl_sj("");
+ if (countopt == 0)
+ wl_sj("F: Aborting because command line does not specify an action.");
+ sum_fat++;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void open_j P_((void));
+LOCAL void open_j()
+/* Creates and opens the journal file. Note that the journal output stream is */
+/* established regardless of whether the user requested a journal file. The */
+/* only difference is that if the user did not specify a journal file, the */
+/* stream is created in error mode which means that it never actually writes. */
+{
+ fn_t jname;
+
+ /* Establish the journal file output stream. */
+ strcpy(jname,""); /* Start with an empty string. */
+ fn_ins(jname,p_comopt->op_f_s); /* Insert input file name. */
+ fn_ins(jname,".jrn"); /* Insert file extension. */
+ fn_ins(jname,p_comopt->op_j_s); /* Insert command line spec. */
+ wf_ini(&f_j,p_comopt->op_j_b); /* Initialize the stream. */
+ wf_ope(&f_j,jname); /* Create the file. */
+ if (p_comopt->op_j_b && wf_err(&f_j))
+ {
+ sprintf(linet1,"F: Error creating journal file \"%s\".",jname);
+ wl_s(linet1);
+ wl_s("Aborting...");
+ sum_fat++;
+ return;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void close_j P_((void));
+LOCAL void close_j()
+/* Closes the journal file. */
+{
+ if (!p_comopt->op_j_b) return;
+ if (wf_err(&f_j))
+ {
+ wl_s("F: Error writing to journal file. Aborting...");
+ sum_fat++;
+ return;
+ }
+ wf_clo(&f_j);
+ if (wf_err(&f_j))
+ {
+ wl_s("F: Error flushing and closing journal file. Aborting...");
+ sum_fat++;
+ return;
+ }
+}
+
+/******************************************************************************/
+
+LOCAL void cl_help P_((void));
+LOCAL void cl_help ()
+{
+ uword messno;
+
+ /* Translate message name to number. This ought to work, as the options */
+ /* package is already have supposed to have cleared this argument as OK. */
+ messno=hel_num(p_comopt->op_h_s);
+ as_cold(messno!=HL_ERR,"cl_help: Unknown help argument.");
+
+ /* Write out the message. */
+ hel_wri(wr_sj,messno);
+}
+
+/******************************************************************************/
+
+EXPORT void command(p_comline)
+/* Execute the top level command line. This is the place where we do all the */
+/* "once per shell" things as opposed to the "once per run" things. */
+/* If a fatal error occurs, the correct course of action here is to increment */
+/* sum_fat and return immediately. The main() function deals with delivering */
+/* the correct return status to the operating system. */
+char *p_comline;
+{
+ old_war=old_err=old_sev=0;
+
+ tracing=FALSE;
+ noabort=FALSE;
+ skipping=FALSE;
+
+ /* Allocate space for command line arguments. */
+ allocarg();
+
+ /* Set up the standard output (stdout) screen (console) output stream. */
+ wf_ini(&f_s,TRUE);
+ wf_att(&f_s,stdout);
+
+ /* Parse the command line and place the information in p_comopt-> */
+ op_ini(p_comopt);
+ if (!op_add(p_comopt,p_comline,wr_s))
+ {wr_s("F: Command line error. Aborting..."); sum_fat++; goto windup;}
+
+ /* If the user asked for some peace and quiet by setting +Q, disable the */
+ /* console stream. Note: FunnelWeb main() always issues a message using a */
+ /* printf if any diagnostics have been generated. */
+ if (p_comopt->op_q_b) wf_ini(&f_s,FALSE);
+
+ /* Create and open the journal file. */
+ open_j(); if (sum_fat>0) goto windup;
+
+ wl_sj("FunnelWeb Version 3.05AC (Oct 1995)");
+ wl_sj("----------------------**-----------");
+ wl_sj("* Differences from FunnelWeb 3.0 are in `sources/README.ABC'.");
+ wl_sj("* No guarantee can be given that all of the features");
+ wl_sj("* of FunnelWeb 3.05AC will appear in Ross Williams'");
+ wl_sj("* next official release of `FunnelWeb'.");
+ wl_sj("-----------------------------------");
+ wl_sj("Copyright (C) Ross Williams 1992.");
+ wl_sj("Copyright (C) Anthony B. Coates 1995. There is ABSOLUTELY NO WARRANTY.");
+ wl_sj("-----------------------------------");
+ wl_sj("Bug reports to `coates@physics.uq.edu.au'.");
+ wl_sj("You are welcome to distribute this software under certain conditions.");
+ /*
+ wl_sj("THIS IS NOT AN APPROVED VERSION OF FUNNELWEB.");
+ wl_sj("USE ONLY AT OWN RISK. [A.B.Coates]");
+ */
+ if (p_comopt->op_k_b)
+ wl_sj("For more information, type HELP.");
+ else
+ wl_sj("For more information, use the +h (help) option (e.g. \"fw +h\").");
+ wl_sj("");
+
+ /* Ensure that the user has specified at least one action. */
+ chk_cline(); if (sum_fat>0) goto windup;
+
+ /* Establish the default options for the shell run (if any). */
+ /* Get rid of any options not to do with a single run of FunnelWeb proper. */
+ ASSIGN(*p_defopt,*p_comopt);
+ p_defopt->op_j_b=FALSE;
+ p_defopt->op_x_b=FALSE;
+ p_defopt->op_k_b=FALSE;
+
+ /* In the absence of everything else, command line options are run options. */
+ ASSIGN(option,*p_comopt);
+
+ /* Execute initialization file if any. */
+ if (fexists(INITFILE))
+ interstr(INITFILE);
+
+ /* Execute the specified actions. */
+ /* ABC { */
+ batchQ = p_comopt->op_q_b && !strcmp(p_comopt->op_q_s, "b");
+ /* } ABC */
+ if (p_comopt->op_x_b) interstr(p_comopt->op_x_s);
+ if (p_comopt->op_f_b) {zerdia(); fwonerun(); sumdia();}
+ if (p_comopt->op_h_b) cl_help();
+ if (p_comopt->op_k_b) interpret(stdin,"standard_input");
+
+ /* If we weren't in onerun mode, give a grand summary of errors. */
+ if (p_comopt->op_k_b || p_comopt->op_x_b)
+ {
+ wr_sj("Final diagnostics totals: ");
+ errsum(sum_fat,sum_sev,sum_err,sum_war);
+ wl_sj(linet1);
+ }
+
+ /* Close the journal file. */
+ close_j(); if (sum_fat>0) goto windup;
+
+ /* Check for errors on the screen stream (standard output). */
+ if (p_comopt->op_s_b && wf_err(&f_s))
+ {
+ /* No point in trying to write a message to the screen! */
+ /* But we can at least register a fatal error. */
+ sum_fat++;
+ }
+
+ windup:
+
+ /* If the user has set +Q to turn off screen output and one or more */
+ /* diagnostics have been generated, we need to break through to warn the */
+ /* user. */
+ {
+ ulong sum_all=sum_fat+sum_sev+sum_err+sum_war;
+ if (p_comopt->op_q_b && sum_all>0)
+ {
+ errsum(sum_fat,sum_sev,sum_err,sum_war);
+ fprintf(stderr,"%s\n",linet1);
+ }
+ }
+}
+
+/******************************************************************************/
+/* End of COMMAND.C */
+/******************************************************************************/