home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / edit / tp / tp.pas
Encoding:
Pascal/Delphi Source File  |  1994-04-24  |  53.6 KB  |  1,947 lines

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