home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TP.ZIP / TP.PAS
Encoding:
Pascal/Delphi Source File  |  1987-11-08  |  53.5 KB  |  1,945 lines

  1. { Text Processor }
  2.  
  3. { Author: Peter Grogono }
  4.  
  5. {$R- No range and bounds checking }
  6. {$U- No range and bounds checking of parameters }
  7. {$V- No type checking on string parameters }
  8.  
  9. program TP;
  10.  
  11. const
  12.  
  13. blank : char = ' ';
  14. FF    : byte = $0C;
  15. CR    : byte = $0D;
  16. LF    : byte = $0A;
  17. TAB   : byte = $09;
  18.  
  19. { Strings }
  20.  
  21. extin = '.TEX';      { Default input file extension }
  22. extout = '.DOC';     { Default output file extension }
  23. extcon = '.CON';     { Extension for contents file }
  24. extref = '.REF';     { Extension for cross-reference file }
  25. period = '.';        { End of                  }
  26. query = '?';         {        sentence         }
  27. shriek = '!';        {                 markers }
  28. sentgap = '  ';      { Two blanks at end of sentence }
  29. secgap = ' ';        { Two blanks after a section number }
  30. hardblank = '`';     { Non-trivial blank }
  31. underscore = '_';    { Underlining character }
  32. concat = '-';        { Concatenation character }
  33. pagechar = '#';      { Translates to page number in titles }
  34.  
  35. { String lengths.  The most important of these is maxlinelen, which
  36.   determines the maximum possible length of a line of text.  When keeping
  37.   blocks of text, TP uses more than 2 * maxlinelen bytes of memory for each
  38.   line.  Consequently you can reduce the dynamic storage requirements by
  39.   reducing the value of maxlinelen, if your lines will never be as long
  40.   as 120 characters.  }
  41.  
  42. namelen = 60;        { MSDOS file name length }
  43. maxlinelen = 120;    { Maximum length of text line }
  44. maxkeylen = 4;       { Maximum length of cross-reference key }
  45.  
  46. { For default values not defined here, see the initialization section
  47.   at the end of the listing.  }
  48.  
  49. { Horizontal control }
  50.  
  51. deffirstmargin = 6;  { Nothing can be printed left of this }
  52. defmaxwidth = 78;    { Width of text on page: 6.5" at 12 cpi }
  53. deflindent = 5;      { Indentation for list numbers }
  54. deflincr = 6;        { Additional indentation for list items }
  55. defparindent = 5;    { Indentation at start of paragraph }
  56. defdisindent = 10;   { Indentation for displays }
  57. deftabgap = 8;       { Tabs at 8, 16, 24, ... } 
  58. numpos = 70;         { Position for page # in table of contents }
  59. contmargin = 6;      { Left margin for contents file }
  60. contindent = 8;      { Indentation for contents file }
  61.  
  62. { Vertical control }
  63.  
  64. defleadin = 3;       { Lines between header and text }
  65. defmaxlines = 52;    { Maximum number of text lines on a page:
  66.                          8.7" at 6 lpi }
  67. deflinespacing = 2;  { Default line spacing }
  68. defparspacing = 4;   { Blank lines between paragraphs }
  69. defbhead = 6;        { Blank lines before a subheading }
  70. defahead = 4;        { Blank lines after a subheading }
  71. defbdisp = 3;        { Blank lines before a display }
  72. defadisp = 3;        { Blank lines after a display }
  73. defchapgap = 20;     { Blank lines after a chapter heading }
  74. deflastline = 55;    { Position of footer, relative to start of text }
  75. defminpara = 4;      { These three constants are used to avoid }
  76. defminsubsec = 8;    { starting something new near the bottom of }
  77. defminsec = 8;       { of a page }
  78. contpagsize = 52;    { Line on a page on the contents file }
  79. contlastline = 55;   { Line # for page # in contents file }
  80. contleadin = 3;      { Line feeds at top of contents page }
  81.  
  82. type
  83.  
  84. string0   = string[10];
  85.  
  86. string255 = string[255];
  87. filename = string[namelen];
  88. linetype = string[maxlinelen];
  89. pair = array [1..2] of char;
  90.  
  91. { A linerecord stores a line and the environment in which it must be
  92.   formatted.  TP stores a block of text to be 'kept' as a linked list
  93.   of line records.  Line records are also used by the procedures PUSH
  94.   and POP to save an environment.  A floatrecord is used to store an
  95.   entire block of text until it is required for output.  TP maintains
  96.   unprinted floating keeps as a linked list of floatrecords.
  97.  
  98.   There is a global variable corresponding to each field of these records.
  99.   It would be better programming practice to acknowledge this by using
  100.   global records rather than separate variables.  This, however, would
  101.   (1) make the program larger because of the offset addressing required;
  102.   (2) make the program slower for the same reason; and (3) penalize users
  103.   who are not using the features which require dynamic storage.  }
  104.  
  105. lineptr = ^ linerecord;
  106. linerecord = record
  107.                suppressing, textonline, breakline : boolean;
  108.                line, overline : linetype;
  109.                spacing : byte;
  110.                next : lineptr
  111.              end; { linerecord }
  112.  
  113. floatptr = ^ floatrecord;
  114. floatrecord = record
  115.                 first, last : lineptr;
  116.                 keepcount : byte;
  117.                 next : floatptr
  118.               end; { floatrecord }
  119.  
  120. { Cross-reference types }
  121.  
  122. keytype = string[maxkeylen];
  123. refptr = ^ refrecord;
  124. refrecord = record
  125.               key : keytype;
  126.               pagenum : integer;
  127.               chapnum, secnum, subsecnum, itemnum, entcount : integer;
  128.               left, right : refptr
  129.             end; { refrecord }
  130.  
  131. { Internal command codes. AA and ZZ are dummies }
  132.  
  133. codetype = (aa,bd,bf,bk,cc,ce,cx,co,ec,dl,ed,ef,ek,el,ep,
  134. fl,gp,hl,ic,il,im,li,ls,mr,mv,nu,ov,pa,pl,rb,rm,
  135. rr,sb,se,si,sl,sm,sp,ss,su,tc,tl,ts,ul,vl,zr,zz);
  136.  
  137. var
  138.  
  139. { Files }
  140.  
  141. infilename, outfilename, contfilename, refilename, Temp : filename;
  142. output, cont : text;
  143.  
  144. { Line buffers }
  145.  
  146. title, footer, line, overline : linetype;
  147.  
  148. { Command character }
  149.  
  150. comchar : char;
  151.  
  152. { Horizontal control }
  153.  
  154. maxwidth, firstmargin, margin, tabgap, parindent, disindent,
  155. listindent, listincr : integer;
  156. textonline, suppressing : boolean;
  157.  
  158. { Vertical control }
  159.  
  160. linesonpage, spacesdone, linespacing, spacing, minpara, minsec, minsubsec,
  161. leadin, maxlines, lastline, parspacing, chapgap, beforehead, afterhead, 
  162. beforedisp, afterdisp, beforeitem, afterlist : integer;
  163. breakline, pageready : boolean;
  164.  
  165. { Table of contents }
  166.  
  167. conttitle : linetype;
  168. contlines, contpage, contchapter, contsection : byte;
  169. contents, pageintc : boolean;
  170.  
  171. { Cross-references }
  172.  
  173. reftable : refptr;
  174. showrefs : boolean;
  175. currkey : keytype;
  176. entcount : byte;
  177.  
  178. { Section numbering }
  179.  
  180. pagenum : integer;
  181. chapnum, secnum, subsecnum : byte;
  182.  
  183. { Keeps and floating keeps }
  184.  
  185. freelist, first, last, stack : lineptr;
  186. firstfloat, lastfloat, freefloat : floatptr;
  187. keepcount : byte;
  188. keeping : boolean;
  189.  
  190. { Displays }
  191.  
  192. displaylevel, dispspacing, savespacing, diswidth, savewidth : integer;
  193.  
  194. { Itemized lists }
  195.  
  196. itemnum : integer;
  197. itemlist : boolean;
  198.  
  199. { Underlining }
  200.  
  201. uscharset : set of char;
  202. underlining : boolean;
  203.  
  204. { Special printer codes }
  205.  
  206. printwarning : boolean;
  207.  
  208. { Miscellaneous counters }
  209.  
  210. spaceleft, wordcount, pagecount : integer;
  211. errorcount : byte;
  212.  
  213. { Constant tables and sets }
  214.  
  215. codetable : array [codetype] of pair;
  216. wordends : set of char;
  217.  
  218. { Append the character ch to string s }
  219.  
  220. procedure append (var s : string0; ch : char);
  221.  
  222. begin
  223. s := s+ch;
  224. end; { append }
  225.  
  226. { Index the string ch into the string s }
  227.  
  228. function index (var s:string0; ch : char) : integer;
  229.  
  230. begin
  231. index := pos(ch,s);
  232. end; { index }
  233.  
  234. { Pad the string s to length len }
  235.  
  236. procedure Pad (var s : string0; len : integer);
  237.  
  238. begin
  239. while length(s) < len do s := s+' ';
  240. end; { pad }
  241.  
  242. { Set string length equal to zero }
  243.  
  244. procedure setlength (var s:string255; l : integer);
  245.  
  246. begin
  247. s[0] := chr(l);
  248. end; { setlength }
  249. {$V-*}
  250. { Read file names from command buffer }
  251.  
  252. { This function provides sophisticated command line argument parsing.
  253.   One often wants a program to be able to take arguments from the command
  254.   line, yet at the same time provide prompts if they are not specified.
  255.   Arguments are assumed to be delimited by spaces, tabs, or slashes.  A slash
  256.   delimits a special argument, a switch.  Only non-positional switches are
  257.   implemented.  A positional switch is a switch that affects only part of the
  258.   command, i.e.
  259.     dir file1/date file2
  260.   meaning to add information about the date of file1, but not file2.
  261.   Nonpositional switches, as implemented here, always affect the entire
  262.   command.  So in this case, date information would be displayed for both
  263.   files.
  264.  
  265.   To request a non-switch parameter, a call to the function is made with the
  266.   boolean argument Switch set to false.  The function takes the first string
  267.   parameter off the command line.  If there are none, it looks at the value
  268.   of the Prompt argument.  If the prompt is specified, it prints it and reads
  269.   in a line.  This line may contain more than one argument: the user can
  270.   anticipate future prompts, or add switches.  The first argument on this line
  271.   is returned.  If there are no string arguments on the read line, the value
  272.   of the Default parameter is examined.  If it is empty, or contains a string,
  273.   that string is returned.  But if it contains the string '/', the prompt/read
  274.   is repeated.  This is for critical parameters.
  275.  
  276.   When a switch is requested, by setting the Switch parameter to true, the
  277.   first switch in the buffer is returned.  This is a string whose leading
  278.   character is a slash, i.e. '/date' in the earlier example.  If there are
  279.   none, an empty string is returned.  The Prompt and Default arguments have no
  280.   meaning when requesting a switch.
  281.  
  282.   The example program at the end of the file needs two file names: an input
  283.   name which must be specified, and an output name that defaults to the input
  284.   name with the extension '.OUT'.  It also checks for any switches specified.
  285.  
  286.   This system is modeled after the command line syntax of VAX/VMS, with the
  287.   omission of positional parameters, and some of the more esoteric things like
  288.   quoted arguments containing spaces.  Parsing of switches is left largely up
  289.   to the user program.
  290.  
  291.   Comments are welcomed (also in the sense that if you'd like to add comments
  292.   to my code, have fun)!
  293.  
  294.   -  Bela Lubkin
  295.  }
  296.  
  297. Type
  298.   BigString=String[127];
  299.  
  300. Function CommandLineArgument(Prompt, Default: BigString;
  301.                              Switch:Boolean): BigString;
  302.   Const
  303.     Buffered: Boolean=False;
  304.     CLBuffer: BigString='';
  305.     Delim: Set Of Char=[^I,' ','/'];
  306.  
  307.   Var
  308.     CommandLine: BigString Absolute CSeg:$0080;  { For MS-DOS  }
  309. (*  CommandLine: BigString Absolute DSeg:$0080;  { For CP/M-86 }  *)
  310. (*  CommandLine: BigString Absolute $0080;       { For CP/M-80 }  *)
  311.     CLA, CLBufferTemp: BigString;
  312.     Posn,PosnA: Integer;
  313.     Found: Boolean;
  314.  
  315.   Begin
  316.     If Not Buffered Then CLBuffer:=CommandLine;
  317.     Buffered:=True;
  318.     Posn:=1;
  319.     Found:=False;
  320.     While Not Found Do
  321.      Begin
  322.       CLA:='';
  323.       While (Posn<=Length(CLBuffer)) And (CLBuffer[Posn] In Delim) Do
  324.         Posn:=Posn+1;
  325.       PosnA:=Posn;
  326.       If (Posn<>1) And (Posn<=Length(CLBuffer)) Then
  327.         If CLBuffer[Posn-1]='/' Then
  328.          Begin
  329.           CLA:='/';
  330.           PosnA:=PosnA-1;
  331.          End;
  332.       While (Posn<=Length(CLBuffer)) And (Not (CLBuffer[Posn] In Delim)) Do
  333.        Begin
  334.         CLA:=CLA+CLBuffer[Posn];
  335.         Posn:=Posn+1;
  336.        End;
  337.       Found:=(Switch Xor (CLA[1]<>'/')) Or (CLA='');
  338.      End;
  339.     Delete(CLBuffer,PosnA,Posn-PosnA);
  340.     If (CLA='') And Not Switch Then
  341.      Begin
  342.       Found:=False;
  343.       While Not Found Do
  344.        Begin
  345.         If Prompt<>'' Then
  346.          Begin
  347.           Write(Prompt);
  348.           ReadLn(CLBufferTemp);
  349.           CLBuffer:=CLBufferTemp+CLBuffer;
  350.           CLA:=CommandLineArgument('','',False);
  351.          End;
  352.         If CLA='' Then CLA:=Default;
  353.         Found:=CLA<>'/';
  354.        End;
  355.      End;
  356.     CommandLineArgument:=CLA;
  357.   End;
  358.  
  359. { Convert lower case letters to upper case }
  360.  
  361. function upper (ch : char) : char;
  362.  
  363. begin
  364. if ch in ['a'..'z'] then upper := chr(ord(ch) - ord('a') + ord('A'))
  365. else upper := ch
  366. end; { upper }
  367.  
  368. { Create a new file name from a given file name and the extension EXT.  }
  369.  
  370. procedure changext (inname : filename; ext : string255; var name : filename);
  371.  
  372. var perpos : integer;
  373.  
  374. begin
  375.   name := inname;
  376.   perpos := index(name,period);
  377.   if perpos <> 0 then delete(name, perpos, length(name));
  378.   name := name+ext;
  379. end; { changext }
  380.  
  381. { ---------------------- Cross-reference procedures ------------------------ }
  382.  
  383. { Store current global values into specified entry.  }
  384.  
  385. procedure update (ref : refptr);
  386.  
  387. begin
  388. ref^.pagenum := pagenum;
  389. ref^.chapnum := chapnum;
  390. ref^.secnum := secnum;
  391. ref^.subsecnum := subsecnum;
  392. ref^.itemnum := itemnum
  393. end; { update }
  394.  
  395. { Make a new entry or update an old entry in the cross-reference table.  }
  396.  
  397. procedure makentry (key : keytype; var ref : refptr);
  398.  
  399. begin
  400. if ref = nil then
  401. begin new(ref); ref^.left := nil; ref^.right := nil;
  402. ref^.key := key; ref^.entcount := 0; update(ref) end
  403. else
  404. if key < ref^.key then makentry(key,ref^.left)
  405. else
  406. if key > ref^.key then makentry(key,ref^.right)
  407. else update(ref) { old entry }
  408. end; { makentry }
  409.  
  410. { Look up an entry in the table, given the key.  }
  411.  
  412. procedure lookup (key : keytype; root : refptr; var ref : refptr);
  413.  
  414. begin
  415. if root = nil then ref := nil else
  416. if key < root^.key then lookup(key,root^.left,ref) else
  417. if key > root^.key then lookup(key,root^.right,ref)
  418. else ref := root
  419. end; { lookup }
  420.  
  421. { Write cross-reference table to a file.  }
  422.  
  423. procedure writerefs;
  424.  
  425. var
  426. refile : text;
  427.  
  428. { Write a sub-tree of entries to the file.  The sub-tree is traversed
  429.   in pre-order so that re-reading the file will not create a degenerate
  430.   tree.  }
  431.  
  432. procedure putentry (ref : refptr);
  433.  
  434. begin
  435. if ref <> nil then
  436. with ref ^ do
  437. begin
  438. writeln(refile,key,pagenum:6,chapnum:4,secnum:4,
  439. subsecnum:4,itemnum:4,entcount:4);
  440. putentry(left); putentry(right)
  441. end
  442. end; { putentry }
  443.  
  444. begin { writerefs }
  445. changext(infilename,extref,refilename);
  446. assign(refile,refilename);
  447. rewrite(refile); putentry(reftable); close(refile)
  448. end; { writerefs }
  449.  
  450. { Read a file of cross-references.  }
  451.  
  452. procedure readrefs;
  453.  
  454. var
  455. refile : text;
  456. key : keytype;
  457. ch : char;
  458.  
  459. begin
  460.   reftable := nil;
  461.   changext(infilename,extref,refilename);
  462.   assign(refile,refilename);
  463.   {$I-}
  464.   reset(refile);
  465.   {$I+}
  466.   if ioresult <> 0 then
  467.     begin
  468.       writeln('File ', refilename, ' not found, ignored.');
  469.     end
  470.   else
  471.   while not eof(refile) do
  472.     begin
  473.       setlength(key,0); read(refile,ch);
  474.       while ch <> blank do
  475.         begin
  476.           key := key+ch;
  477.           read(refile,ch)
  478.         end; { while }
  479.       readln(refile,pagenum,chapnum,secnum,subsecnum,itemnum);
  480.       pad(key,maxkeylen);
  481.       makentry(key,reftable)
  482.     end; { while }
  483.   close(refile);
  484. end; { readrefs }
  485.  
  486. procedure putline; forward;
  487.  
  488. { --------------------- Free store and keep management --------------------- }
  489.  
  490. { The next three procedures handle dynamic storage of lines.  There is a
  491.   stack for saving environments and a queue for storing 'kept' text.
  492.   The procedure POP is used to remove a line from the stack or the queue.
  493.   The procedure SAVE is used to insert a line into the stack or the queue,
  494.   it does not do the pointer updating because it doesn't know whether the
  495.   line is to go at the back of a queue or the front of a list.  }
  496.  
  497. procedure save (var ptr : lineptr);
  498.  
  499. begin
  500.   if freelist = nil then new(ptr)
  501.   else
  502.     begin
  503.       ptr := freelist;
  504.       freelist := freelist^.next
  505.     end;
  506.   ptr^.suppressing := suppressing;
  507.   ptr^.textonline := textonline;
  508.   ptr^.breakline := breakline;
  509.   ptr^.line := line;
  510.   ptr^.overline := overline;
  511.   ptr^.spacing := spacing
  512. end; { save }
  513.  
  514. procedure push;
  515.  
  516. var
  517. ptr : lineptr;
  518.  
  519. begin save(ptr); ptr^.next := stack; stack := ptr end; { push }
  520.  
  521. procedure pop (var ptr : lineptr);
  522.  
  523. var
  524. old : lineptr;
  525.  
  526. begin
  527.   suppressing := ptr^.suppressing;
  528.   textonline := ptr^.textonline;
  529.   breakline := ptr^.breakline;
  530.   line := ptr^.line;
  531.   overline := ptr^.overline;
  532.   spacing := ptr^.spacing;
  533.   old := ptr;
  534.   ptr := ptr^.next;
  535.   old^.next := freelist;
  536.   freelist := old
  537. end; { pop }
  538.  
  539. { Reset the keep pointers and count.  This procedure does not affect the
  540.   contents of the keep queue.  }
  541.  
  542. procedure resetkeep;
  543.  
  544. begin
  545.   first := nil;
  546.   last := nil;
  547.   keepcount := 0
  548. end; { resetkeep }
  549.  
  550. { Put a line of text into a keep buffer }
  551.  
  552. procedure keep;
  553.  
  554. var
  555. ptr : lineptr;
  556.  
  557. begin
  558.   save(ptr);
  559.   keepcount := keepcount + spacing;
  560.   if first = nil then
  561.     first := ptr
  562.   else
  563.     last^.next := ptr;
  564.   last := ptr;
  565.   ptr^.next := nil
  566. end; { keep }
  567.  
  568. { End a keep.  Write kept lines to output file.  }
  569.  
  570. procedure endkeep;
  571.  
  572. var
  573. ptr : lineptr;
  574.  
  575. begin
  576.   ptr := first;
  577.   resetkeep;
  578.   while ptr <> nil do
  579.     begin
  580.       pop(ptr);
  581.       putline
  582.     end { while }
  583. end; { endkeep }
  584.  
  585. { ------------------------- Table of Contents management ------------------- }
  586.  
  587. { Write a title in the contents file }
  588.  
  589. procedure putconttitle;
  590.  
  591. var
  592. count : byte;
  593.  
  594. begin
  595.   writeln(cont,chr(FF));
  596.   writeln(cont,blank:contmargin,conttitle);
  597.   for count := 1 to contleadin do writeln(cont);
  598.   contpage := succ(contpage);
  599.   contlines := 0
  600. end; { putcontitle }
  601.  
  602. { End a page of the contents file }
  603.  
  604. procedure endcontpage;
  605.  
  606. begin
  607.   while contlines < contlastline do
  608.     begin
  609.       writeln(cont); contlines := succ(contlines)
  610.     end; { while }
  611.   writeln(cont,blank:numpos,'C-',contpage:1)
  612. end; { endcontpage }
  613.  
  614. { Write blank lines followed by title or section name to contents file;
  615.   start a new page when necessary.  }
  616.  
  617. procedure putcontline (lines, indent : byte; line : linetype);
  618.  
  619. var
  620. count : byte;
  621. ch : char;
  622.  
  623. begin
  624.   if contlines + lines > contpagsize then
  625.     begin
  626.       endcontpage;
  627.       putconttitle
  628.     end
  629.   else
  630.     begin
  631.       for count := 1 to lines do writeln(cont);
  632.       contlines := contlines + lines
  633.     end;
  634.   write(cont,blank:indent);
  635.   for count := 1 to length(line) do
  636.     begin
  637.       ch := line[count];
  638.       if ch = hardblank then write(cont,blank)
  639.       else write(cont,ch)
  640.     end; { for }
  641.   if pageintc then write(cont,blank:3,pagenum:1)
  642. end; { putcontline }
  643.  
  644. { -------------------------- Page layout ----------------------------------- }
  645.  
  646. { Write a running header or footer }
  647.  
  648. procedure writerunner (runner : linetype);
  649.  
  650. var
  651. i : byte;
  652. ch : char;
  653.  
  654. begin
  655.   write(output,blank:firstmargin);
  656.   for i := 1 to length(runner) do
  657.     begin
  658.       ch := runner[i];
  659.       if ch = hardblank then write(output,blank)
  660.       else
  661.         if ch = pagechar then write(output,pagenum:1)
  662.         else write(output,ch)
  663.     end; { for }
  664.   writeln(output)
  665. end; { writerunner }
  666.  
  667. { Start a new page and write header on it.  If there are any floating keeps
  668.   in the list, as many are printed as will fit on the page.  When a floating
  669.   keep has been printed out the memory that it occupied is reclaimed.  }
  670.  
  671. procedure startpage;
  672.  
  673. var
  674. count : byte;
  675. float : floatptr;
  676. done : boolean;
  677.  
  678. begin
  679.   writeln(output,chr(FF));
  680.   writerunner(title);
  681.   for count := 1 to leadin do writeln(output);
  682.   pagenum := succ(pagenum);
  683.   pagecount := succ(pagecount);
  684.   linesonpage := 0;
  685.   pageready := true;
  686.   done := false;
  687.   repeat
  688.     if firstfloat = nil then done := true
  689.     else
  690.       begin
  691.         count := firstfloat^.keepcount;
  692.         if (count + linesonpage > maxlines) and (count <= maxlines) then
  693.           done := true { Not enough space }
  694.         else
  695.           begin
  696.             push;
  697.             first := firstfloat^.first;
  698.             last := firstfloat^.last;
  699.             keepcount := count;
  700.             endkeep;
  701.             float := firstfloat;
  702.             firstfloat := float^.next;
  703.             float^.next := freefloat;
  704.             freefloat := float;
  705.             pop(stack)
  706.           end
  707.       end
  708.   until done
  709. end; { startpage }
  710.  
  711. { End a page by filling it with blank lines and writing footer }
  712.  
  713. procedure endpage;
  714.  
  715. begin
  716.   if pageready then
  717.     begin
  718.       while linesonpage < lastline do
  719.         begin
  720.           writeln(output);
  721.           linesonpage := succ(linesonpage)
  722.         end; { while }
  723.       writerunner(footer);
  724.       pageready := false
  725.     end
  726. end; { endpage }
  727.  
  728. { Any floating keeps must be released at the end of a chapter and at
  729.   the end of the text.  }
  730.  
  731. procedure endchap;
  732.  
  733. begin
  734.   putline; endpage;
  735.   while firstfloat <> nil do
  736.     begin
  737.       startpage;
  738.       endpage
  739.     end { while }
  740. end; { endchap }
  741.  
  742. { -------------------------- Output management ----------------------------- }
  743.  
  744. { Initialize the current line }
  745.  
  746. procedure resetline;
  747.  
  748. begin
  749.   setlength(line,0);
  750.   setlength(overline,0);
  751.   spacing := linespacing;
  752.   textonline := false;
  753.   breakline := false
  754. end; { resetline }
  755.  
  756. { Output a completed line.  Where the line goes depends on whether
  757.   we are "keeping" or not.  Output blank lines after the line
  758.   according to the value of SPACING.  Reset the line buffers. }
  759.  
  760. procedure putline;
  761.  
  762. var
  763. ch : char;
  764. count : byte;
  765.  
  766. { Write the left margin.  No user text can appear in margin, but it is used
  767.   for cross-reference entries if \ZR is called.  }
  768.  
  769. procedure writemargin;
  770.  
  771. begin
  772.   if showrefs and (length(currkey) > 0) then
  773.     begin
  774.       write(output,currkey,blank:firstmargin - maxkeylen);
  775.       setlength(currkey,0)
  776.     end
  777.   else write(output,blank:firstmargin)
  778. end; { writemargin }
  779.  
  780. begin { putline }
  781.   if keeping then keep
  782.   else
  783.     begin
  784.       if textonline or not suppressing then
  785.         begin
  786.           if linesonpage >= maxlines then endpage;
  787.           if not pageready then startpage;
  788.           writemargin;
  789.           for count := 1 to length(line) do
  790.             begin
  791.               ch := line[count];
  792.               if ch = hardblank then write(output,blank)
  793.               else write(output,ch)
  794.             end; { for }
  795.           if length(overline) > 0 then
  796.             begin
  797.               write(output,chr(CR));
  798.               writemargin;
  799.               write(output,overline)
  800.             end;
  801.           spacesdone := 0
  802.         end;
  803.       while (spacesdone < spacing) and (linesonpage < maxlines) do
  804.         begin
  805.           writeln(output);
  806.           linesonpage := succ(linesonpage);
  807.           spacesdone := succ(spacesdone)
  808.         end; { while }
  809.     end;
  810.   resetline
  811. end; { putline }
  812.  
  813. { Append one character to a line.  Start a new line if necessary.
  814.   Underline the character if UNDERLINING is true and the character
  815.   is in the underline set. }
  816.  
  817. procedure putchar (ch : char; underlining : boolean);
  818.  
  819. begin
  820.   if breakline or (length(line) >= maxwidth) then putline;
  821.   if not textonline then pad(line,margin);
  822.   line := line+ch;
  823.   if underlining and (ch in uscharset) then
  824.     begin
  825.       pad(overline,pred(length(line)));
  826.       overline := overline+underscore;
  827.     end;
  828.   textonline := true
  829. end; { putchar }
  830.  
  831. { Append a positive number to the line buffer without leading
  832.   or trailing blanks. }
  833.  
  834. procedure putnum (var line : string0; num : integer);
  835.  
  836. var
  837. buf : array [1..5] of char;
  838. bp, cp : byte;
  839.  
  840. begin
  841.   bp := 0;
  842.   repeat
  843.     bp := succ(bp);
  844.     buf[bp] := chr(num mod 10 + ord('0'));
  845.     num := num div 10
  846.   until num = 0;
  847.   for cp := bp downto 1 do line := line+buf[cp]
  848. end; { putnum }
  849.  
  850. { Append a section number to a line }
  851.  
  852. procedure putsecnum (var line : string0;
  853. chapnum, secnum, subsecnum : integer);
  854.  
  855. var
  856. trailing : boolean;
  857.  
  858. begin
  859.   trailing := false;
  860.   if chapnum > 0 then
  861.     begin
  862.       putnum(line,chapnum);
  863.       trailing := true
  864.     end;
  865.   if secnum > 0 then
  866.     begin
  867.       if trailing then line := line+period;
  868.       putnum(line,secnum);
  869.       trailing := true
  870.     end;
  871.   if subsecnum > 0 then
  872.     begin
  873.       if trailing then line := line+period;
  874.       putnum(line,subsecnum)
  875.     end
  876. end; { putsecnum }
  877.  
  878. { Append a word to the line buffer.  Separate words by:
  879.     0 blanks if CONCAT character is last but not only character;
  880.     2 blanks if end of sentence;
  881.     1 blank otherwise.
  882.   If first character is underscore then underline entire word. }
  883.  
  884. procedure putword (word : string255);
  885.  
  886. var
  887. ch, lastchar : char;
  888. wordlen, linelen, count : byte;
  889. space : integer;
  890. underline, concatenate, sentend : boolean;
  891.  
  892. begin
  893.   linelen := length(line);
  894.   if linelen = 0 then
  895.   begin
  896.     lastchar := blank;
  897.     sentend := false;
  898.     concatenate := false
  899.   end
  900.   else
  901.     begin
  902.       lastchar := line[linelen];
  903.       if (lastchar = concat) and
  904.          (linelen > 1) and
  905.          (line[pred(linelen)] <> blank) and
  906.          (line[pred(linelen)] <> concat) then
  907.         begin
  908.           sentend := false;
  909.           concatenate := true;
  910.           setlength(line,pred(linelen))
  911.         end
  912.       else
  913.         begin
  914.           sentend := lastchar in [period,query,shriek];
  915.           concatenate := false
  916.         end
  917.     end;
  918.   wordlen := length(word);
  919.   writeln(word, ' ', wordlen);
  920.   underline := (wordlen > 1) and (word[1] = underscore);
  921.   if underline then wordlen := pred(wordlen);
  922.   space := maxwidth - linelen - wordlen;
  923.   if (breakline or
  924.      (sentend and (space <= 6)) or
  925.      (not sentend and (space <= 1))) then putline;
  926.   if textonline then
  927.     begin
  928.       if sentend then line := line+sentgap
  929.       else if not concatenate then line := line+blank
  930.     end
  931.   else pad(line,margin);
  932.   if underline then
  933.     begin
  934.       pad(overline,length(line));
  935.       for count := 2 to succ(wordlen) do
  936.         begin
  937.           ch := word[count];
  938.           line := line+ch;
  939.           if ch in uscharset then overline := overline+underscore
  940.           else overline := overline+blank
  941.         end { for }
  942.     end
  943.   else line := line+word;
  944.   textonline := true;
  945.   wordcount := succ(wordcount)
  946. end; { putword }
  947.  
  948. { Record the need to break a line, and the blank space needed after it }
  949.  
  950. procedure break (spaceneeded : byte);
  951.  
  952. begin
  953.   breakline := true;
  954.   if spaceneeded > spacing then spacing := spaceneeded
  955. end; { break }
  956.  
  957. { -------------------------- Text Processing ------------------------------- }
  958.  
  959. { Process a file of text.  This procedure calls itself recursively
  960.   to process included files.  Global variables are maintained while
  961.   an included file is processed, but variables local to this
  962.   procedure are saved implicitly on the stack until the included
  963.   file has been processed, and are then restored. }
  964.  
  965. procedure process (infilename : filename);
  966.  
  967. var
  968. input : text;
  969. word : linetype;
  970. ch : char;
  971. inlinecount : integer;
  972.  
  973. { Get a character from the input file.  Translate EOF to NUL (0)
  974.   and EOL to CR.  Count lines read. }
  975.  
  976. procedure getchar;
  977.  
  978. begin
  979.   if eof(input) then ch := chr(0)
  980.   else if eoln(input) then
  981.     begin
  982.       read(input,ch);
  983.       read(input,ch);
  984.       ch := chr(CR);
  985.       inlinecount := succ(inlinecount)
  986.     end
  987.   else read(input,ch)
  988. end; { getchar }
  989.  
  990. { Get a word from the input file.  The first character is already
  991.   in ch.  A word is terminated by blank, EOL, EOF, or TAB. }
  992.  
  993. procedure getword (var word : string255);
  994.  
  995. begin
  996.   word := '';
  997.   repeat
  998.     word := word+ch;
  999.     getchar
  1000.   until ch in wordends;
  1001. end; { getword }
  1002.  
  1003. { Read and store text up to the end of the input line }
  1004.  
  1005. procedure getline (var line : string255);
  1006.  
  1007. begin
  1008.   while ch <> chr(CR) do
  1009.     begin
  1010.       line := line+ch;
  1011.       getchar
  1012.     end { while }
  1013. end; { getline }
  1014.  
  1015. { -------------------------  Command decoder  ------------------------- }
  1016.  
  1017. { Called when comchar is encountered in text. }
  1018.  
  1019. procedure command;
  1020.  
  1021. var
  1022. infilename : filename;
  1023. cmd : pair;
  1024. code : codetype;
  1025. count : integer;
  1026. word : linetype;
  1027. num : integer;
  1028. key : keytype;
  1029. ref : refptr;
  1030. refcode : char;
  1031. float : floatptr;
  1032.  
  1033. { Report an error }
  1034.  
  1035. procedure error (message : string255);
  1036.  
  1037. begin
  1038.   writeln('Line ',inlinecount:1,', command ',codetable[code],': ',message);
  1039.   errorcount := succ(errorcount)
  1040. end; { error }
  1041.  
  1042. { Skip over blanks }
  1043.  
  1044. procedure skip;
  1045.  
  1046. begin
  1047.   while ch = blank do getchar
  1048. end; { skip }
  1049.  
  1050. { Read an unsigned integer.  Skip leading blanks.
  1051.   Any non-digit terminates the number. }
  1052.  
  1053. procedure getnum (var num : integer);
  1054.  
  1055. begin
  1056.   num := 0;
  1057.   skip;
  1058.   while ch in ['0'..'9'] do
  1059.     begin
  1060.       num := 10 * num + ord(ch) - ord('0');
  1061.       getchar
  1062.     end { while }
  1063. end; { getnum }
  1064.  
  1065. { Read a number.  The following cases are handled:
  1066.     NNN    return value of NNN;
  1067.     =      return DEFAULT;
  1068.     +NNN   return DEFAULT + NNN;
  1069.     -NNN   return DEFAULT - NNN. }
  1070.  
  1071. procedure getdefnum (var num : integer; default : integer);
  1072.  
  1073. var
  1074. mode : (plus, minus, abs);
  1075.  
  1076. begin
  1077.   skip;
  1078.   if ch = '+' then
  1079.     begin
  1080.       mode := plus;
  1081.       getchar
  1082.     end
  1083.   else if ch = '-' then
  1084.     begin
  1085.       mode := minus;
  1086.       getchar
  1087.     end
  1088.   else mode := abs;
  1089.   getnum(num);
  1090.   if (num = 0) and (ch = '=') then
  1091.     begin
  1092.       num := default;
  1093.       getchar
  1094.     end
  1095.   else
  1096.     case mode of
  1097.       plus : num := default + num;
  1098.       minus : num := default - num;
  1099.       abs :
  1100.     end { case }
  1101. end; { getdefnum }
  1102.  
  1103. { Read a cross-reference key }
  1104.  
  1105. procedure getkey (var key : string0);
  1106.  
  1107. begin
  1108.   setlength(key,0); skip;
  1109.   while ch in ['a'..'z','A'..'Z','0'..'9'] do
  1110.     begin
  1111.     if length(key) < maxkeylen then key := key+ch;
  1112.     getchar
  1113.   end; { while }
  1114.   pad(key,maxkeylen)
  1115. end; { getkey }
  1116.  
  1117. { Set vertical spacing parameters based on the value of linespacing }
  1118.  
  1119. procedure setspacing (linespacing : byte);
  1120.  
  1121. begin
  1122.   parspacing := 2 * linespacing;
  1123.   beforehead := 3 * linespacing;
  1124.   afterhead := 2 * linespacing;
  1125.   beforedisp := succ(linespacing);
  1126.   afterdisp := succ(linespacing);
  1127.   beforeitem := succ(linespacing);
  1128.   afterlist := succ(linespacing);
  1129.   dispspacing := linespacing
  1130. end; { setspacing }
  1131.  
  1132. { This procedure is called when the command processor encounters a
  1133.   command character that is not followed by a letter; ch contains
  1134.   the character following the command character. }
  1135.  
  1136. procedure putcomchar;
  1137.  
  1138. var
  1139. word : linetype;
  1140.  
  1141. begin
  1142.   if suppressing then
  1143.     if ch in wordends then putword(comchar)
  1144.     else
  1145.       begin
  1146.         setlength(word,0);
  1147.         word := word+comchar;
  1148.         repeat
  1149.           word := (word+ch);
  1150.           getchar
  1151.         until ch in wordends;
  1152.         putword(word)
  1153.       end
  1154.   else putchar(comchar,underlining)
  1155. end; { putcomchar }
  1156.  
  1157. { Check amount of space on page and start a new page if necessary.
  1158.   No effect in keep mode. }
  1159.  
  1160. procedure check (linesneeded : byte);
  1161.  
  1162. begin
  1163.   if not keeping then
  1164.     begin
  1165.       if linesonpage + linesneeded > maxlines then endpage;
  1166.       if not pageready then startpage
  1167.     end
  1168. end; { check }
  1169.  
  1170. { Start a new paragraph, on a new page if necessary. }
  1171.  
  1172. procedure startpara (spaceneeded : byte);
  1173.  
  1174. begin
  1175.   break(spaceneeded);
  1176.   putline;
  1177.   check(minpara);
  1178.   pad(line,margin + parindent)
  1179. end; { startpara }
  1180.  
  1181. { Write a subheading.  Write chapter number, section number,
  1182.   subsection number if > 0, title.  Title is terminated by
  1183.   EOL or command terminator.  Start a new paragraph. }
  1184.  
  1185. procedure putsubhead (min : byte; numbered : boolean);
  1186.  
  1187. var
  1188. word : linetype;
  1189.  
  1190. begin
  1191.   break(beforehead);
  1192.   putline;
  1193.   check(min);
  1194.   setlength(word,0);
  1195.   if numbered then
  1196.     begin
  1197.       putsecnum(word,chapnum,secnum,subsecnum);
  1198.       if length(word) > 0 then
  1199.         begin
  1200.           word := (word+secgap);
  1201.           putword(word)
  1202.         end
  1203.     end;
  1204.   skip;
  1205.   while ch <> chr(CR) do
  1206.     begin
  1207.       getword(word);
  1208.       skip;
  1209.       putword(word)
  1210.     end; { while }
  1211.   if contents and numbered then
  1212.     putcontline(contsection,contmargin+contindent,line);
  1213.   startpara(afterhead)
  1214. end; { putsubhead }
  1215.  
  1216. { ---------------------- Command processor --------------------------------- }
  1217.  
  1218. begin { command }
  1219.   getchar;
  1220.   if not (ch in ['a'..'z','A'..'Z']) then putcomchar
  1221.   else
  1222.     begin
  1223.       cmd[1] := upper(ch);
  1224.       getchar;
  1225.       cmd[2] := upper(ch);
  1226.       getchar;
  1227.       code := zz;
  1228.       codetable[aa] := cmd;
  1229.       while codetable[code] <> cmd do code := pred(code);
  1230.       case code of
  1231.  
  1232. { Illegal commands }
  1233.  
  1234.         aa, zz : error('invalid command code');
  1235.  
  1236. { BD : Begin display }
  1237.  
  1238.         bd : begin
  1239.                margin := margin + disindent;
  1240.                break(beforedisp);
  1241.                displaylevel := succ(displaylevel);
  1242.                if displaylevel = 1 then
  1243.                  begin
  1244.                    savespacing := linespacing;
  1245.                    linespacing := dispspacing;
  1246.                    setspacing(linespacing);
  1247.                    savewidth := maxwidth;
  1248.                    maxwidth := diswidth
  1249.                  end
  1250.              end;
  1251.  
  1252. { BF : Begin floating keep }
  1253.  
  1254.         bf : if keeping then error('already keeping')
  1255.              else
  1256.                begin
  1257.                  push;
  1258.                  resetline;
  1259.                  keeping := true;
  1260.                  keepcount := 0
  1261.                end;
  1262.  
  1263. { BK : Begin keep }
  1264.  
  1265.         bk : if keeping then error('already keeping')
  1266.              else
  1267.                begin
  1268.                  break(0);
  1269.                  putline;
  1270.                  keeping := true
  1271.                end;
  1272.  
  1273. { CC : Printer control characters }
  1274.  
  1275.         cc : begin
  1276.                skip;
  1277.                while ch in ['0'..'9'] do
  1278.                  begin
  1279.                    getnum(num);
  1280.                    skip;
  1281.                    if (1 <= num) and (num <= 31) then write(output,chr(num))
  1282.                    else
  1283.                      begin
  1284.                        error('invalid control character');
  1285.                        getchar
  1286.                      end
  1287.                  end; { while }
  1288.                printwarning := true
  1289.              end;
  1290.  
  1291. { CE : Print one line centered }
  1292.  
  1293.         ce : begin
  1294.                break(0);
  1295.                putline;
  1296.                setlength(word,0);
  1297.                skip;
  1298.                getline(word);
  1299.                for count := 1 to (maxwidth - length(word)) div 2 do line := (line+blank);
  1300.                line := (line+word);
  1301.                textonline := true;
  1302.                putline
  1303.              end;
  1304.  
  1305. { CH : Start a new chapter }
  1306.  
  1307.         cx : begin
  1308.                if keeping then error('floating or keeping');
  1309.                endchap;
  1310.                chapnum := succ(chapnum);
  1311.                secnum := 0;
  1312.                subsecnum := 0;
  1313.                setlength(title,0);
  1314.                putnum(title,chapnum);
  1315.                title := (title+'.  ');
  1316.                skip;
  1317.                getline(title);
  1318.                startpage;
  1319.                startpara(chapgap);
  1320.                if contents then putcontline(contchapter,contmargin,title)
  1321.              end;
  1322.  
  1323. { CO : Comment }
  1324.  
  1325.         co : while ch <> chr(CR) do getchar;
  1326.  
  1327. { DL : Set display layout }
  1328.  
  1329.         dl : begin
  1330.                getdefnum(beforedisp,defbdisp);
  1331.                getdefnum(afterdisp,defadisp);
  1332.                getdefnum(dispspacing,linespacing);
  1333.                getdefnum(disindent,defdisindent);
  1334.                getdefnum(diswidth,maxwidth)
  1335.              end;
  1336.  
  1337. { EC : Set escape character (= command character) }
  1338.  
  1339.         ec : begin
  1340.                skip;
  1341.                comchar := ch;
  1342.                getchar
  1343.              end;
  1344.  
  1345. { ED : End display }
  1346.  
  1347.         ed : if displaylevel > 0 then
  1348.                begin
  1349.                  if displaylevel = 1 then
  1350.                    begin
  1351.                      linespacing := savespacing;
  1352.                      setspacing(linespacing);
  1353.                      maxwidth := savewidth
  1354.                    end;
  1355.                  margin := margin - disindent;
  1356.                  break(afterdisp);
  1357.                  displaylevel := pred(displaylevel)
  1358.                end
  1359.              else error('not displaying');
  1360.  
  1361. { EF : End a floating keep.  If there are no keeps already in the queue
  1362.   and there is room on this page, then print the contents of the keep;
  1363.   otherwise put it in the queue.  }
  1364.  
  1365.         ef : if keeping then
  1366.                begin
  1367.                  putline;
  1368.                  keeping := false;
  1369.                  if (firstfloat <> nil) or
  1370.                     (keepcount + linesonpage > maxlines) and
  1371.                     (keepcount <= maxlines) then
  1372.                    begin
  1373.                      if freefloat = nil then new(float)
  1374.                      else
  1375.                        begin
  1376.                          float := freefloat;
  1377.                          freefloat := freefloat^.next
  1378.                        end;
  1379.                      float^.first := first;
  1380.                      float^.last := last;
  1381.                      float^.keepcount := keepcount;
  1382.                      float^.next := nil;
  1383.                      if firstfloat = nil then firstfloat := float
  1384.                      else lastfloat^.next := float;
  1385.                      lastfloat := float;
  1386.                      resetkeep
  1387.                    end
  1388.                  else endkeep;
  1389.                  pop(stack)
  1390.                end
  1391.              else error('not keeping');
  1392.  
  1393. { EK : End keep.  If there is room on the page, then print the keep;
  1394.   otherwise start a new page and then print it.  There may be floating
  1395.   keeps waiting to be printed and so we must go on skipping pages until
  1396.   there is enough space for the keep.  }
  1397.  
  1398.         ek : if keeping then
  1399.                begin
  1400.                  putline;
  1401.                  keeping := false;
  1402.                  if keepcount <= maxlines then
  1403.                  while keepcount + linesonpage > maxlines do
  1404.                    begin
  1405.                      endpage;
  1406.                      if not pageready then startpage
  1407.                    end; { while }
  1408.                  endkeep
  1409.                end
  1410.              else error('not keeping');
  1411.  
  1412. { EL : End a list of items }
  1413.  
  1414.         el : begin
  1415.                margin := 0;
  1416.                break(afterlist);
  1417.                putline;
  1418.                itemnum := 0;
  1419.                itemlist := false
  1420.              end;
  1421.  
  1422. { EP : End page }
  1423.  
  1424.         ep : if keeping then error('illegal in keep')
  1425.              else
  1426.                begin
  1427.                  putline;
  1428.                  endpage
  1429.                end;
  1430.  
  1431. { FL : Define new running footer.  The footer is terminated by
  1432.        EOL or command terminator.  No entry in table of contents. }
  1433.  
  1434.         fl: begin
  1435.               setlength(footer,0);
  1436.               skip;
  1437.               getline(footer)
  1438.             end;
  1439.  
  1440. { GP : Get page number from keyboard or parameter }
  1441.  
  1442.         gp : begin
  1443.                skip;
  1444.                if ch = query then
  1445.                  begin
  1446.                    getchar;
  1447.                    if pagenum = 0 then
  1448.                      begin
  1449.                        write('Enter page number: ');
  1450.                        read(num)
  1451.                      end
  1452.                    else num := succ(pagenum)
  1453.                  end
  1454.                else getnum(num);
  1455.                pagenum := pred(num)
  1456.              end;
  1457.  
  1458. { HL : Set horizontal layout parameters }
  1459.  
  1460.         hl : begin
  1461.                getdefnum(firstmargin,deffirstmargin);
  1462.                getdefnum(maxwidth,defmaxwidth)
  1463.              end;
  1464.  
  1465. { IC : Include named file }
  1466.  
  1467.         ic : begin
  1468.                setlength(infilename,0);
  1469.                skip;
  1470.                getline(infilename);
  1471.                if index(infilename,period) = 0 then infilename := (infilename+extin);
  1472.                process(infilename)
  1473.              end;
  1474.  
  1475. { IL : Set itemized list layout }
  1476.  
  1477.         il : begin
  1478.                getdefnum(beforeitem,succ(linespacing));
  1479.                getdefnum(afterlist,succ(linespacing));
  1480.                getdefnum(listindent,deflindent);
  1481.                getdefnum(listincr,deflincr)
  1482.              end;
  1483.  
  1484. { IM : Set immediate margin }
  1485.  
  1486.         im : begin
  1487.                count := length(line);
  1488.                getdefnum(num,count);
  1489.                if count >= num then putline;
  1490.                pad(line,pred(num));
  1491.                margin := num
  1492.              end;
  1493.  
  1494. { LI : List item.  Put item number and indent. }
  1495.  
  1496.         li : if itemlist then
  1497.                begin
  1498.                  itemnum := succ(itemnum);
  1499.                  margin := listindent;
  1500.                  break(beforeitem);
  1501.                  putline;
  1502.                  pad(line,margin);
  1503.                  putchar('(',false);
  1504.                  putnum(line,itemnum);
  1505.                  putchar(')',false);
  1506.                  margin := margin + listincr;
  1507.                  pad(line,pred(margin))
  1508.                end
  1509.              else error('not in list mode');
  1510.  
  1511. { LS : Set linespacing }
  1512.  
  1513.         ls : begin
  1514.                getdefnum(linespacing,deflinespacing);
  1515.                if (1 <= linespacing) and (linespacing <= 3) then
  1516.                  begin
  1517.                    setspacing(linespacing);
  1518.                    if spacing < linespacing then spacing := linespacing
  1519.                  end
  1520.                else error('value out of range')
  1521.              end;
  1522.  
  1523. { MR : make a cross-reference }
  1524.  
  1525.         mr : begin
  1526.                getkey(key);
  1527.                currkey := key;
  1528.                makentry(key,reftable)
  1529.              end;
  1530.  
  1531. { MV : Set minimum values for starting something near bottom of page }
  1532.  
  1533.         mv : begin
  1534.                getdefnum(minpara,defminpara);
  1535.                getdefnum(minsubsec,defminsubsec);
  1536.                getdefnum(minsec,defminsec)
  1537.              end;
  1538.  
  1539. { NU : Remove characters from underline set }
  1540.  
  1541.         nu : while ch <> chr(CR) do
  1542.                begin
  1543.                  uscharset := uscharset - [ch];
  1544.                  getchar
  1545.                end; { while }
  1546.  
  1547. { OV : Overlay next two characters }
  1548.  
  1549.         ov : begin
  1550.                skip;
  1551.                if suppressing then line := (line+blank);
  1552.                pad(overline,length(line));
  1553.                line := (line+ch);
  1554.                getchar;
  1555.                overline := (overline+ch);
  1556.                getchar
  1557.              end;
  1558.  
  1559. { PA : Start a new paragraph }
  1560.  
  1561.         pa : startpara(parspacing);
  1562.  
  1563. { PL : Set paragraph layout }
  1564.  
  1565.         pl : begin
  1566.                getdefnum(parspacing,defparspacing);
  1567.                getdefnum(parindent,defparindent)
  1568.              end;
  1569.  
  1570. { RB : Switch to retain blank mode }
  1571.  
  1572.         rb : if suppressing then
  1573.                begin
  1574.                  suppressing := false;
  1575.                  underlining := false
  1576.                end
  1577.              else error('occurred twice');
  1578.  
  1579. { RM : Put next word in right margin }
  1580.  
  1581.         rm : begin
  1582.                skip;
  1583.                getword(word);
  1584.                if length(line) + length(word) > maxwidth then putline;
  1585.                pad(line,maxwidth - length(word));
  1586.                line := (line+word)
  1587.              end;
  1588.  
  1589. { RR : Retrieve cross-reference data and print it }
  1590.  
  1591.         rr : begin
  1592.                skip;
  1593.                refcode := upper(ch);
  1594.                getchar;
  1595.                getkey(key);
  1596.                lookup(key,reftable,ref);
  1597.                setlength(word,0);
  1598.                if ref = nil then putnum(word,0)
  1599.                else
  1600.                  with ref ^ do
  1601.                    begin
  1602.                      entcount := succ(entcount);
  1603.                      case refcode of
  1604.                        'P' : putnum(word,pagenum);
  1605.                        'C' : putnum(word,chapnum);
  1606.                        'S' : putsecnum(word,chapnum,secnum,subsecnum);
  1607.                        'I' : putnum(word,itemnum)
  1608.                      end { case }
  1609.                    end;
  1610.                while not (ch in wordends) do
  1611.                  begin
  1612.                    word := (word+ch);
  1613.                    getchar
  1614.                  end;
  1615.                putword(word)
  1616.              end;
  1617.  
  1618. { SB : Switch to suppress blank and EOL mode }
  1619.  
  1620.         sb : if suppressing then error('occurred twice')
  1621.              else suppressing := true;
  1622.  
  1623. { SE : Start section }
  1624.  
  1625.         se : begin
  1626.                secnum := succ(secnum);
  1627.                subsecnum := 0;
  1628.                putsubhead(minsec,true)
  1629.              end;
  1630.  
  1631. { SI : Set item number }
  1632.  
  1633.         si : if itemlist then error('inside list')
  1634.              else
  1635.                begin
  1636.                  itemlist := true;
  1637.                  getnum(itemnum)
  1638.                end;
  1639.  
  1640. { SL : Set subheading layout }
  1641.  
  1642.         sl : begin
  1643.                getdefnum(beforehead,defbhead);
  1644.                getdefnum(afterhead,defahead)
  1645.              end;
  1646.  
  1647. { SM : Set left margin }
  1648.  
  1649.         sm : getdefnum(margin,length(line));
  1650.  
  1651. { SP : Force line break and write blank lines. }
  1652.  
  1653.         sp : begin
  1654.                getdefnum(count,linespacing);
  1655.                break(count);
  1656.                putline
  1657.              end;
  1658.  
  1659. { SS : Start subsection }
  1660.  
  1661.         ss : begin
  1662.                if secnum = 0 then error('no section');
  1663.                subsecnum := succ(subsecnum);
  1664.                putsubhead(minsubsec,true)
  1665.              end;
  1666.  
  1667. { SU : Start unnumbered section }
  1668.  
  1669.         su : putsubhead(minsec,false);
  1670.  
  1671. { TC : write a table of contents.  Linespacing in contents file
  1672.        is determined by LS setting when this command is executed. }
  1673.  
  1674.         tc : if contents then error('occurred twice')
  1675.              else
  1676.                begin
  1677.                  contents := true;
  1678.                  contsection := linespacing;
  1679.                  contchapter := 2 * linespacing;
  1680.                  changext(outfilename,extcon,contfilename);
  1681.                  assign(cont,contfilename); rewrite(cont);
  1682.                  setlength(conttitle,0);
  1683.                  skip;
  1684.                  if ch = '#' then
  1685.                    begin
  1686.                      pageintc := true;
  1687.                      getchar;
  1688.                      skip
  1689.                    end;
  1690.                  getline(conttitle);
  1691.                  putconttitle
  1692.                end;
  1693.  
  1694. { TL : Define new running title.  The title is terminated by
  1695.        EOL or command terminator.  Make an entry in the table
  1696.        of contents.  # will be translated to page number. }
  1697.  
  1698.         tl : begin
  1699.                setlength(title,0);
  1700.                skip;
  1701.                getline(title);
  1702.                if contents then putcontline(contchapter,contmargin,title)
  1703.              end;
  1704.  
  1705. { TS : Set tab spacing }
  1706.  
  1707.         ts : getdefnum(tabgap,deftabgap);
  1708.  
  1709. { UL : Add characters to underline set }
  1710.  
  1711.         ul : while ch <> chr(CR) do
  1712.                begin
  1713.                  if ch <> blank then uscharset := uscharset + [ch];
  1714.                  getchar
  1715.                end; { while }
  1716.  
  1717. { VL : Set vertical layout parameters }
  1718.  
  1719.         vl : begin
  1720.                getdefnum(leadin,defleadin);
  1721.                getdefnum(maxlines,defmaxlines);
  1722.                getdefnum(lastline,deflastline);
  1723.                getdefnum(chapgap,defchapgap)
  1724.              end;
  1725.  
  1726. { ZR : Show references in left margin }
  1727.  
  1728.         zr : showrefs := true;
  1729.  
  1730.         end; { case }
  1731.       skip
  1732.     end
  1733. end; { command }
  1734.  
  1735. { ----------------- Main text processing loop ------------------------------ }
  1736.  
  1737. { If suppressing is true (usual case) the input text is processed
  1738.   word by word.  If suppressing is false the text is processed
  1739.   character by character. }
  1740.  
  1741. begin { process }
  1742.  
  1743.   assign(input,infilename);
  1744.   {$I-}
  1745.   reset(input);
  1746.   {$I+}
  1747.   if Ioresult <> 0 then
  1748.     begin
  1749.       writeln('File ', infilename, ' not found.  Aborting');
  1750.     end
  1751.   else
  1752.     begin
  1753.       writeln(infilename,' opened for input.');
  1754.       inlinecount := 0;
  1755.       getchar;
  1756.  
  1757.       while ch <> chr(0) do
  1758.         begin
  1759.           while ch = comchar do command;
  1760.           if suppressing then
  1761.             if ch in wordends then getchar
  1762.             else
  1763.               begin
  1764.                 getword(word);
  1765.                 putword(word)
  1766.               end
  1767.           else { retaining blanks and line breaks }
  1768.             begin
  1769.               if ch in wordends then
  1770.                 begin
  1771.                   wordcount := succ(wordcount);
  1772.                   underlining := false
  1773.                 end;
  1774.               if ch = chr(CR) then putline
  1775.               else if ch = chr(TAB) then
  1776.                 repeat line := (line+blank) until length(line) mod tabgap = 0
  1777.               else if (ch = underscore) and not underlining then underlining := true
  1778.               else putchar(ch,underlining);
  1779.               write(ch);
  1780.               getchar
  1781.             end
  1782.         end; { while }
  1783.  
  1784.       writeln(infilename,' closed on page ',pagenum:1,'; ',
  1785.       inlinecount:1,' lines read.');
  1786.       close(input);
  1787.   end;
  1788.  
  1789. end; { process }
  1790.  
  1791. { ------------------------------- Main program ----------------------------- }
  1792.  
  1793. begin
  1794.  
  1795. { Read file names from command line }
  1796.  
  1797.   infilename := CommandLineArgument('Input file name: ','/',False);
  1798.   Temp:=infilename;
  1799.   If Pos('.',Temp)<>0 Then Delete(Temp,Pos('.',Temp),length(Temp));
  1800.   outfilename := CommandLineArgument('Output file name: ',Temp+extout,False);
  1801.   if length(infilename) = 0 then writeln('No input file.')
  1802.   else
  1803.     begin
  1804.  
  1805. { Read cross-reference file.  This must be done before global variables
  1806.   are initialized because it changes some of them.  }
  1807.  
  1808.       readrefs;
  1809.  
  1810. { Initialize keep space }
  1811.  
  1812.       freelist := nil;
  1813.       stack := nil;
  1814.       resetkeep;
  1815.       firstfloat := nil;
  1816.       lastfloat := nil;
  1817.       freefloat := nil;
  1818.  
  1819. { Initialize sets.  The underline character set contains all characters
  1820.   except the common punctuation characters; this is to prevent the
  1821.   underlining of a punctuation character that follows an underlined word.
  1822.   Blank and rubout cannot be underlined.  See \UL and \NU. }
  1823.  
  1824.       wordends := [blank,chr(0),chr(CR),chr(TAB)];
  1825.       uscharset := [chr(33)..chr(126)] - [',','.',';',':','!','?','-','_'];
  1826.  
  1827. { Initialize flags }
  1828.  
  1829.       suppressing := true;
  1830.       pageready := false;
  1831.       keeping := false;
  1832.       contents := false;
  1833.       pageintc := false;
  1834.       itemlist := false;
  1835.       underlining := false;
  1836.       printwarning := false;
  1837.       showrefs := false;
  1838.  
  1839. { Initialize counters and parameters  }
  1840.  
  1841.       linesonpage := 0;
  1842.       pagenum := 0;
  1843.       wordcount := 0;
  1844.       chapnum := 0;
  1845.       secnum := 0;
  1846.       subsecnum := 0;
  1847.       contpage := 0;
  1848.       pagecount := 0;
  1849.       margin := 0;
  1850.       spacesdone := 0;
  1851.       errorcount := 0;
  1852.       itemnum := 0;
  1853.       displaylevel := 0;
  1854.       spaceleft := maxint;
  1855.  
  1856. { Set defaults }
  1857.  
  1858.       comchar := '\';                   { Default command character }
  1859.  
  1860. { Set horizontal defaults }
  1861.  
  1862.       firstmargin := deffirstmargin;    { Nothing can be printed left of this }
  1863.       maxwidth    := defmaxwidth;       { Width of text on page; 6.5" at 12 cpi }
  1864.       parindent   := defparindent;      { Paragraph indentation }
  1865.       tabgap      := deftabgap;         { Tabs at X where X mod tabgap = 0 }
  1866.       diswidth    := maxwidth;          { Default length of displyed lines }
  1867.       disindent   := defdisindent;      { Display indentation }
  1868.       listindent  := deflindent;        { Indentation for a numbered list }
  1869.       listincr    := deflincr;          { Additional indentation for list items }
  1870.  
  1871. { Set vertical defaults }
  1872.  
  1873.       leadin      := defleadin;         { Lines between running header and text }
  1874.       maxlines    := defmaxlines;       { Maximum # of text lines on a page:
  1875.                                             8.5" at 6 lpi }
  1876.       lastline    := deflastline;       { Line #, relative to start of text,
  1877.                                             for footer }
  1878.       linespacing := deflinespacing;    { Normal spacing between lines }
  1879.       dispspacing := linespacing;       { Line spacing in a display }
  1880.       parspacing  := defparspacing;     { Lines before a paragraph }
  1881.       beforehead  := defbhead;          { Lines before a heading }
  1882.       afterhead   := defahead;          { Lines after a heading }
  1883.       beforedisp  := defbdisp;          { Lines before a display }
  1884.       afterdisp   := defadisp;          { Lines after a display }
  1885.       beforeitem  := succ(deflinespacing);  { Lines before a list item }
  1886.       afterlist   := succ(deflinespacing);  { Lines after an itemized list }
  1887.       chapgap     := defchapgap;        { Lines before first line of chapter }
  1888.       minpara     := defminpara;        { Limit for starting paragraph }
  1889.       minsubsec   := defminsubsec;      { Limit for starting subsection }
  1890.       minsec      := defminsec;         { Limit for starting section }
  1891.  
  1892. { Initialize line buffers and strings }
  1893.  
  1894.       resetline;
  1895.       setlength(title,0);
  1896.       setlength(footer,0);
  1897.       setlength(currkey,0);
  1898.  
  1899. { Define code mnemonic table }
  1900.  
  1901.       codetable[bd] := 'BD'; codetable[bf] := 'BF'; codetable[bk] := 'BK';
  1902.       codetable[cc] := 'CC'; codetable[ce] := 'CE'; codetable[cx] := 'CH';
  1903.       codetable[co] := 'CO'; codetable[dl] := 'DL'; codetable[ec] := 'EC';
  1904.       codetable[ed] := 'ED'; codetable[ef] := 'EF'; codetable[ek] := 'EK';
  1905.       codetable[el] := 'EL'; codetable[ep] := 'EP'; codetable[fl] := 'FL';
  1906.       codetable[gp] := 'GP'; codetable[hl] := 'HL'; codetable[ic] := 'IC';
  1907.       codetable[il] := 'IL'; codetable[im] := 'IM'; codetable[li] := 'LI';
  1908.       codetable[ls] := 'LS'; codetable[mr] := 'MR'; codetable[mv] := 'MV';
  1909.       codetable[nu] := 'NU'; codetable[ov] := 'OV';
  1910.       codetable[pa] := 'PA'; codetable[pl] := 'PL'; codetable[rb] := 'RB';
  1911.       codetable[rm] := 'RM'; codetable[rr] := 'RR'; codetable[sb] := 'SB';
  1912.       codetable[se] := 'SE'; codetable[si] := 'SI'; codetable[sl] := 'SL';
  1913.       codetable[sm] := 'SM'; codetable[sp] := 'SP'; codetable[ss] := 'SS';
  1914.       codetable[su] := 'SU'; codetable[tc] := 'TC'; codetable[tl] := 'TL';
  1915.       codetable[ts] := 'TS'; codetable[ul] := 'UL'; codetable[vl] := 'VL';
  1916.       codetable[zr] := 'ZR'; codetable[zz] := 'ZZ';
  1917.  
  1918. { Open the output file }
  1919.  
  1920.       writeln(outfilename,' opened for output.');
  1921.       assign(output,outfilename);
  1922.       rewrite(output);
  1923.  
  1924. { Process the input file }
  1925.  
  1926.       process(infilename);
  1927.       endchap;
  1928.       if contents then endcontpage;
  1929.       if reftable <> nil then writerefs;
  1930.       close(output);
  1931.  
  1932. { Display the results }
  1933.  
  1934.       writeln(outfilename,': ',pagecount:1,' pages; ',wordcount:1,' words.');
  1935.       if contpage > 0 then writeln(contfilename,': ',contpage:1,' pages.');
  1936.       if MemAvail > 0 then writeln('Free memory: ',MemAvail:1,' bytes.');
  1937.       if errorcount > 0 then writeln('Errors: ',errorcount:1,'.');
  1938.       if printwarning then
  1939.         begin
  1940.           writeln;
  1941.           writeln('WARNING: the output file contains printer control characters!')
  1942.         end
  1943.     end
  1944. end. { TP }
  1945.