home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / unix / volume26 / funnlweb / part18 < prev    next >
Encoding:
Text File  |  1993-04-10  |  71.8 KB  |  2,160 lines

  1. Newsgroups: comp.sources.unix
  2. From: ross@spam.adelaide.edu.au (Ross Williams)
  3. Subject: v26i138: funnelweb - a tool for literate programming in C, Part18/20
  4. Sender: unix-sources-moderator@vix.com
  5. Approved: paul@vix.com
  6.  
  7. Submitted-By: ross@spam.adelaide.edu.au (Ross Williams)
  8. Posting-Number: Volume 26, Issue 138
  9. Archive-Name: funnelweb/part18
  10.  
  11. #! /bin/sh
  12. # This is a shell archive.  Remove anything before this line, then unpack
  13. # it by saving it into a file and typing "sh file".  To overwrite existing
  14. # files, type "sh file -c".  You can also feed this as standard input via
  15. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  16. # will see the following message at the end:
  17. #        "End of archive 18 (of 20)."
  18. # Contents:  sources/command.c
  19. # Wrapped by vixie@gw.home.vix.com on Sun Apr 11 11:00:33 1993
  20. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  21. if test -f 'sources/command.c' -a "${1}" != "-c" ; then 
  22.   echo shar: Will not clobber existing file \"'sources/command.c'\"
  23. else
  24. echo shar: Extracting \"'sources/command.c'\" \(69805 characters\)
  25. sed "s/^X//" >'sources/command.c' <<'END_OF_FILE'
  26. X/*##############################################################################
  27. X
  28. XFUNNNELWEB COPYRIGHT
  29. X====================
  30. XFunnelWeb is a literate-programming macro preprocessor.
  31. X
  32. Copyright (C) 1992 Ross N. Williams.
  33. X
  34. X   Ross N. Williams
  35. X   ross@spam.adelaide.edu.au
  36. X   16 Lerwick Avenue, Hazelwood Park 5066, Australia.
  37. X
  38. This program is free software; you can redistribute it and/or modify
  39. it under the terms of Version 2 of the GNU General Public License as
  40. published by the Free Software Foundation.
  41. X
  42. This program is distributed WITHOUT ANY WARRANTY; without even the implied
  43. warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  44. See Version 2 of the GNU General Public License for more details.
  45. X
  46. You should have received a copy of Version 2 of the GNU General Public
  47. License along with this program. If not, you can FTP the license from
  48. prep.ai.mit.edu/pub/gnu/COPYING-2 or write to the Free Software
  49. XFoundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  50. X
  51. Section 2a of the license requires that all changes to this file be
  52. recorded prominently in this file. Please record all changes here.
  53. X
  54. Programmers:
  55. X   RNW  Ross N. Williams  ross@spam.adelaide.edu.au
  56. X
  57. Changes:
  58. X   07-May-1992  RNW  Program prepared for release under GNU GPL V2.
  59. X
  60. X##############################################################################*/
  61. X
  62. X
  63. X/******************************************************************************/
  64. X/*                                COMMAND.C                                   */
  65. X/******************************************************************************/
  66. X
  67. X#include <ctype.h>
  68. X#include "style.h"
  69. X
  70. X#include "analyse.h"
  71. X#include "as.h"
  72. X#include "command.h"
  73. X#include "data.h"
  74. X#include "dump.h"
  75. X#include "lister.h"
  76. X#include "memory.h"
  77. X#include "mapper.h"
  78. X#include "misc.h"
  79. X#include "option.h"
  80. X#include "parser.h"
  81. X#include "scanner.h"
  82. X#include "tangle.h"
  83. X#include "weave.h"
  84. X
  85. X/******************************************************************************/
  86. X
  87. X/* Because FunnelWeb has so many options ,it is convenient to allow the user  */
  88. X/* to construct a startup file. This is its name.                             */
  89. X#define INITFILE "fwinit.fws"
  90. X
  91. X/******************************************************************************/
  92. X
  93. X/* Three variables hold options information in FunnelWeb.                     */
  94. X/* 'p_comopt' holds the options conveyed by the original raw command line.      */
  95. X/* 'p_defopt' holds the options that are default in the FunnelWeb shell.        */
  96. X/* 'option' is global and holds the options that have been specified for the  */
  97. X/*          current invocation of FunnelWeb proper.                           */
  98. X/* 'option' is global so here we see only 'p_comopt' and 'p_defopt'.              */
  99. LOCVAR op_t *p_comopt;  /* Initial command line options.                         */
  100. LOCVAR op_t *p_defopt;  /* Default shell options.                                */
  101. X
  102. X/* The FunnelWeb shell interpreter has many different commands and it is      */
  103. X/* worth them sharing the same basic command line scanner. These two          */
  104. X/* variables hold the numer of arguments and a pointer to strings holding     */
  105. X/* copies of each argument.                                                   */
  106. X/* Note: The first argument is placed in arr_arg[0].                          */
  107. X#define ARG_MAX 5
  108. LOCVAR uword arg_num;
  109. LOCVAR char *arg_arr[ARG_MAX+1]; /* Is this +1 necessary? */
  110. X
  111. X/* The FunnelWeb command interpreter allows 10 substitution strings.          */
  112. X/* These strings are stored in the following array.                           */
  113. X#define NUM_SUBS (10+26)        /* 0..9 and A..Z */
  114. LOCVAR char *subval[NUM_SUBS];
  115. X
  116. LOCVAR ulong old_war;
  117. LOCVAR ulong old_err;
  118. LOCVAR ulong old_sev;
  119. X
  120. X/* The FunnelWeb script interpreter can echo (trace) each command before it   */
  121. X/* executes it. Whether it does is determined by this variable.               */
  122. LOCVAR bool tracing;
  123. X
  124. X/* If this variable is set to true then the interpreter will not abort if the */
  125. X/* very next command (and that one only) generates an error or severe error.  */
  126. LOCVAR bool noabort;
  127. X
  128. X/* If the following boolean is true, the interpreter skips lines until it     */
  129. X/* hits the HERE command.                                                     */
  130. LOCVAR bool skipping;
  131. X
  132. X/* The following variables count how many times DIFF is invoked and how many  */
  133. X/* times it succeeded. This allows us to output a summary at the end of the   */
  134. X/* processing of the test suite.                                              */
  135. LOCVAR ulong difftotl = 0;
  136. LOCVAR ulong diffsucc = 0;
  137. X
  138. X/******************************************************************************/
  139. X
  140. X/* Here are the prototypes for recursive or out-of-order functions.           */
  141. LOCAL void interstr P_((char *));
  142. LOCAL void do_set P_((char *));
  143. X
  144. X/******************************************************************************/
  145. X
  146. LOCAL char *sing P_((ulong,char *,char *));
  147. LOCAL char *sing(num,sinstr,plustr)
  148. X/* Return one or other string depending on whether first argument is singular.*/
  149. ulong num;
  150. char *sinstr;
  151. char *plustr;
  152. X{
  153. X if (num==1)
  154. X    return sinstr;
  155. X else
  156. X    return plustr;
  157. X}
  158. X
  159. X/******************************************************************************/
  160. X
  161. LOCAL uword numpos P_((ulong,ulong,ulong));
  162. LOCAL uword numpos(a,b,c)
  163. X/* Returns the number of its arguments that are positive. */
  164. ulong a,b,c;
  165. X{
  166. X uword result=0;
  167. X if (a>0) result++;
  168. X if (b>0) result++;
  169. X if (c>0) result++;
  170. X return result;
  171. X}
  172. X
  173. X/******************************************************************************/
  174. X
  175. LOCAL void errsum P_((ulong,ulong,ulong,ulong));
  176. LOCAL void errsum(fat,sev,err,war)
  177. X/* Supply as arguments, the number of various kinds of diagnostics.           */
  178. X/* Places in linet1 a string describing the diagnostics.                      */
  179. ulong fat,sev,err,war;
  180. X{
  181. X char linet2[100];
  182. X
  183. X if (fat+sev+err+war==0)
  184. X    {strcpy(linet1,"SUCCESS: No diagnostics."); return;}
  185. X
  186. X strcpy(linet1,"There ");
  187. X
  188. X /* "Was" or "were" depending on the plurality of highest level diagnostic. */
  189. X      if (fat>0) strcat(linet1,sing(fat,"was","were"));
  190. X else if (sev>0) strcat(linet1,sing(sev,"was","were"));
  191. X else if (err>0) strcat(linet1,sing(err,"was","were"));
  192. X else if (war>0) strcat(linet1,sing(war,"was","were"));
  193. X else as_bomb("errsum: Error hierarchy failed!");
  194. X
  195. X strcat(linet1," ");
  196. X
  197. X /* Fatal errors. */
  198. X if (fat>0)
  199. X   {
  200. X    sprintf(linet2,"%1lu Fatal error",fat); strcat(linet1,linet2);
  201. X    strcat(linet1,sing(fat,"","s"));
  202. X   }
  203. X
  204. X /* Joiner stuff. */
  205. X if (fat>0 && numpos(sev,err,war)>=2)    strcat(linet1,", ");
  206. X if (fat>0 && sev>0 && err==0 && war==0) strcat(linet1," and ");
  207. X
  208. X /* Severe errors. */
  209. X if (sev>0)
  210. X   {
  211. X    sprintf(linet2,"%1lu Severe error",sev); strcat(linet1,linet2);
  212. X    strcat(linet1,sing(sev,"","s"));
  213. X   }
  214. X
  215. X /* Joiner stuff. */
  216. X if (fat+sev>0 && err>0 && war >0) strcat(linet1,", ");
  217. X if (fat+sev>0 && err>0 && war==0) strcat(linet1," and ");
  218. X
  219. X /* Errors. */
  220. X if (err>0)
  221. X   {
  222. X    sprintf(linet2,"%1lu Error",err); strcat(linet1,linet2);
  223. X    strcat(linet1,sing(err,"","s"));
  224. X   }
  225. X
  226. X /* Joiner stuff. */
  227. X if (fat+sev+err>0 && war>0) strcat(linet1," and ");
  228. X
  229. X /* Warnings. */
  230. X if (war > 0)
  231. X   {
  232. X    sprintf(linet2,"%1lu Warning",war); strcat(linet1,linet2);
  233. X    strcat(linet1,sing(war,"","s"));
  234. X   }
  235. X
  236. X /* The final full stop! */
  237. X strcat(linet1,".");
  238. X}
  239. X
  240. X/******************************************************************************/
  241. X
  242. LOCAL void allocarg P_((void));
  243. LOCAL void allocarg()
  244. X/* Some compilers don't allow much room for statics and so it is necessary to */
  245. X/* declare some variables as pointers and allocate them explicitly. This      */
  246. X/* function allocates the command line argument array to point to strings.    */
  247. X{
  248. X uword i;
  249. X for (i=0;i<=ARG_MAX;i++)
  250. X    arg_arr[i]=(char *) mm_perm(sizeof(cl_t));
  251. X
  252. X /* Also initialize the substitution strings. */
  253. X for (i=0;i<NUM_SUBS;i++)
  254. X   {
  255. X    subval[i]=(char *) mm_perm(sizeof(cl_t));
  256. X    subval[i][0]=EOS;
  257. X   }
  258. X p_comopt=(p_op_t) mm_perm(sizeof(op_t));
  259. X p_defopt=(p_op_t) mm_perm(sizeof(op_t));
  260. X}
  261. X
  262. X/******************************************************************************/
  263. X
  264. LOCAL void zerdia P_((void));
  265. LOCAL void zerdia()
  266. X{
  267. X old_war=num_war;
  268. X old_err=num_err;
  269. X old_sev=num_sev;
  270. X num_sev=num_err=num_war=0;
  271. X}
  272. X
  273. LOCAL void sumdia P_((void));
  274. LOCAL void sumdia()
  275. X{
  276. X sum_sev+=num_sev;
  277. X sum_err+=num_err;
  278. X sum_war+=num_war;
  279. X}
  280. X
  281. LOCAL void restdia P_((void));
  282. LOCAL void restdia()
  283. X{
  284. X num_sev+=old_sev;
  285. X num_err+=old_err;
  286. X num_war+=old_war;
  287. X}
  288. X
  289. X/******************************************************************************/
  290. X
  291. LOCAL void explode P_((char *));
  292. LOCAL void explode(p)
  293. X/* Parses the string p into a set of non-blank sequences. Copies each         */
  294. X/* distinct run of non-blanks into successive values of arg_arr. Places the   */
  295. X/* number of runs of non-blanks into arg_num. DOESN'T generate an error if    */
  296. X/* there are too many arguments.                                              */
  297. char *p;
  298. X{
  299. X arg_num=0;
  300. X while (TRUE)
  301. X   {
  302. X    char *x;
  303. X
  304. X    /* Skip to the next argument. */
  305. X    while (*p==' ') p++;
  306. X
  307. X    /* Exit if we have hit the end of the string. */
  308. X    if (*p==EOS) break;
  309. X
  310. X    /* Exit if we are already full up with arguments. */
  311. X    if (arg_num==ARG_MAX) break;
  312. X
  313. X    /* Copy the next argument. */
  314. X    x=arg_arr[arg_num];
  315. X    while (*p!=' ' && *p!=EOS) *x++ = *p++;
  316. X    *x=EOS;
  317. X    arg_num++;
  318. X   }
  319. X}
  320. X
  321. X/******************************************************************************/
  322. X
  323. LOCAL void dollsub P_((char *));
  324. LOCAL void dollsub(p_comlin)
  325. X/* Assumes that it's string argument is in a command line object. */
  326. X/* Performs dollar substitutions. */
  327. char *p_comlin;
  328. X{
  329. X char *p=p_comlin;
  330. X cl_t  cl;
  331. X char *t;
  332. X
  333. X t = &cl[0];
  334. X
  335. X /* Copy the unexpanded command line to the temporary from which we expand it */
  336. X /* back from whence it came.                                                 */
  337. X strcpy(t,p);
  338. X
  339. X /* Work through the unexpanded command line expanding. */
  340. X while (*t != EOS)
  341. X   {
  342. X    /* Complain if there is not room for one more character. */
  343. X    if ((p-p_comlin) == COMLINE_MAX) goto toobig;
  344. X
  345. X    /* If no dollar sign, no tricks. Just copy the character over. */
  346. X    if (*t!='$') {*p++ = *t++; continue;}
  347. X
  348. X    /* We have seen a dollar sign. Move onto the next character. */
  349. X    t++;
  350. X
  351. X    /* The only legal escapes are decimal digits, slash, and the dollar sign. */
  352. X    if (!isdigit(*t) && !isupper(*t) && *t!='$' && *t!='/')
  353. X      {
  354. X       wl_sj("S: Illegal dollar subtitution sequence in command line.");
  355. X       wl_sj("Legal sequences are $0..$9, $A..$Z, $/, and $$.");
  356. X       num_sev++;
  357. X       strcpy(p,t);
  358. X       return;
  359. X      }
  360. X
  361. X    /* A dollar escape is easy to process. */
  362. X    if (*t=='$') {*p++ = *t++; continue;}
  363. X
  364. X    /* A slash escape is easy to process. */
  365. X    if (*t=='/') {*p++ = FN_DELIM; t++; continue;}
  366. X
  367. X    /* Substitutions have to be expanded. */
  368. X    if (isdigit(*t) || isupper(*t))
  369. X      {
  370. X       ubyte num;
  371. X       char *q;
  372. X       if (isdigit(*t))
  373. X           num = *t-'0';
  374. X       else
  375. X           num = 10+(*t-'A');
  376. X       as_cold(num<NUM_SUBS,"dollsub: num is too bug!");
  377. X       q=subval[num];
  378. X       if ((p-p_comlin)+strlen(subval[num]) > COMLINE_MAX) goto toobig;
  379. X       while (*q!=EOS) *p++ = *q++;
  380. X       t++;
  381. X      }
  382. X   }
  383. X *p=EOS;
  384. X return;
  385. X
  386. X toobig:
  387. X    wl_sj("S: Expanded (i.e. after $1 etc) command line is too long.");
  388. X    num_sev++;
  389. X    strcpy(p,t);
  390. X    return;
  391. X}
  392. X
  393. X/******************************************************************************/
  394. X
  395. LOCAL void fwonerun P_((void));
  396. LOCAL void fwonerun()
  397. X/* Performs a single run of FunnelWeb proper, using the global variable       */
  398. X/* 'options' as the input command line.                                       */
  399. X{
  400. X fn_t lisnam;
  401. X
  402. X /* The following clocks record the time taken by various parts of FunnelWeb.  */
  403. X ck_t mappck;
  404. X ck_t scanck;
  405. X ck_t parsck;
  406. X ck_t analck;
  407. X ck_t dumpck;
  408. X ck_t lstrck;
  409. X ck_t tangck;
  410. X ck_t weavck;
  411. X ck_t totlck;
  412. X
  413. X /* Intialize/zero all the clocks. */
  414. X ck_ini(&mappck);
  415. X ck_ini(&scanck);
  416. X ck_ini(&parsck);
  417. X ck_ini(&analck);
  418. X ck_ini(&dumpck);
  419. X ck_ini(&lstrck);
  420. X ck_ini(&tangck);
  421. X ck_ini(&weavck);
  422. X ck_ini(&totlck);
  423. X
  424. X /* Start the total time clock ticking. A total time clock is used to gather  */
  425. X /* up the gaps between the invocations of the other clocks.                  */
  426. X ck_start(&totlck);
  427. X
  428. X ck_start(&lstrck);
  429. X
  430. X /* Establish the listing file output stream. */
  431. X strcpy(lisnam,"");             /* Start with an empty string.                */
  432. X fn_ins(lisnam,option.op_f_s);  /* Insert input file name.                    */
  433. X fn_ins(lisnam,".lis");         /* Insert constant extension.                 */
  434. X fn_ins(lisnam,option.op_l_s);  /* Insert command line spec.                  */
  435. X wf_ini(&f_l,option.op_l_b);    /* Initialize the stream.                     */
  436. X wf_ope(&f_l,lisnam);           /* Create the file.                           */
  437. X if (option.op_l_b && wf_err(&f_l))
  438. X   {
  439. X    sprintf(linet1,"S: Error creating listing file \"%s\".",lisnam);
  440. X    wl_sj(linet1);
  441. X    wl_sj("Aborting...");
  442. X    num_sev++;
  443. X    return;
  444. X   }
  445. X
  446. X wl_l("FUNNELWEB LISTING FILE");
  447. X wl_l("======================");
  448. X wl_l("");
  449. X
  450. X /* Initialize the lister for this run. */
  451. X lr_ini();
  452. X
  453. X ck_stop(&lstrck);
  454. X
  455. X /* Scanner comes first. */
  456. X ck_start(&scanck);
  457. X scanner(&mappck,&scanck);
  458. X ck_stop(&scanck);
  459. X
  460. X /* Dump the line and token lists if requested. The scanner supplies sensible */
  461. X /* lists even if it encounters errors, so there is no danger here.           */
  462. X ck_start(&dumpck);
  463. X if (option.op_b2_b) dm_lnls(&f_l);
  464. X if (option.op_b3_b) dm_tkls(&f_l);
  465. X ck_stop(&dumpck);
  466. X
  467. X /* Invoke the parser if there were no serious scanner errors. */
  468. X if (num_sev+num_err==0)
  469. X   {
  470. X    ck_start(&parsck);
  471. X    parser();
  472. X    ck_stop(&parsck);
  473. X    /* Only perform post parser dumps if the parser was run. */
  474. X    ck_start(&dumpck);
  475. X    if (option.op_b4_b) dm_matb(&f_l);
  476. X    if (option.op_b5_b) dm_dcls(&f_l);
  477. X    ck_stop(&dumpck);
  478. X   }
  479. X else
  480. X   {
  481. X    if (option.op_b4_b)
  482. X       wl_l("Macro table dump skipped (Parser was not invoked).");
  483. X    if (option.op_b5_b)
  484. X       wl_l("Document list dump skipped (Parser was not invoked).");
  485. X   }
  486. X
  487. X /* Invoke the macro structure analyser if still no errors. */
  488. X if (num_sev+num_err==0)
  489. X   {
  490. X    ck_start(&analck);
  491. X    analyse();
  492. X    ck_stop(&analck);
  493. X   }
  494. X
  495. X /* The scanner, parser, and analyser send errors to the lister package. */
  496. X /* Send sorted listing to the listing file (and screen if desired).     */
  497. X ck_start(&lstrck);
  498. X if (option.op_l_b) lr_gen(&f_l,option.op_c_i);
  499. X if (option.op_s_b) lr_gen(&f_s,option.op_s_i);
  500. X if (option.op_s_b) lr_gen(&f_j,option.op_s_i);
  501. X ck_stop(&lstrck);
  502. X
  503. X /* If the first stages went OK, invoke tangle and weave. */
  504. X if (num_sev+num_err==0)
  505. X   {
  506. X    if (option.op_o_b)
  507. X      {
  508. X       ck_start(&tangck);
  509. X       tangle();
  510. X       ck_stop(&tangck);
  511. X      }
  512. X    if (option.op_t_b)
  513. X      {
  514. X       ck_start(&weavck);
  515. X       weave();
  516. X       ck_stop(&weavck);
  517. X      }
  518. X    /* Leave output lines from Tangle and Weave joined, but separate them */
  519. X    /* from any further output.                                           */
  520. X    if (option.op_t_b || option.op_o_b)
  521. X       wl_sjl("");
  522. X   }
  523. X else
  524. X   {
  525. X    /* Otherwise tell the user that back-end phases will be skipped. */
  526. X    if ( option.op_o_b ||  option.op_t_b)
  527. X      {
  528. X       if (num_sev+num_err==1)
  529. X          wr_sjl("Error caused ");
  530. X       else
  531. X          wr_sjl("Errors caused ");
  532. X      }
  533. X    if ( option.op_o_b &&  option.op_t_b) wr_sjl("tangle and weave phases");
  534. X    if ( option.op_o_b && !option.op_t_b) wr_sjl("tangle phase");
  535. X    if (!option.op_o_b &&  option.op_t_b) wr_sjl("weave phase");
  536. X    if ( option.op_o_b ||  option.op_t_b)
  537. X       {wl_sjl(" to be skipped."); wl_sjl("");}
  538. X   }
  539. X
  540. X ck_stop(&totlck);
  541. X
  542. X /* If requested write out a summary of the time taken. */
  543. X if (option.op_b6_b)
  544. X    dm_times(&f_l,
  545. X             &mappck,&scanck,&parsck,&analck,
  546. X             &dumpck,&lstrck,&tangck,&weavck,&totlck);
  547. X
  548. X /* Write out a line summarizing the diagnostics for this run. */
  549. X errsum(0L,num_sev,num_err,num_war); wl_sjl(linet1);
  550. X
  551. X /* Close the listing file. */
  552. X if (!option.op_l_b) goto finishoff;
  553. X if (wf_err(&f_l))
  554. X   {
  555. X    wl_sj("S: Error writing to listing file. Aborting...");
  556. X    num_sev++;
  557. X    goto finishoff;
  558. X   }
  559. X wf_clo(&f_l);
  560. X if (wf_err(&f_l))
  561. X   {
  562. X    wl_sj("S: Error flushing and closing listing file. Aborting...");
  563. X    num_sev++;
  564. X   }
  565. X
  566. X finishoff:
  567. X /* VERY IMPORTANT: Ask the memory management package to free up all the      */
  568. X /* temporary memory (allocated using mm_temp) that has been allocated. This  */
  569. X /* ensures that the memory allocated for this FunnelWeb run will be recycled.*/
  570. X mm_zapt();
  571. X
  572. X} /* End of fwonerun */
  573. X
  574. X/******************************************************************************/
  575. X
  576. LOCAL void do_absen P_((void));
  577. LOCAL void do_absen ()
  578. X{
  579. X if (arg_num != 2)
  580. X   {
  581. X    wl_sj("S: The ABSENT command requires exactly one argument.");
  582. X    num_sev++;
  583. X    return;
  584. X   }
  585. X if (fexists(arg_arr[1]))
  586. X   {
  587. X    sprintf(linet1,"S: ABSENT found \"%s\".",arg_arr[1]);
  588. X    wl_sj(linet1);
  589. X    num_sev++;
  590. X    return;
  591. X   }
  592. X}
  593. X
  594. X/******************************************************************************/
  595. X
  596. LOCAL void do_cody P_((void));
  597. LOCAL void do_cody ()
  598. X/* The CODIFY command takes an input text file and generates an output text   */
  599. X/* file containing C code to write out the input text file. The need for this */
  600. X/* command springs from the weaver. Experience with FunnelWeb showed that use */
  601. X/* of a separate header file, to be included, while apparently sensible,      */
  602. X/* caused no end of problems. In particular, problems with portably           */
  603. X/* specifying where the header file should be found. In the end, it was       */
  604. X/* decided that it would be better to write the header file out with the      */
  605. X/* weave output. As the header file is quite long, it is best to automate the */
  606. X/* process of converting the file from text to C code to write out the text.  */
  607. X/* That is what the CODIFY command does.                                      */
  608. X{
  609. X FILE *file1;
  610. X FILE *file2;
  611. X
  612. X#define MAXHACK 1000
  613. X char hackline[MAXHACK+1];
  614. X uword lineno;
  615. X
  616. X if (arg_num != 3)
  617. X   {
  618. X    wl_sj("S: The CODIFY command requires exactly two arguments.");
  619. X    num_sev++;
  620. X    return;
  621. X   }
  622. X
  623. X /* Open the input file for text reading. */
  624. X file1=fopen(arg_arr[1],"r");
  625. X if (file1 == FOPEN_F)
  626. X   {
  627. X    wl_sj("S: Error opening the input file.");
  628. X    num_sev++;
  629. X    return;
  630. X   }
  631. X file2=fopen(arg_arr[2],"w");
  632. X if (file2 == FOPEN_F)
  633. X   {
  634. X    fclose(file1);
  635. X    wl_sj("S: Error creating the output file.");
  636. X    num_sev++;
  637. X    return;
  638. X   }
  639. X
  640. X lineno=0;
  641. X
  642. X /* PROCESS A SINGLE LINE PER ITERATION. */
  643. X while (TRUE)
  644. X   {
  645. X    uword linelength;
  646. X    uword i;
  647. X
  648. X    /* Read in a line of input and terminate loop if there are no more lines. */
  649. X    fgets(hackline,MAXHACK,file1);
  650. X    if (ferror(file1))
  651. X       {wl_sj("S: Error reading the input file.");num_sev++;return;}
  652. X    if (feof(file1)) break;
  653. X    lineno++;
  654. X
  655. X    /* Complain if the input line is too long. */
  656. X    if (strlen(hackline)>81)
  657. X      {
  658. X       sprintf(linet1,"Line %lu of input file is too long.",
  659. X               (ulong) strlen(hackline));
  660. X       wl_sj(linet1);
  661. X       wl_sj("The maximum allowable length is 80 characters.");
  662. X       num_sev++;
  663. X       return;
  664. X      }
  665. X
  666. X    /* Write the start-of-line string. */
  667. X    if (fputs(" WX(\"",file2) == FPUTS_F) goto write_failure;
  668. X
  669. X    /* Write out the line in sanitized form. */
  670. X    linelength=strlen(hackline);
  671. X    for (i=0;i<linelength;i++)
  672. X      {
  673. X       char ch = hackline[i];
  674. X       if (ch==EOL)
  675. X          ; /* Ignore this. */
  676. X       else
  677. X       if (ch=='\"')
  678. X          {if (fputs("\\\"",file2) == FPUTS_F) goto write_failure;}
  679. X       else
  680. X       if (ch=='\\')
  681. X          {if (fputs("\\\\",file2) == FPUTS_F) goto write_failure;}
  682. X       else
  683. X          {if (fputc(ch,file2) == FPUTC_F) goto write_failure;}
  684. X      }
  685. X
  686. X    /* Write the end of line string. */
  687. X    if (fputs("\");\n",file2) == FPUTS_F) goto write_failure;
  688. X   }
  689. X
  690. X if (fflush(file2) != FFLUSH_S)
  691. X    {wl_sj("S: Error flushing the output file.");num_sev++;return;}
  692. X if (fclose(file1) == FCLOSE_F)
  693. X   {wl_sj("S: Error closing the input file.");num_sev++;return;}
  694. X if (fclose(file2) == FCLOSE_F)
  695. X   {wl_sj("S: Error closing the output file.");num_sev++;return;}
  696. X return;
  697. X
  698. X write_failure:
  699. X    wl_sj("S: Error writing the output file.");num_sev++;return;
  700. X}
  701. X
  702. X/******************************************************************************/
  703. X
  704. LOCAL void do_comp P_((void));
  705. LOCAL void do_comp ()
  706. X/* The compare command should have two arguments being file names. It         */
  707. X/* compares the two files and raises a fatal error if they differ.            */
  708. X{
  709. X char *errstr;
  710. X bool result;
  711. X if (arg_num != 3)
  712. X   {
  713. X    wl_sj("S: COMPARE command must be given exactly two arguments.");
  714. X    num_sev++;
  715. X    return;
  716. X   }
  717. X errstr=eq_files(arg_arr[1],arg_arr[2],&result);
  718. X if (errstr!=NULL)
  719. X   {
  720. X    wl_sj("S: COMPARE command ran into a problem:");
  721. X    wl_sj(errstr);
  722. X    num_sev++;
  723. X    return;
  724. X   }
  725. X if (!result)
  726. X   {
  727. X    wl_sj("S: A comparison FAILED. Two files compared were not identical!");
  728. X    sprintf(linet1,"   File1: \"%s\".",arg_arr[1]); wl_sj(linet1);
  729. X    sprintf(linet1,"   File2: \"%s\".",arg_arr[2]); wl_sj(linet1);
  730. X    wl_sj("   FunnelWeb has just FAILED a regression test.");
  731. X    wl_sj("   You should now inspect the two files to see how the result of");
  732. X    wl_sj("   this run of FunnelWeb differed from the \"correct answer\" in");
  733. X    wl_sj("   the answer directory.");
  734. X    num_sev++;
  735. X    return;
  736. X   }
  737. X}
  738. X
  739. X/******************************************************************************/
  740. X
  741. LOCAL void do_defin P_((char *));
  742. LOCAL void do_defin (p_comlin)
  743. X/* The define command associates a string with a $n. */
  744. char *p_comlin;
  745. X{
  746. X char *p;
  747. X ubyte defnum;
  748. X
  749. X /* p is our scanning pointer and starts at the start of the command line. */
  750. X p=p_comlin;
  751. X
  752. X /* Skip past the DEFINE command onto the second argument. */
  753. X while ((*p!=' ') && (*p!=EOS)) p++;
  754. X while (*p==' ') p++;
  755. X
  756. X /* There should be a single digit or upper case letter there. */
  757. X if (!isdigit(*p) && !isupper(*p))
  758. X   {wl_sj("S: The first argument to DEFINE must be a decimal digit or");
  759. X    wl_sj("   upper case letter.");
  760. X    wl_sj("   Example: define 3 \"A Walrus in Spain is a Walrus in Vain.\"");
  761. X    num_sev++;return;}
  762. X if (isdigit(*p))
  763. X    defnum = *p-'0';
  764. X else
  765. X    defnum = 10+(*p-'A');
  766. X as_cold(defnum<NUM_SUBS,"do_defin: num is too bug!");
  767. X
  768. X /* Move past the digit. */
  769. X p++;
  770. X
  771. X /* Skip blanks to get to the next argument. */
  772. X while (*p==' ') p++;
  773. X
  774. X /* Complain if there is no second argument. */
  775. X if (*p==EOS)
  776. X   {wl_sj("S: The DEFINE command expected a second argument.");
  777. X    num_sev++; return;}
  778. X
  779. X /* Otherwise make sure that we have a double quoted string. */
  780. X if (*p!='"' || *(p+1)==EOS || p[strlen(p)-1]!='"')
  781. X   {wl_sj("S: Second argument to DEFINE must be in double quotes.");
  782. X    num_sev++;return;}
  783. X
  784. X /* All is checked. Now it is safe to copy over the string. */
  785. X p++;
  786. X strcpy(subval[defnum],p);
  787. X subval[defnum][strlen(subval[defnum])-1]=EOS;
  788. X
  789. X /* TRACE: All the definitions.
  790. X {
  791. X  ubyte i;
  792. X  for (i=0;i<NUM_SUB;i++)
  793. X     printf("$%u=\"%s\"\n",(unsigned) i,subval[i]);
  794. X }
  795. X */
  796. X}
  797. X
  798. X/******************************************************************************/
  799. X
  800. LOCAL void do_diff P_((void));
  801. X/* COMMAND FORMAT: diff file1 file2 logfile [abort]                           */
  802. X/* This function/command performs a proper text differences on two files.     */
  803. X/* This function is long and messy because of C's lack of nested functions.   */
  804. X/* I don't want to create global variables here, and defining subfunctions    */
  805. X/* that do not have access to the variables of this function would provoke    */
  806. X/* too wide an interface to be worth the trouble. So it's code city.          */
  807. X/* How I wish that I had FunnelWeb to help me write this function!            */
  808. LOCAL void do_diff()
  809. X{
  810. X bool   diffabort;        /* TRUE iff we should abort if files are different. */
  811. X bool   is_same  = FALSE; /* True iff files are proven to be the same.        */
  812. X FILE  *logfile;          /* File to write result of comparison.              */
  813. X bool   badwrite = FALSE; /* TRUE if we couldn't write to the logfile.        */
  814. X char  *p_file1;          /* Pointer to mapping of first  file to compare.    */
  815. X char  *p_file2;          /* Pointer to mapping of second file to compare.    */
  816. X ulong  len_file1;        /* Number of bytes in mapping of first  file.       */
  817. X ulong  len_file2;        /* Number of bytes in mapping of second file.       */
  818. X char  *mess1;            /* Error message from mapper for first  file.       */
  819. X char  *mess2;            /* Error message from mapper for second file.       */
  820. X bool  is_image;          /* TRUE iff mapped images are identical.            */
  821. X bool   anydiff = FALSE;  /* TRUE iff any differences detected during loop.   */
  822. X
  823. X /* Check that the number of arguments is correct. */
  824. X if (arg_num < 4 || arg_num > 5)
  825. X   {
  826. X    wl_sj("S: The DIFF command must be given either 3 or 4 arguments.");
  827. X    wl_sj("   Usage: diff f1 f2 logfile [abort]");
  828. X    num_sev++;
  829. X    return;
  830. X   }
  831. X
  832. X /* Check that the fourth argument, if present, is legal. */
  833. X diffabort=FALSE;
  834. X if (arg_num == 5)
  835. X    if (strcmp(arg_arr[4],"ABORT")==0 || strcmp(arg_arr[4],"abort")==0)
  836. X       diffabort=TRUE;
  837. X    else
  838. X      {
  839. X       wl_sj(
  840. X          "S: The DIFF command's fourth argument, if present, must be ABORT.");
  841. X       wl_sj("   Usage: diff f1 f2 logfile [abort]");
  842. X       num_sev++;
  843. X       return;
  844. X      }
  845. X
  846. X /* Now open the log file to append result of compare. */
  847. X logfile=fopen(arg_arr[3],"a");
  848. X if (logfile == FOPEN_F)
  849. X   {
  850. X    wl_sj("S: DIFF: Error opening the log file (to append result of compare).");
  851. X    num_sev++;
  852. X    return;
  853. X   }
  854. X
  855. X /* The following define simplifies writing to the log file. */
  856. X#define LOGLINE {if (fputs(linet1,logfile) == FPUTS_F) badwrite=TRUE;}
  857. X#define LOGSTR(STR) {if (fputs((STR),logfile) == FPUTS_F) badwrite=TRUE;}
  858. X#define LOGCHAR(CH) {if (fputc((CH),logfile) == FPUTC_F) badwrite=TRUE;}
  859. X
  860. X /* Write the header for this comparison to the log file. */
  861. X sprintf(linet1,"\n\n"                          ); LOGLINE;
  862. X sprintf(linet1,"Comparing \"%s\"\n" ,arg_arr[1]); LOGLINE;
  863. X sprintf(linet1,"     with \"%s\".\n",arg_arr[2]); LOGLINE;
  864. X
  865. X /* Now map in the two files to be compared.                                  */
  866. X /* Once this is done, we MUST do a mm_zapt later or memory will leak.        */
  867. X /* We attempt to map the second file, even if the first mapping has failed   */
  868. X /* as, if the first file is absent, there is a good chance that the second   */
  869. X /* is absent too, and it is useful to the user to know this.                 */
  870. X mess1=map_file(arg_arr[1],&p_file1,&len_file1);
  871. X mess2=map_file(arg_arr[2],&p_file2,&len_file2);
  872. X if (mess1 != NULL)
  873. X   {
  874. X    sprintf(linet1,"Error mapping \"%s\".\n",arg_arr[1]); LOGLINE;
  875. X    wr_sj("E: DIFF: "); wr_sj(linet1);
  876. X    sprintf(linet1,"         %s\n",mess1); LOGLINE; wr_sj(linet1);
  877. X    num_err++;
  878. X   }
  879. X if (mess2 != NULL)
  880. X   {
  881. X    sprintf(linet1,"Error mapping \"%s\".\n",arg_arr[2]); LOGLINE;
  882. X    wr_sj("E: DIFF: "); wr_sj(linet1);
  883. X    sprintf(linet1,"         %s\n",mess2); LOGLINE; wr_sj(linet1);
  884. X    num_err++;
  885. X   }
  886. X if ((mess1 != NULL) || (mess2 != NULL))
  887. X    goto frombadmap;
  888. X
  889. X /* At this point the two files to be compared are sitting in memory and we   */
  890. X /* have a ready-for-writing log file. We are now ready to compare.           */
  891. X
  892. X /* First perform a binary image comparison as a check for later.             */
  893. X /* We could do this later, but it is better to do this now, in case the      */
  894. X /* complicated comparison code somehow corrupts one of the images.           */
  895. X is_image= ((len_file1 == len_file2) &&
  896. X            (memcmp(p_file1,p_file2,(size_t) len_file1) == 0));
  897. X
  898. X /* This anonymous block performs the actual comparison. */
  899. X {
  900. X  /* The comparison is performed by scrolling the two input files through two */
  901. X  /* fixed-length line buffers (buf1 and buf2 - see below). To avoid copying, */
  902. X  /* the buffers are made circular. Processing takes place by comparing the   */
  903. X  /* first line of each buffer. If the line is the same, the buffers are      */
  904. X  /* scrolled by one line. If they are different, then we have encountered a  */
  905. X  /* DIFFERENCES SECTION and we have to compare lines near the top of the     */
  906. X  /* buffers to find a match. When a match is found, each buffer is scrolled  */
  907. X  /* down to its match point and processing continues.                        */
  908. X
  909. X/* LBUFSIZ is the number of number of lines that each buffer can hold.        */
  910. X/*         Lines are indexed from [0,LBUFSIZ-1].                              */
  911. X/*         WARNING: LBUFSIZ must be a power of two corresponding to WRAP(X).  */
  912. X/*         WARNING: Totally different input files will provoke O(LBUFSIZ^2)   */
  913. X/*                  checksum comparisons per LBUFSIZ input lines.             */
  914. X/* WRAP(X) is a macro that performs wraparound of buffer indices.             */
  915. X/* GAP     is the number of lines that have to match to end a diff section.   */
  916. X/* MAXDIST is the maximum "distance" that is tested when matching.            */
  917. X#define LBUFSIZ 64
  918. X#define WRAP(X) ((X) & 0x3F)
  919. X#define GAP     3
  920. X#define MAXDIST (LBUFSIZ-GAP)
  921. X
  922. X/* The following macro compares two lines in the buffers.                     */
  923. X/* The arguments are absolute buffer indices, not relative ones.              */
  924. X/* We assume that checksums will mismatch more often than line lengths.       */
  925. X#define COMPLINE(INDA,INDB) \
  926. X   ((buf1[INDA].c_line == buf2[INDB].c_line) &&  \
  927. X    (buf1[INDA].l_line == buf2[INDB].l_line) &&  \
  928. X    (memcmp(buf1[INDA].p_line,buf2[INDB].p_line, \
  929. X                (size_t) buf1[INDA].l_line)==0))
  930. X
  931. X  /* The two line buffers buf1 and buf2 (see below) don't actually store      */
  932. X  /* lines. Instead they store line structures which store pointers to the    */
  933. X  /* lines in the mapped images of the files. They also store the length of   */
  934. X  /* each line, and a checksum of the line. The checksum is useful for        */
  935. X  /* speeding up the comparisons between lines when processing a differences  */
  936. X  /* section.                                                                 */
  937. X  typedef
  938. X     struct
  939. X       {
  940. X        char *p_line; /* Pointer to first byte in the line. */
  941. X        ulong l_line; /* Number of bytes in the line.       */
  942. X        uword c_line; /* Checksum of the line.              */
  943. X       } line_t;
  944. X
  945. X  char   *p_next1 = p_file1;            /* Points to next line in file1.      */
  946. X  char   *p_next2 = p_file2;            /* Points to next line in file2.      */
  947. X  char   *p_post1 = p_file1+len_file1;  /* Byte following image of file1.     */
  948. X  char   *p_post2 = p_file2+len_file2;  /* Byte following image of file2.     */
  949. X  line_t buf1[LBUFSIZ];      /* Comparison buffer for first  file.            */
  950. X  line_t buf2[LBUFSIZ];      /* Comparison buffer for second file.            */
  951. X  ulong  buf1top = 0;        /* Index of first line in first  buffer.         */
  952. X  ulong  buf2top = 0;        /* Index of first line in second buffer.         */
  953. X  ulong  buf1fil = 0;        /* Number of lines in first  buffer.             */
  954. X  ulong  buf2fil = 0;        /* Number of lines in second buffer.             */
  955. X  ulong  topnum1 = 1;        /* Line number of first line of first buffer.    */
  956. X  ulong  topnum2 = 1;        /* Line number of first line of second buffer.   */
  957. X
  958. X  /* The following loop compares the line(s) at the top of the two buffers    */
  959. X  /* and processes (lines1,lines2) lines of each.                             */
  960. X  while (TRUE)
  961. X    {
  962. X     ulong lines1; /* Lines of file1 processed during this loop iteration.    */
  963. X     ulong lines2; /* Lines of file2 processed during this loop iteration.    */
  964. X     ulong d,g;    /* Used in comparison loops.                               */
  965. X
  966. X     /* The first thing we do is to fill each buffer as full as possible. At  */
  967. X     /* the end of the next two lumps of code, the only reason that a file's  */
  968. X     /* is not full is that we have reached the end of the file.              */
  969. X
  970. X     /* Fill the first buffer as full as possible. */
  971. X     while ((buf1fil < LBUFSIZ) && (p_next1 != p_post1))
  972. X       {
  973. X        ulong  ind   = WRAP(buf1top + buf1fil);
  974. X        ulong  len   = 0;
  975. X        uword  csum  = 0;
  976. X        char  *p_lin = p_next1;
  977. X        while (TRUE)
  978. X          {
  979. X           if (p_next1 == p_post1) break;
  980. X           len++;
  981. X           csum=(csum+*p_next1++) & 0xFFFF;
  982. X           if (*(p_next1-1) == EOL) break;
  983. X          }
  984. X        buf1[ind].p_line = p_lin;
  985. X        buf1[ind].l_line = len;
  986. X        buf1[ind].c_line = csum;
  987. X        buf1fil++;
  988. X       }
  989. X
  990. X     /* Fill the second buffer as full as possible. */
  991. X     while ((buf2fil < LBUFSIZ) && (p_next2 != p_post2))
  992. X       {
  993. X        ulong  ind   = WRAP(buf2top + buf2fil);
  994. X        ulong  len   = 0;
  995. X        uword  csum  = 0;
  996. X        char  *p_lin = p_next2;
  997. X        while (TRUE)
  998. X          {
  999. X           if (p_next2 == p_post2) break;
  1000. X           len++;
  1001. X           csum=(csum+*p_next2++) & 0xFFFF;
  1002. X           if (*(p_next2-1) == EOL) break;
  1003. X          }
  1004. X        buf2[ind].p_line = p_lin;
  1005. X        buf2[ind].l_line = len;
  1006. X        buf2[ind].c_line = csum;
  1007. X        buf2fil++;
  1008. X       }
  1009. X
  1010. X     /* If the buffers are empty then we must be at the end of each file. */
  1011. X     if (buf1fil==0 && buf2fil==0)
  1012. X        break;
  1013. X
  1014. X     /* Try to peel a pair of matching lines off the top of the buffer.       */
  1015. X     /* If we succeed, zip down to the end of the loop and flush them.        */
  1016. X     /* We can't integrate this code into the next part because the next part */
  1017. X     /* requires GAP matches, whereas here we require just one.               */
  1018. X     if ((buf1fil>0) && (buf2fil>0) && COMPLINE(buf1top,buf2top))
  1019. X       {lines1=lines2=1; goto flushlines;}
  1020. X
  1021. X     /* At this point, we know we have a differences section. */
  1022. X     anydiff=TRUE;
  1023. X
  1024. X     /* We now compare the top lines of the two buffers for a match. A match  */
  1025. X     /* is only considered to have been found if we match GAP consecutive     */
  1026. X     /* lines. The best match minimizes the DISTANCE which is the sum of the  */
  1027. X     /* offsets (lines1,lines2) (in lines) from the top of each buffer where  */
  1028. X     /* the match starts. Even better matches minimize abs(lines1-lines2) as  */
  1029. X     /* well. All these nested loops are to ensure that we search best first. */
  1030. X     for (d=1;d<=MAXDIST;d++)
  1031. X       {
  1032. X        /* Calculate half distance on the high side. */
  1033. X        long half = (d/2)+1;
  1034. X        long off;
  1035. X        long sign_v;
  1036. X        /* Explore up and down simultaneously from the halfway mark. */
  1037. X        for (off=0;off<=half;off++)
  1038. X           for (sign_v= -1;sign_v<2;sign_v+=2)
  1039. X             {
  1040. X              long x = half + sign_v*off;
  1041. X              /* The following test allows the above loops to be sloppy. */
  1042. X              if (0<=x && x<=d)
  1043. X                {
  1044. X                 lines1=x;
  1045. X                 lines2=d-lines1;
  1046. X                 /* We now know that we want to test at (lines1,lines2).  */
  1047. X                 /* So compare the GAP lines starting at those positions. */
  1048. X                 /* Note: lines1 and lines2, as well as being the number  */
  1049. X                 /* of lines processed, are also the offset to the first  */
  1050. X                 /* match line in our match gap.                          */
  1051. X                 for (g=0;g<GAP;g++)
  1052. X                   { /* Note: R for relative, A for absolute. */
  1053. X                    ulong t1r = lines1 + g;
  1054. X                    ulong t2r = lines2 + g;
  1055. X                    ulong t1a,t2a;
  1056. X
  1057. X                    /* If both files have run out at this point, it's a match!*/
  1058. X                    if ((t1r>=buf1fil) && (t2r>=buf2fil)) continue;
  1059. X
  1060. X                    /* If just one of the files has run out it's a mismatch.  */
  1061. X                    if ((t1r>=buf1fil) || (t2r>=buf2fil)) goto gapfail;
  1062. X
  1063. X                    /* We now know that we have two real lines. Compare them. */
  1064. X                    /* Variables are to avoid big nested macro expansions.    */
  1065. X                    t1a = WRAP(buf1top+t1r);
  1066. X                    t2a = WRAP(buf2top+t2r);
  1067. X                    if (!COMPLINE(t1a,t2a)) goto gapfail;
  1068. X                   }
  1069. X                 /* If we dropped out of the gap loop, we must have found     */
  1070. X                 /* GAP consecutive matches. So we can run off and write out  */
  1071. X                 /* the difference section.                                   */
  1072. X                 goto writediff;
  1073. X
  1074. X                 /* Here's where we jump if we found a mismatch during gap    */
  1075. X                 /* looping. All we do is try next pair of offsets.           */
  1076. X                 gapfail:;
  1077. X                } /* End sloppy if. */
  1078. X             } /* End for sign_v. */
  1079. X       } /* End for distance loop. */
  1080. X
  1081. X     /* If we got to here then we must have dropped out of the search loop    */
  1082. X     /* which means that there must have been no match at all between the     */
  1083. X     /* buffers. The only thing to do is to write out what we have as         */
  1084. X     /* a differences section.                                                */
  1085. X     lines1=buf1fil;
  1086. X     lines2=buf2fil;
  1087. X
  1088. X     /* Write out the differences section (lines1,lines2) to the log file. */
  1089. X     writediff:
  1090. X     { /* Begin writediff */
  1091. X      ulong i,j;
  1092. X      LOGSTR("\n");
  1093. X      LOGSTR("     +-----\n");
  1094. X      for (i=0;i<lines1;i++)
  1095. X        {
  1096. X         ulong nline = WRAP(buf1top+i);
  1097. X         sprintf(linet1,"%05lu|",(ulong) topnum1+i); LOGLINE;
  1098. X         for (j=0;j<buf1[nline].l_line;j++)
  1099. X            LOGCHAR(*(buf1[nline].p_line+j));
  1100. X        }
  1101. X      LOGSTR("     +-----\n");
  1102. X      for (i=0;i<lines2;i++)
  1103. X        {
  1104. X         ulong nline = WRAP(buf2top+i);
  1105. X         sprintf(linet1,"%05lu|",(ulong) topnum2+i); LOGLINE;
  1106. X         for (j=0;j<buf2[nline].l_line;j++)
  1107. X            LOGCHAR(*(buf2[nline].p_line+j));
  1108. X        }
  1109. X      LOGSTR("     +-----\n");
  1110. X     } /* End writediff. */
  1111. X
  1112. X     /* Flush from buffer however many lines we ended up processing. */
  1113. X     flushlines:
  1114. X     buf1top=WRAP(buf1top+lines1); topnum1+=lines1; buf1fil-=lines1;
  1115. X     buf2top=WRAP(buf2top+lines2); topnum2+=lines2; buf2fil-=lines2;
  1116. X
  1117. X    } /* End the while loop that runs through the two files. */
  1118. X } /* End of anonymous block for doing actual comparison. */
  1119. X
  1120. X /* The anydiff flag tells us if the loop found any difference sections. */
  1121. X is_same=!anydiff;
  1122. X
  1123. X /* Target position if we couldn't map in the input files earlier. */
  1124. X frombadmap:
  1125. X
  1126. X /* Release the memory allocated by the mapper for the input files. */
  1127. X /* Failure to do this will result in a memory leak!                */
  1128. X mm_zapt();
  1129. X
  1130. X /* If the two files are identical, tell the log file. */
  1131. X if (is_same)
  1132. X   LOGSTR("The two files are IDENTICAL.\n");
  1133. X
  1134. X /* Invalidate this test in the log file, if inconsistent (see later). */
  1135. X if (is_same != is_image)
  1136. X    LOGSTR("<<CONSISTENCY FAILURE: ABOVE COMPARISON INVALID>>\n");
  1137. X
  1138. X /* If we had problems with the log file at any stage, kick up a fuss now. */
  1139. X if (badwrite)
  1140. X   {wl_sj("S: DIFF: Error writing to log file."); num_sev++;}
  1141. X
  1142. X /* Close the log file. */
  1143. X if (fclose(logfile) == FCLOSE_F)
  1144. X   {wl_sj("S: DIFF: Error closing the log file."); num_sev++;}
  1145. X
  1146. X /* The above code is quite tricky and there is a chance that it contains     */
  1147. X /* bugs. So, as a safety check we compare the results from the binary memory */
  1148. X /* image comparison performed earlier and the more complicated text          */
  1149. X /* comparison above. If they differ, then it's time to go kaboom.            */
  1150. X if (is_image && !is_same)
  1151. X    as_bomb("do_diff: Image comparison succeeded, but text comparison failed.");
  1152. X if (!is_image && is_same)
  1153. X    as_bomb("do_diff: Image comparison failed, but text comparison succeeded.");
  1154. X
  1155. X /* If files are non-same and ABORT option is turned on, set severe status. */
  1156. X if (!is_same && diffabort)
  1157. X   {
  1158. X    wl_sj(
  1159. X     "S: DIFF: Files have not been proven identical, and ABORT option is on.");
  1160. X    num_sev++;
  1161. X   }
  1162. X
  1163. X /* Tell the console whether comparison succeeded. */
  1164. X if (is_same)
  1165. X    wl_sj("The two files are IDENTICAL.");
  1166. X else
  1167. X    wl_sj("The two files are DIFFERENT (see the differences file for the details).");
  1168. X
  1169. X /* Increment the difference summary counters. */
  1170. X difftotl++;
  1171. X if (is_same)
  1172. X    diffsucc++;
  1173. X}
  1174. X
  1175. X/******************************************************************************/
  1176. X
  1177. LOCAL void do_dsum P_((void));
  1178. LOCAL void do_dsum ()
  1179. X{
  1180. X sprintf(linet1,"Summary of Differences"); wl_sj(linet1);
  1181. X sprintf(linet1,"----------------------"); wl_sj(linet1);
  1182. X sprintf(linet1,"Identical = %lu.",(ulong)            diffsucc); wl_sj(linet1);
  1183. X sprintf(linet1,"Different = %lu.",(ulong) (difftotl-diffsucc)); wl_sj(linet1);
  1184. X sprintf(linet1,"Total     = %lu.",(ulong)            difftotl); wl_sj(linet1);
  1185. X}
  1186. X
  1187. X/******************************************************************************/
  1188. X
  1189. LOCAL void do_dzer P_((void));
  1190. LOCAL void do_dzer ()
  1191. X/* Zaps difference counters. */
  1192. X{
  1193. X difftotl = 0;
  1194. X diffsucc = 0;
  1195. X}
  1196. X
  1197. X/******************************************************************************/
  1198. X
  1199. LOCAL void do_eneo P_((void));
  1200. LOCAL void do_eneo ()
  1201. X{
  1202. X if (arg_num != 2)
  1203. X   {
  1204. X    wl_sj("S: The ENEO command must be given exactly one argument.");
  1205. X    num_sev++;
  1206. X    return;
  1207. X   }
  1208. X if (fexists(arg_arr[1]))
  1209. X    if (remove(arg_arr[1]) != REMOVE_S)
  1210. X      {
  1211. X       sprintf(linet1,"S: ENEO failed to delete \"%s\".",arg_arr[1]);
  1212. X       wl_sj(linet1);
  1213. X       num_sev++;
  1214. X       return;
  1215. X      }
  1216. X}
  1217. X
  1218. X/******************************************************************************/
  1219. X
  1220. LOCAL void do_exec P_((void));
  1221. LOCAL void do_exec ()
  1222. X{
  1223. X uword i;
  1224. X
  1225. X if (arg_num < 2)
  1226. X   {
  1227. X    wl_sj("S: The EXECUTE command requires at least one argument.");
  1228. X    num_sev++;
  1229. X    return;
  1230. X   }
  1231. X if (arg_num > 10)
  1232. X   {
  1233. X    wl_sj("S: The EXECUTE command can have at most nine arguments.");
  1234. X    num_sev++;
  1235. X    return;
  1236. X   }
  1237. X
  1238. X /* Zap all the numeric arguments. */
  1239. X for (i=0; i<10; i++)
  1240. X    subval[i][0]=EOS;
  1241. X
  1242. X /* Copy the arguments over to the $1 $2 etc substitution variables. */
  1243. X for (i=1;i<arg_num; i++)
  1244. X    strcpy(subval[i-1],arg_arr[i]);
  1245. X
  1246. X /* Run up a new FunnelWeb shell and interpret the file. */
  1247. X interstr(arg_arr[1]);
  1248. X}
  1249. X
  1250. X/******************************************************************************/
  1251. X
  1252. LOCAL void do_exist P_((void));
  1253. LOCAL void do_exist ()
  1254. X{
  1255. X if (arg_num != 2)
  1256. X   {
  1257. X    wl_sj("S: The EXISTS command requires exactly one argument.");
  1258. X    num_sev++;
  1259. X    return;
  1260. X   }
  1261. X if (!fexists(arg_arr[1]))
  1262. X   {
  1263. X    sprintf(linet1,"S: EXISTS failed to find \"%s\".",arg_arr[1]);
  1264. X    wl_sj(linet1);
  1265. X    num_sev++;
  1266. X    return;
  1267. X   }
  1268. X}
  1269. X
  1270. X/******************************************************************************/
  1271. X
  1272. LOCAL void do_fix P_((void));
  1273. LOCAL void do_fix ()
  1274. X/* When the test suite is moved from one machine to another, it is possible   */
  1275. X/* that at some stage it will be moved using a BINARY transfer rather than a  */
  1276. X/* text file transfer. The result is that the test files will contain lines   */
  1277. X/* terminated with a sequence of control characters that the local buffered   */
  1278. X/* IO library will not convert to '\n' upon reading in. The are a few         */
  1279. X/* solutions to this problem, but one of the most direct is to have a command */
  1280. X/* such as this one that can convert the file over.                           */
  1281. X/*                                                                            */
  1282. X/* Once we have identified an end of line, it is easy to write it out as we   */
  1283. X/* can just send a '\n' and the local buffered IO library will write the      */
  1284. X/* right codes for us. The tricky part is deciding what an EOL is in the      */
  1285. X/* input stream. Well, I could have made the control characters for the       */
  1286. X/* remote EOL a parameter of this command, but instead I decided to use a     */
  1287. X/* simple algorithm that should cover nearly all cases...                     */
  1288. X/* ALGORITHM: Parse the input into alternating blocks of control characters   */
  1289. X/* and non-control characters. Parse each block of control characters into    */
  1290. X/* subblocks by parsing it from left to right and starting a new subblock the */
  1291. X/* moment a character of the subblock currently being parsed appears again.   */
  1292. X/* This covers at least the following cases, and probably many more:          */
  1293. X/*    UNIX         LF                                                         */
  1294. X/*    MSDOS     CR LF                                                         */
  1295. X/*    Macintosh CR                                                            */
  1296. X{
  1297. X /* Erk! But I found out the hard way, it doesn't work on a VAX!              */
  1298. X /* I'm making this do nothing on a VAX rather than generate an error as I    */
  1299. X /* want the scripts to work silently on the VAX without modification.        */
  1300. X#if !VMS
  1301. X FILE *infile;     /* File variable for input file.                           */
  1302. X FILE *tmpfile;    /* File variable for temporary output file.                */
  1303. X char *p_target;   /* Name of final (target) output file.                     */
  1304. X STAVAR char *p_temp=NULL;   /* Name of temporary output file.                */
  1305. X
  1306. X bool  seen[256];  /* TRUE if char is in current control sequence.            */
  1307. X char  undo[256];  /* undo[0..length-1] contains current control sequence.    */
  1308. X uword length;     /* Number of control bytes in our buffer.                  */
  1309. X uword i;
  1310. X int status;
  1311. X
  1312. X /* Allocate the temporary file name if not already allocated. */
  1313. X if (p_temp==NULL) p_temp=(p_fn_t) mm_perm(sizeof(fn_t));
  1314. X
  1315. X /* We can take one or two arguments. One argument means that we should       */
  1316. X /* fix up the input file, leaving the result in the input file.              */
  1317. X if (arg_num != 2 && arg_num != 3)
  1318. X   {
  1319. X    wl_sj("S: The FIXEOLS command requires one or two filename arguments.");
  1320. X    num_sev++;
  1321. X    return;
  1322. X   }
  1323. X
  1324. X /* Change to two arguments if the input name is the same as output name. */
  1325. X if (arg_num==3 && strcmp(arg_arr[1],arg_arr[2])==0) arg_num=2;
  1326. X
  1327. X /* Work out what the target name is going to be. */
  1328. X p_target=(arg_num==2) ? arg_arr[1] : arg_arr[2];
  1329. X
  1330. X /* Generate a temporary filename for the output file. This is tricky because */
  1331. X /* on many machines, one cannot rename across devices or directories. This   */
  1332. X /* means that the temporary file has to be created in the same directory as  */
  1333. X /* the file that we are going to rename it to later (the target file).       */
  1334. X strcpy(p_temp,p_target);
  1335. X fn_ins(p_temp,fn_temp());
  1336. X
  1337. X /* Open the input file for BINARY reading. */
  1338. X infile=fopen(arg_arr[1],"rb");
  1339. X if (infile == FOPEN_F)
  1340. X   {
  1341. X    sprintf(linet1,"S: FIXEOLS: Error opening \"%s\".",arg_arr[1]);
  1342. X    wl_sj(linet1);
  1343. X    num_sev++;
  1344. X    return;
  1345. X   }
  1346. X
  1347. X /* Create the output file for TEXT writing. */
  1348. X tmpfile=fopen(p_temp,"w");
  1349. X if (tmpfile == FOPEN_F)
  1350. X   {
  1351. X    fclose(infile);
  1352. X    wl_sj("S: FIXEOLS: Error creating the temporary output file.");
  1353. X    num_sev++;
  1354. X    return;
  1355. X   }
  1356. X
  1357. X /* Initialialize the control character buffer to empty. */
  1358. X for (i=0;i<256;i++) seen[i]=FALSE;
  1359. X length=0;
  1360. X
  1361. X while (TRUE)
  1362. X   {
  1363. X    /* Read in the next character from the input file. */
  1364. X    char ch=getc(infile);
  1365. X    if (ferror(infile))
  1366. X       {wl_sj("S: FIXEOLS: Error reading the input file.");num_sev++;return;}
  1367. X    if (feof(infile)) break;
  1368. X
  1369. X    /* Does character terminate a non-empty buffer? If so, flush it. */
  1370. X    if (length>0 && (isascprn(ch) || seen[ch]))
  1371. X      {
  1372. X       if (fputc(EOL,tmpfile) == FPUTC_F) goto write_failure;
  1373. X       for (i=0;i<length;i++) seen[undo[i]]=FALSE;
  1374. X       length=0;
  1375. X      }
  1376. X
  1377. X    /* Now we can approach the character cleanly and freshly. */
  1378. X    if (isascprn(ch))
  1379. X       {if (fputc(ch,tmpfile) == FPUTC_F) goto write_failure;}
  1380. X    else
  1381. X       {undo[length++]=ch; seen[ch]=TRUE;}
  1382. X   }
  1383. X if (length>0)
  1384. X   {if (fputc(EOL,tmpfile) == FPUTC_F) goto write_failure;}
  1385. X
  1386. X if (fflush(tmpfile) != FFLUSH_S)
  1387. X    {wl_sj("S: FIXEOLS: Error flushing the temporary output file.");num_sev++;return;}
  1388. X if (fclose(infile) == FCLOSE_F)
  1389. X   {wl_sj("S: FIXEOLS: Error closing the input file.");num_sev++;return;}
  1390. X if (fclose(tmpfile) == FCLOSE_F)
  1391. X   {wl_sj("S: FIXEOLS: Error closing the temporary output file.");num_sev++;return;}
  1392. X
  1393. X /* If renaming to the input file, we have to delete the input file first. */
  1394. X if (arg_num==2)
  1395. X   {
  1396. X    status=remove(arg_arr[1]);
  1397. X    if (status != REMOVE_S)
  1398. X      {
  1399. X       wl_sj("S: FIXEOLS: Error deleting existing input file to replace it");
  1400. X       wl_sj("            with the temporary output file. Deleting temporary and aborting...");
  1401. X       remove(p_temp);
  1402. X       num_sev++;
  1403. X       return;
  1404. X      }
  1405. X   }
  1406. X
  1407. X /* Rename the temporary file to the target output file. */
  1408. X status=rename(p_temp,p_target);
  1409. X
  1410. X /* Do the error checking. */
  1411. X if (status != RENAME_S)
  1412. X   {
  1413. X    wl_sjl("S: FIXEOLS: Error renaming temporary output file to output file.");
  1414. X    sprintf(linet1,"Temporary file name was \"%s\".",p_temp);
  1415. X    wl_sjl(linet1);
  1416. X    sprintf(linet1,"Output    file name was \"%s\".",p_target);
  1417. X    wl_sjl(linet1);
  1418. X    wl_sjl("FunnelWeb will leave both files intact so you can look at them.");
  1419. X    num_sev++;
  1420. X    return;
  1421. X   }
  1422. X
  1423. X return;
  1424. X
  1425. X write_failure:
  1426. X    wl_sj("S: FIXEOLS: Error writing the output file.");num_sev++;return;
  1427. X#endif
  1428. X}
  1429. X
  1430. X/******************************************************************************/
  1431. X
  1432. LOCAL void do_fweb P_((char *));
  1433. LOCAL void do_fweb(p_cl)
  1434. X/* This function performs a single run of FunnelWeb proper. */
  1435. char *p_cl;
  1436. X{
  1437. X op_t saveop;
  1438. X
  1439. X /* Do set can do all the work for us. However, it operates on p_defopt so we */
  1440. X /* have to do some juggling.                                                 */
  1441. X ASSIGN(saveop,*p_defopt);
  1442. X do_set(p_cl);
  1443. X ASSIGN(option,*p_defopt);
  1444. X ASSIGN(*p_defopt,saveop);
  1445. X if (num_sev>0) return;
  1446. X
  1447. X /* do_set ensures that the user hasn't specified any action parameters such  */
  1448. X /* as +X and +K, but it necessarily doesn't check to make sure that the user */
  1449. X /* has actually specified an input file!                                     */
  1450. X if (!option.op_f_b)
  1451. X   {
  1452. X    wl_sj("S: No input file specified in FW command.");
  1453. X    num_sev++;
  1454. X    return;
  1455. X   }
  1456. X fwonerun();
  1457. X}
  1458. X
  1459. X/******************************************************************************/
  1460. X
  1461. LOCAL void do_help P_((void));
  1462. LOCAL void do_help()
  1463. X{
  1464. X uword messno;
  1465. X
  1466. X if (arg_num == 1)
  1467. X   {
  1468. X    hel_wri(wr_sj,HL_MEN);
  1469. X    return;
  1470. X   }
  1471. X if (arg_num > 2)
  1472. X   {
  1473. X    wl_sj("S: The HELP command takes at most one argument.");
  1474. X    num_sev++;
  1475. X    return;
  1476. X   }
  1477. X
  1478. X /* Translate message name to number. */
  1479. X
  1480. X messno=hel_num(arg_arr[1]);
  1481. X if (messno == HL_ERR)
  1482. X   {
  1483. X    wl_sj(
  1484. X   "S: Unrecognised help message name. Try just \"help\" for a list of names.");
  1485. X    num_sev++;
  1486. X    return;
  1487. X   }
  1488. X
  1489. X hel_wri(wr_sj,messno);
  1490. X}
  1491. X
  1492. X/******************************************************************************/
  1493. X
  1494. LOCAL void do_set(p_comlin)
  1495. X/* The SET command allows the user to specify default FunnelWeb options.      */
  1496. char *p_comlin;
  1497. X{
  1498. X op_t tmpopt;
  1499. X
  1500. X /* Experiment with temporary options, not the real thing. */
  1501. X ASSIGN(tmpopt,*p_defopt);
  1502. X
  1503. X /* Now execute the effect of the command line on 'p_defopt'. */
  1504. X if (!op_add(&tmpopt,p_comlin,wr_sj))
  1505. X   {
  1506. X    wl_s("This is a severe error (S). Aborting to FunnelWeb shell...");
  1507. X    num_sev++;
  1508. X    return;
  1509. X   }
  1510. X
  1511. X /* Now make sure that the user didn't specify an option to do with the       */
  1512. X /* entire FunnelWeb run and not just this invocation of FunnelWeb proper.    */
  1513. X if (tmpopt.op_j_b)
  1514. X   {
  1515. X    wl_s("S: You cannot invoke FunnelWeb with +J from the FunnelWeb shell.");
  1516. X    wl_s("   To create a journal file, exit FunnelWeb and reinvoke with \"fw +j\".");
  1517. X    wl_s("This is a severe error. Aborting to FunnelWeb shell...");
  1518. X    num_sev++;
  1519. X    return;
  1520. X   }
  1521. X if (tmpopt.op_x_b)
  1522. X   {
  1523. X    wl_s("S: You cannot invoke FunnelWeb with +X from the FunnelWeb shell.");
  1524. X    wl_s("Use the interactive command EXECUTE instead.");
  1525. X    wl_s("This is a severe error. Aborting to FunnelWeb shell...");
  1526. X    num_sev++;
  1527. X    return;
  1528. X   }
  1529. X if (tmpopt.op_k_b)
  1530. X   {
  1531. X    wl_s("S: You cannot invoke FunnelWeb with +K from the FunnelWeb shell.");
  1532. X    wl_s("This is a severe error. Aborting to FunnelWeb shell...");
  1533. X    num_sev++;
  1534. X    return;
  1535. X   }
  1536. X if (tmpopt.op_h_b)
  1537. X   {
  1538. X    wl_s("S: You cannot invoke FunnelWeb with +H from the FunnelWeb shell.");
  1539. X    wl_s("Use the interactive command HELP instead.");
  1540. X    wl_s("This is a severe error. Aborting to FunnelWeb shell...");
  1541. X    num_sev++;
  1542. X    return;
  1543. X   }
  1544. X
  1545. X /* If we get to here, the options must be OK so we can set them as default. */
  1546. X ASSIGN(*p_defopt,tmpopt);
  1547. X}
  1548. X
  1549. X/******************************************************************************/
  1550. X
  1551. LOCAL void do_show P_((void));
  1552. LOCAL void do_show()
  1553. X/* The SHOW command writes out the current options. */
  1554. X{
  1555. X if (arg_num != 1)
  1556. X   {
  1557. X    wl_sj("S: The SHOW command does not take arguments.");
  1558. X    num_sev++;
  1559. X    return;
  1560. X   }
  1561. X wl_sj("Here are the FunnelWeb command line options that are");
  1562. X wl_sj("current in this FunnelWeb session:");
  1563. X op_wri(p_defopt,wr_sj);
  1564. X}
  1565. X
  1566. X/******************************************************************************/
  1567. X
  1568. LOCAL void do_stat P_((void));
  1569. LOCAL void do_stat ()
  1570. X/* The status command checks the number of diagnostics generated by the run.  */
  1571. X{
  1572. X uword i;
  1573. X char *thing;
  1574. X ulong cnum;
  1575. X
  1576. X if (arg_num<1 || arg_num>4)
  1577. X   {
  1578. X    wl_sj("S: The STATUS command requires zero to three arguments.");
  1579. X    num_sev++;
  1580. X    return;
  1581. X   }
  1582. X
  1583. X /* Zero arguments just means write out status. */
  1584. X if (arg_num == 1)
  1585. X   {
  1586. X    sprintf(linet1,"Last command: Severes=%lu, Errors=%lu, Warnings=%lu.",
  1587. X            (ulong) old_sev, (ulong) old_err, (ulong) old_war);
  1588. X    wl_sj(linet1);
  1589. X    sprintf(linet1,"Totals      : Severes=%lu, Errors=%lu, Warnings=%lu.",
  1590. X            (ulong) sum_sev, (ulong) sum_err, (ulong) sum_war);
  1591. X    wl_sj(linet1);
  1592. X    return;
  1593. X   }
  1594. X
  1595. X /* More than one argument means CHECK status. */
  1596. X for (i=1;i<arg_num;i++)
  1597. X   {
  1598. X    char ch=toupper(arg_arr[i][0]);
  1599. X    unsigned num;
  1600. X    if (ch!='W' && ch!='E' && ch!='S')
  1601. X      {
  1602. X       sprintf(linet1,
  1603. X              "S: Argument %u of STATUS command has bad letter.",
  1604. X               (unsigned) i);
  1605. X       wl_sj(linet1);
  1606. X       wl_sj("Arguments must be of the form ('W'|'E'|'S')<decimalnumber>.");
  1607. X       num_sev++;
  1608. X       return;
  1609. X      }
  1610. X    if (sscanf(&arg_arr[i][1],"%u",&num) != 1)
  1611. X      {
  1612. X       sprintf(linet1,
  1613. X      "S: Argument %u of STATUS command has bad number.",
  1614. X               (unsigned) i);
  1615. X       wl_sj(linet1);
  1616. X       wl_sj("   Arguments must be of the form ('W'|'E'|'S')<decimalnumber>.");
  1617. X       num_sev++;
  1618. X       return;
  1619. X      }
  1620. X    switch(ch)
  1621. X      {
  1622. X       case 'W': cnum=old_war; thing="warnings"; break;
  1623. X       case 'E': cnum=old_err; thing="errors"  ; break;
  1624. X       case 'S': cnum=old_sev; thing="severes" ; break;
  1625. X       default : as_bomb("do_stat: case defaulted.");
  1626. X      }
  1627. X    if (cnum != num)
  1628. X      {
  1629. X       sprintf(linet1,
  1630. X       "S: STATUS command detected wrong number of %s.",thing);
  1631. X       wl_sj(linet1);
  1632. X       sprintf(linet1, "Specifed %s=%u, Actual %s=%u.",
  1633. X               thing,(unsigned) num,thing,(unsigned) cnum);
  1634. X       wl_sj(linet1);
  1635. X       num_sev++;
  1636. X      }
  1637. X   }
  1638. X}
  1639. X
  1640. X/******************************************************************************/
  1641. X
  1642. X
  1643. LOCAL void do_trace P_((void));
  1644. LOCAL void do_trace()
  1645. X{
  1646. X if (arg_num != 2) goto help;
  1647. X strupper(arg_arr[1]);
  1648. X if (strcmp(arg_arr[1],"OFF") == 0) {tracing=FALSE; return;}
  1649. X if (strcmp(arg_arr[1], "ON") == 0) {tracing=TRUE ; return;}
  1650. X
  1651. X help:
  1652. X    wl_sj("S: The TRACE command has two forms:");
  1653. X    wl_sj("      TRACE OFF");
  1654. X    wl_sj("      TRACE ON");
  1655. X    num_sev++;
  1656. X    return;
  1657. X}
  1658. X
  1659. X/******************************************************************************/
  1660. X
  1661. LOCAL void do_write P_((char *));
  1662. LOCAL void do_write(p)
  1663. char *p;
  1664. X{
  1665. X uword len;
  1666. X
  1667. X /* Skip over the main command and the following blanks. */
  1668. X while (*p!=' ' && *p!=EOS) p++;
  1669. X while (*p==' ') p++;
  1670. X
  1671. X /* Now make sure that the remaining string is delimited by double quotes.    */
  1672. X len=strlen(p);
  1673. X if ((*p != '\"') || (p[len-1] != '\"') || len<2)
  1674. X   {
  1675. X    wl_sj("W: The argument to WRITE should be delimited by double quotes.");
  1676. X    wl_sj(p);
  1677. X    num_war++;
  1678. X    return;
  1679. X   }
  1680. X
  1681. X /* Now temporarily hack out the quotes and write out the string. */
  1682. X p[len-1]=EOS;
  1683. X wl_sj(p+1);
  1684. X p[len-1]='\"';
  1685. X}
  1686. X
  1687. X/******************************************************************************/
  1688. X
  1689. LOCAL void do_writu P_((char *));
  1690. LOCAL void do_writu(p)
  1691. char *p;
  1692. X{
  1693. X uword len;
  1694. X
  1695. X /* Skip over the main command and the following blanks. */
  1696. X while (*p!=' ' && *p!=EOS) p++;
  1697. X while (*p==' ') p++;
  1698. X
  1699. X /* Now make sure that the remaining string is delimited by double quotes.    */
  1700. X len=strlen(p);
  1701. X if ((*p != '\"') || (p[len-1] != '\"') || len<2)
  1702. X   {
  1703. X    wl_sj("W: The argument to WRITEU should be delimited by double quotes.");
  1704. X    wl_sj(p);
  1705. X    num_war++;
  1706. X    return;
  1707. X   }
  1708. X
  1709. X /* Now temporarily hack out the quotes and write out the string. */
  1710. X p[len-1]=EOS;
  1711. X wl_sj(p+1);
  1712. X p[len-1]='\"';
  1713. X
  1714. X /* Now write out another line underlining the above. */
  1715. X {uword i; for (i=0;i<len-2;i++) wr_sj("-"); wl_sj("");}
  1716. X}
  1717. X
  1718. X/******************************************************************************/
  1719. X
  1720. LOCAL bool do_command P_((char *));
  1721. LOCAL bool do_command(p_command)
  1722. X/* Execute a single FunnelWeb shell command. */
  1723. char *p_command;
  1724. X{
  1725. X char *v;
  1726. X bool result=FALSE;
  1727. X
  1728. X zerdia();
  1729. X
  1730. X /* Check the command for non-printables. */
  1731. X {
  1732. X  char *s=p_command;
  1733. X  while (*s!=EOS)
  1734. X    {
  1735. X     if (!isascprn(*s))
  1736. X       {
  1737. X        sprintf(linet1,
  1738. X                "S: Command line has non-printable at column %u.",
  1739. X                (unsigned) (s-p_command)+1);
  1740. X        wl_sj(linet1);
  1741. X        num_sev++;
  1742. X        goto finished;
  1743. X       }
  1744. X     s++;
  1745. X    }
  1746. X }
  1747. X
  1748. X /* Perform substitutions. */
  1749. X dollsub(p_command); if (num_sev>0) goto finished;
  1750. X
  1751. X /* Ignore commands consisting entirely of blanks (or empty commands). */
  1752. X {
  1753. X  char *s=p_command;
  1754. X  while (*s==' ') s++;
  1755. X  if (*s==EOS) goto finished;
  1756. X }
  1757. X
  1758. X /* Reject command lines beginning with a blank. */
  1759. X if (p_command[0]==' ')
  1760. X   {
  1761. X    wl_sj("S: Leading blanks are not allowed in command lines.");
  1762. X    num_sev++;
  1763. X    goto finished;
  1764. X   }
  1765. X
  1766. X /* Ignore command lines commencing with the comment character. */
  1767. X if (p_command[0]=='!')
  1768. X    {restdia(); goto finished;}
  1769. X
  1770. X /* Parse the command line into arguments. */
  1771. X explode(p_command);
  1772. X
  1773. X /* Complain if there is no command verb. */
  1774. X as_cold(arg_num>0,"do_command: zero arguments!");
  1775. X
  1776. X /* It's convenient to have v pointing to verb. */
  1777. X v=arg_arr[0];
  1778. X
  1779. X /* Convert the verb to upper case. */
  1780. X strupper(v);
  1781. X
  1782. X
  1783. X /* Execute the verb. */
  1784. X
  1785. X if (strcmp(v,"HERE")==0) skipping=FALSE;
  1786. X else if (!skipping)
  1787. X      if (strcmp(v,"ABSENT"     )==0) do_absen();
  1788. X else if (strcmp(v,"CODIFY"     )==0) do_cody ();
  1789. X else if (strcmp(v,"COMPARE"    )==0) do_comp ();
  1790. X else if (strcmp(v,"DEFINE"     )==0) do_defin(&p_command[0]);
  1791. X else if (strcmp(v,"DIFF"       )==0) do_diff();
  1792. X else if (strcmp(v,"DIFFSUMMARY")==0) do_dsum();
  1793. X else if (strcmp(v,"DIFFZERO"   )==0) do_dzer();
  1794. X else if (strcmp(v,"ENEO"       )==0) do_eneo ();
  1795. X else if (strcmp(v,"EXECUTE"    )==0) do_exec ();
  1796. X else if (strcmp(v,"EXISTS"     )==0) do_exist();
  1797. X else if (strcmp(v,"FIXEOLS"    )==0) do_fix  ();
  1798. X else if (strcmp(v,"FW"         )==0) do_fweb (&p_command[0]);
  1799. X else if (strcmp(v,"HELP"       )==0) do_help ();
  1800. X else if (strcmp(v,"QUIT"       )==0) result=TRUE;
  1801. X else if (strcmp(v,"SET"        )==0) do_set  (&p_command[0]);
  1802. X else if (strcmp(v,"SHOW"       )==0) do_show ();
  1803. X else if (strcmp(v,"SKIPTO"     )==0) skipping=TRUE;
  1804. X else if (strcmp(v,"STATUS"     )==0) do_stat ();
  1805. X else if (strcmp(v,"TOLERATE"   )==0) noabort=TRUE;
  1806. X else if (strcmp(v,"TRACE"      )==0) do_trace();
  1807. X else if (strcmp(v,"WRITE"      )==0) do_write(&p_command[0]);
  1808. X else if (strcmp(v,"WRITEU"     )==0) do_writu(&p_command[0]);
  1809. X else
  1810. X   {
  1811. X    /* The following trace is likely to confuse beginners.        */
  1812. X    /* sprintf(linet1,"Expanded command line=\"%s\".",p_command); */
  1813. X    /* wl_sj(linet1);                                             */
  1814. X    wl_sj("S: Unknown command. Type HELP for a list of commands.");
  1815. X    num_sev++;
  1816. X    goto finished;
  1817. X   }
  1818. X
  1819. X finished:
  1820. X sumdia();
  1821. X return result;
  1822. X}
  1823. X
  1824. X/******************************************************************************/
  1825. X
  1826. LOCAL void interpret P_((FILE *,char *));
  1827. LOCAL void interpret(p_file,filnam)
  1828. X/* p_file must be a file opened for reading. The file's name must be supplied */
  1829. X/* in filnam for error reporting reasons. The function reads each line from   */
  1830. X/* the file and feeds it to the FunnelWeb interpreter command executer.       */
  1831. XFILE *p_file;
  1832. char *filnam;
  1833. X{
  1834. X ulong lineno=0;
  1835. X char *result;
  1836. X bool b;
  1837. X cl_t  comline;
  1838. X char *p_comline;
  1839. X
  1840. X p_comline = &comline[0];
  1841. X
  1842. X while (TRUE)
  1843. X   {
  1844. X    bool oldnoabort = noabort;
  1845. X    noabort=FALSE;
  1846. X
  1847. X    if (p_file == stdin || tracing)
  1848. X       wr_sj("FunnelWeb>");
  1849. X
  1850. X    result=fgets(p_comline,(int) COMLINE_MAX,p_file);
  1851. X    if (feof(p_file))
  1852. X      {
  1853. X       sprintf(linet1,"<End of Script File \"%s\">",filnam);
  1854. X      if (p_file == stdin || tracing)
  1855. X          wl_sj(linet1);
  1856. X       break;
  1857. X      }
  1858. X    if (ferror(p_file) || (result == FGETS_FE))
  1859. X      {
  1860. X       sprintf(linet1,"F: Error reading command file \"%s\".",filnam);
  1861. X       wl_sj(linet1);
  1862. X       wl_sj("Aborting...");
  1863. X       sum_fat++;
  1864. X       return;
  1865. X      }
  1866. X    if (p_file == stdin || tracing) wr_j(p_comline);
  1867. X    if (p_file != stdin && tracing) wr_s(p_comline);
  1868. X
  1869. X    lineno++;
  1870. X    if (strlen(p_comline)==COMLINE_MAX)
  1871. X      {
  1872. X       sprintf(linet1,"F: Line %lu of command file \"%s\" is too long.",
  1873. X                      (unsigned long) lineno,filnam);
  1874. X       wl_sj(linet1);
  1875. X       wl_sj("Aborting...");
  1876. X       sum_fat++;
  1877. X       return;
  1878. X      }
  1879. X    as_cold(p_comline[strlen(p_comline)-1]==EOL,"interpret: NO NEWLINE!");
  1880. X    p_comline[strlen(p_comline)-1]=EOS;
  1881. X    as_cold(strlen(p_comline)<COMLINE_MAX,"interpret: Filename too long.");
  1882. X
  1883. X    b=do_command(p_comline);
  1884. X    if (b) break;
  1885. X
  1886. X    if (sum_fat>0) break;
  1887. X    if ((p_file != stdin) && (num_sev+num_err>0) && !oldnoabort)
  1888. X      {
  1889. X       wl_sj("Error caused termination of FunnelWeb shellscript.");
  1890. X       break;
  1891. X      }
  1892. X   }
  1893. X}
  1894. X
  1895. X/******************************************************************************/
  1896. X
  1897. LOCAL void interstr(filnam)
  1898. X/* The 'interpret' function (above) interprets each line of a file already    */
  1899. X/* opened for reading. This function does a little more, opening and closing  */
  1900. X/* the file before and after calling 'interpret'.                             */
  1901. char *filnam;
  1902. X{
  1903. X FILE *p_file;
  1904. X fn_t fn;
  1905. X
  1906. X /* Set up a default of ".fws" as a file extension. */
  1907. X strcpy(&fn[0],".fws");
  1908. X
  1909. X /* Inherit the actual filename. */
  1910. X as_cold(strlen(filnam)<=FILENAME_MAX,"interstr: Filename blasted.");
  1911. X fn_ins(&fn[0],filnam);
  1912. X
  1913. X p_file=fopen(fn,"r");
  1914. X if (p_file == FOPEN_F)
  1915. X   {
  1916. X    sprintf(linet1,"S: Error opening command file \"%s\".",fn);
  1917. X    wl_sj(linet1);
  1918. X    sum_sev++;
  1919. X    return;
  1920. X   }
  1921. X
  1922. X interpret(p_file,&fn[0]); if (sum_fat>0) return;
  1923. X
  1924. X if (fclose(p_file) == FCLOSE_F)
  1925. X   {
  1926. X    sprintf(linet1,"F: Error closing command file \"%s\".",fn);
  1927. X    wl_sj(linet1);
  1928. X    wl_sj("Aborting...");
  1929. X    sum_fat++;
  1930. X    return;
  1931. X   }
  1932. X}
  1933. X
  1934. X/******************************************************************************/
  1935. X
  1936. LOCAL void chk_cline P_((void));
  1937. LOCAL void chk_cline()
  1938. X/* Checks to make sure that the command line specifies exactly one action.    */
  1939. X{
  1940. X uword countopt=0;
  1941. X
  1942. X /* Count the number of active action options are turned on. */
  1943. X if (p_comopt->op_f_b) countopt++;
  1944. X if (p_comopt->op_x_b) countopt++;
  1945. X if (p_comopt->op_k_b) countopt++;
  1946. X if (p_comopt->op_h_b) countopt++;
  1947. X
  1948. X if (countopt == 0)
  1949. X   {
  1950. X    wl_sj("Your command line does not specify an action.");
  1951. X    wl_sj("Here some common ways of invoking FunnelWeb.");
  1952. X    wl_sj("");
  1953. X    wl_sj("   fw filename          Tangle filename.web.");
  1954. X    wl_sj("   fw filename +t       Tangle and weave filename.web.");
  1955. X    wl_sj("   fw +k                Enter interactive mode.");
  1956. X    wl_sj("   fw +xfilename        Execute FunnelWeb shellscript filename.fws.");
  1957. X    wl_sj("   fw +h                Display help information about FunnelWeb.");
  1958. X    wl_sj("");
  1959. X    if (countopt == 0)
  1960. X       wl_sj("F: Aborting because command line does not specify an action.");
  1961. X    sum_fat++;
  1962. X   }
  1963. X}
  1964. X
  1965. X/******************************************************************************/
  1966. X
  1967. LOCAL void open_j P_((void));
  1968. LOCAL void open_j()
  1969. X/* Creates and opens the journal file. Note that the journal output stream is */
  1970. X/* established regardless of whether the user requested a journal file. The   */
  1971. X/* only difference is that if the user did not specify a journal file, the    */
  1972. X/* stream is created in error mode which means that it never actually writes. */
  1973. X{
  1974. X fn_t jname;
  1975. X
  1976. X /* Establish the journal file output stream. */
  1977. X strcpy(jname,"");                 /* Start with an empty string.             */
  1978. X fn_ins(jname,p_comopt->op_f_s);   /* Insert input file name.                 */
  1979. X fn_ins(jname,".jrn");             /* Insert file extension.                  */
  1980. X fn_ins(jname,p_comopt->op_j_s);   /* Insert command line spec.               */
  1981. X wf_ini(&f_j,p_comopt->op_j_b);    /* Initialize the stream.                  */
  1982. X wf_ope(&f_j,jname);            /* Create the file.                           */
  1983. X if (p_comopt->op_j_b && wf_err(&f_j))
  1984. X   {
  1985. X    sprintf(linet1,"F: Error creating journal file \"%s\".",jname);
  1986. X    wl_s(linet1);
  1987. X    wl_s("Aborting...");
  1988. X    sum_fat++;
  1989. X    return;
  1990. X   }
  1991. X}
  1992. X
  1993. X/******************************************************************************/
  1994. X
  1995. LOCAL void close_j P_((void));
  1996. LOCAL void close_j()
  1997. X/* Closes the journal file. */
  1998. X{
  1999. X if (!p_comopt->op_j_b) return;
  2000. X if (wf_err(&f_j))
  2001. X   {
  2002. X    wl_s("F: Error writing to journal file. Aborting...");
  2003. X    sum_fat++;
  2004. X    return;
  2005. X   }
  2006. X wf_clo(&f_j);
  2007. X if (wf_err(&f_j))
  2008. X   {
  2009. X    wl_s("F: Error flushing and closing journal file. Aborting...");
  2010. X    sum_fat++;
  2011. X    return;
  2012. X   }
  2013. X}
  2014. X
  2015. X/******************************************************************************/
  2016. X
  2017. LOCAL void cl_help P_((void));
  2018. LOCAL void cl_help ()
  2019. X{
  2020. X uword messno;
  2021. X
  2022. X /* Translate message name to number. This ought to work, as the options      */
  2023. X /* package is already have supposed to have cleared this argument as OK.     */
  2024. X messno=hel_num(p_comopt->op_h_s);
  2025. X as_cold(messno!=HL_ERR,"cl_help: Unknown help argument.");
  2026. X
  2027. X /* Write out the message. */
  2028. X hel_wri(wr_sj,messno);
  2029. X}
  2030. X
  2031. X/******************************************************************************/
  2032. X
  2033. XEXPORT void command(p_comline)
  2034. X/* Execute the top level command line. This is the place where we do all the  */
  2035. X/* "once per shell" things as opposed to the "once per run" things.           */
  2036. X/* If a fatal error occurs, the correct course of action here is to increment */
  2037. X/* sum_fat and return immediately. The main() function deals with delivering  */
  2038. X/* the correct return status to the operating system.                         */
  2039. char *p_comline;
  2040. X{
  2041. X old_war=old_err=old_sev=0;
  2042. X
  2043. X tracing=FALSE;
  2044. X noabort=FALSE;
  2045. X skipping=FALSE;
  2046. X
  2047. X /* Allocate space for command line arguments. */
  2048. X allocarg();
  2049. X
  2050. X /* Set up the standard output (stdout) screen (console) output stream. */
  2051. X wf_ini(&f_s,TRUE);
  2052. X wf_att(&f_s,stdout);
  2053. X
  2054. X /* Parse the command line and place the information in p_comopt-> */
  2055. X op_ini(p_comopt);
  2056. X if (!op_add(p_comopt,p_comline,wr_s))
  2057. X   {wr_s("F: Command line error. Aborting..."); sum_fat++; goto windup;}
  2058. X
  2059. X /* If the user asked for some peace and quiet by setting +Q, disable the     */
  2060. X /* console stream. Note: FunnelWeb main() always issues a message using a    */
  2061. X /* printf if any diagnostics have been generated.                            */
  2062. X if (p_comopt->op_q_b) wf_ini(&f_s,FALSE);
  2063. X
  2064. X /* Create and open the journal file. */
  2065. X open_j(); if (sum_fat>0) goto windup;
  2066. X
  2067. X wl_sj("FunnelWeb Version 3.0 (May 1992)");
  2068. X wl_sj("--------------------------------");
  2069. X wl_sj("Copyright (C) Ross Williams 1992. There is ABSOLUTELY NO WARRANTY.");
  2070. X wl_sj("You are welcome to distribute this software under certain conditions.");
  2071. X if (p_comopt->op_k_b)
  2072. X wl_sj("For more information, type HELP.");
  2073. X else
  2074. X wl_sj("For more information, use the +h (help) option (e.g. \"fw +h\").");
  2075. X wl_sj("");
  2076. X
  2077. X /* Ensure that the user has specified at least one action. */
  2078. X chk_cline(); if (sum_fat>0) goto windup;
  2079. X
  2080. X /* Establish the default options for the shell run (if any).                 */
  2081. X /* Get rid of any options not to do with a single run of FunnelWeb proper.   */
  2082. X ASSIGN(*p_defopt,*p_comopt);
  2083. X p_defopt->op_j_b=FALSE;
  2084. X p_defopt->op_x_b=FALSE;
  2085. X p_defopt->op_k_b=FALSE;
  2086. X
  2087. X /* In the absence of everything else, command line options are run options. */
  2088. X ASSIGN(option,*p_comopt);
  2089. X
  2090. X /* Execute initialization file if any. */
  2091. X if (fexists(INITFILE))
  2092. X    interstr(INITFILE);
  2093. X
  2094. X /* Execute the specified actions. */
  2095. X if (p_comopt->op_x_b) interstr(p_comopt->op_x_s);
  2096. X if (p_comopt->op_f_b) {zerdia(); fwonerun(); sumdia();}
  2097. X if (p_comopt->op_h_b) cl_help();
  2098. X if (p_comopt->op_k_b) interpret(stdin,"standard_input");
  2099. X
  2100. X /* If we weren't in onerun mode, give a grand summary of errors. */
  2101. X if (p_comopt->op_k_b || p_comopt->op_x_b)
  2102. X    {
  2103. X     wr_sj("Final diagnostics totals: ");
  2104. X     errsum(sum_fat,sum_sev,sum_err,sum_war);
  2105. X     wl_sj(linet1);
  2106. X    }
  2107. X
  2108. X /* Close the journal file. */
  2109. X close_j(); if (sum_fat>0) goto windup;
  2110. X
  2111. X /* Check for errors on the screen stream (standard output). */
  2112. X if (p_comopt->op_s_b && wf_err(&f_s))
  2113. X   {
  2114. X    /* No point in trying to write a message to the screen! */
  2115. X    /* But we can at least register a fatal error.          */
  2116. X    sum_fat++;
  2117. X   }
  2118. X
  2119. X windup:
  2120. X
  2121. X /* If the user has set +Q to turn off screen output and one or more          */
  2122. X /* diagnostics have been generated, we need to break through to warn the     */
  2123. X /* user.                                                                     */
  2124. X {
  2125. X  ulong sum_all=sum_fat+sum_sev+sum_err+sum_war;
  2126. X  if (p_comopt->op_q_b && sum_all>0)
  2127. X     {
  2128. X      errsum(sum_fat,sum_sev,sum_err,sum_war);
  2129. X      fprintf(stderr,"%s\n",linet1);
  2130. X     }
  2131. X }
  2132. X}
  2133. X
  2134. X/******************************************************************************/
  2135. X/*                             End of COMMAND.C                               */
  2136. X/******************************************************************************/
  2137. END_OF_FILE
  2138. if test 69805 -ne `wc -c <'sources/command.c'`; then
  2139.     echo shar: \"'sources/command.c'\" unpacked with wrong size!
  2140. fi
  2141. # end of 'sources/command.c'
  2142. fi
  2143. echo shar: End of archive 18 \(of 20\).
  2144. cp /dev/null ark18isdone
  2145. MISSING=""
  2146. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ; do
  2147.     if test ! -f ark${I}isdone ; then
  2148.     MISSING="${MISSING} ${I}"
  2149.     fi
  2150. done
  2151. if test "${MISSING}" = "" ; then
  2152.     echo You have unpacked all 20 archives.
  2153.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  2154. else
  2155.     echo You still need to unpack the following archives:
  2156.     echo "        " ${MISSING}
  2157. fi
  2158. ##  End of shell archive.
  2159. exit 0
  2160.