{ Text Processor } { Author: Peter Grogono } {$E- No statement numbers } {$F- No real overflow/underflow checking (no reals used anyway) } {$M- No integer multiply/divide check } {$R- No range and bounds checking } {$S+ Check stack overflow because dynamic storage is used } {$U- No range and bounds checking of parameters } program TP; const {$ICONSTS.PAS } { Strings } extin = '.TEX'; { Default input file extension } extout = '.DOC'; { Default output file extension } extcon = '.CON'; { Extension for contents file } extref = '.REF'; { Extension for cross-reference file } period = '.'; { End of } query = '?'; { sentence } shriek = '!'; { markers } sentgap = ' '; { Two blanks at end of sentence } secgap = ' '; { Two blanks after a section number } hardblank = '`'; { Non-trivial blank } underscore = '_'; { Underlining character } concat = '-'; { Concatenation character } pagechar = '#'; { Translates to page number in titles } { String lengths. The most important of these is maxlinelen, which determines the maximum possible length of a line of text. When keeping blocks of text, TP uses more than 2 * maxlinelen bytes of memory for each line. Consequently you can reduce the dynamic storage requirements by reducing the value of maxlinelen, if your lines will never be as long as 120 characters. } namelen = 14; { CP/M file name length } maxlinelen = 120; { Maximum length of text line } maxkeylen = 4; { Maximum length of cross-reference key } { For default values not defined here, see the initialization section at the end of the listing. } { Horizontal control } deffirstmargin = 6; { Nothing can be printed left of this } defmaxwidth = 78; { Width of text on page: 6.5" at 12 cpi } deflindent = 5; { Indentation for list numbers } deflincr = 6; { Additional indentation for list items } defparindent = 5; { Indentation at start of paragraph } defdisindent = 10; { Indentation for displays } deftabgap = 8; { Tabs at 8, 16, 24, ... } numpos = 70; { Position for page # in table of contents } contmargin = 6; { Left margin for contents file } contindent = 8; { Indentation for contents file } { Vertical control } defleadin = 3; { Lines between header and text } defmaxlines = 52; { Maximum number of text lines on a page: 8.7" at 6 lpi } deflinespacing = 2; { Default line spacing } defparspacing = 4; { Blank lines between paragraphs } defbhead = 6; { Blank lines before a subheading } defahead = 4; { Blank lines after a subheading } defbdisp = 3; { Blank lines before a display } defadisp = 3; { Blank lines after a display } defchapgap = 20; { Blank lines after a chapter heading } deflastline = 55; { Position of footer, relative to start of text } defminpara = 4; { These three constants are used to avoid } defminsubsec = 8; { starting something new near the bottom of } defminsec = 8; { of a page } contpagsize = 52; { Line on a page on the contents file } contlastline = 55; { Line # for page # in contents file } contleadin = 3; { Line feeds at top of contents page } type {$ITYPES.PAS } filename = string namelen; linetype = string maxlinelen; pair = array [1..2] of char; { A linerecord stores a line and the environment in which it must be formatted. TP stores a block of text to be 'kept' as a linked list of line records. Line records are also used by the procedures PUSH and POP to save an environment. A floatrecord is used to store an entire block of text until it is required for output. TP maintains unprinted floating keeps as a linked list of floatrecords. There is a global variable corresponding to each field of these records. It would be better programming practice to acknowledge this by using global records rather than separate variables. This, however, would (1) make the program larger because of the offset addressing required; (2) make the program slower for the same reason; and (3) penalize users who are not using the features which require dynamic storage. } lineptr = ^ linerecord; linerecord = record suppressing, textonline, breakline : boolean; line, overline : linetype; spacing : byte; next : lineptr end; { linerecord } floatptr = ^ floatrecord; floatrecord = record first, last : lineptr; keepcount : byte; next : floatptr end; { floatrecord } { Cross-reference types } keytype = string maxkeylen; refptr = ^ refrecord; refrecord = record key : keytype; pagenum : integer; chapnum, secnum, subsecnum, itemnum, entcount : byte; left, right : refptr end; { refrecord } { Internal command codes. AA and ZZ are dummies } codetype = (aa,bd,bf,bk,cc,ce,cx,co,ec,dl,ed,ef,ek,el,ep, fl,gp,hl,ic,il,im,li,ls,mr,mv,nu,ov,pa,pl,rb,rm, rr,sb,se,si,sl,sm,sp,ss,su,tc,tl,ts,ul,vl,zr,zz); var { Files } infilename, outfilename, contfilename, refilename : filename; output, cont : text; { Line buffers } title, footer, line, overline : linetype; { Command character } comchar : char; { Horizontal control } maxwidth, firstmargin, margin, tabgap, parindent, disindent, listindent, listincr : byte; textonline, suppressing : boolean; { Vertical control } linesonpage, spacesdone, linespacing, spacing, minpara, minsec, minsubsec, leadin, maxlines, lastline, parspacing, chapgap, beforehead, afterhead, beforedisp, afterdisp, beforeitem, afterlist : byte; breakline, pageready : boolean; { Table of contents } conttitle : linetype; contlines, contpage, contchapter, contsection : byte; contents, pageintc : boolean; { Cross-references } reftable : refptr; showrefs : boolean; currkey : keytype; entcount : byte; { Section numbering } pagenum : integer; chapnum, secnum, subsecnum : byte; { Keeps and floating keeps } freelist, first, last, stack : lineptr; firstfloat, lastfloat, freefloat : floatptr; keepcount : byte; keeping : boolean; { Displays } displaylevel, dispspacing, savespacing, diswidth, savewidth : byte; { Itemized lists } itemnum : byte; itemlist : boolean; { Underlining } uscharset : set of char; underlining : boolean; { Special printer codes } printwarning : boolean; { Miscellaneous counters } spaceleft, wordcount, pagecount : integer; errorcount : byte; { Constant tables and sets } codetable : array [codetype] of pair; wordends : set of char; {$IPROCS.PAS } {$IGETFILES.PAS } { Convert lower case letters to upper case } function upper (ch : char) : char; begin if ch in ['a'..'z'] then upper := chr(ord(ch) - ord('a') + ord('A')) else upper := ch end; { upper } { Create a new file name from a given file name and the extension EXT. } procedure changext (inname : filename; ext : string255; var name : filename); begin name := inname; setlength(name,pred(index(name,period))); append(name,ext) end; { changext } { ---------------------- Cross-reference procedures ------------------------ } { Store current global values into specified entry. } procedure update (ref : refptr); begin ref^.pagenum := pagenum; ref^.chapnum := chapnum; ref^.secnum := secnum; ref^.subsecnum := subsecnum; ref^.itemnum := itemnum end; { update } { Make a new entry or update an old entry in the cross-reference table. } procedure makentry (key : keytype; var ref : refptr); begin if ref = nil then begin new(ref); ref^.left := nil; ref^.right := nil; ref^.key := key; ref^.entcount := 0; update(ref) end else if key < ref^.key then makentry(key,ref^.left) else if key > ref^.key then makentry(key,ref^.right) else update(ref) { old entry } end; { makentry } { Look up an entry in the table, given the key. } procedure lookup (key : keytype; root : refptr; var ref : refptr); begin if root = nil then ref := nil else if key < root^.key then lookup(key,root^.left,ref) else if key > root^.key then lookup(key,root^.right,ref) else ref := root end; { lookup } { Write cross-reference table to a file. } procedure writerefs; var refile : text; { Write a sub-tree of entries to the file. The sub-tree is traversed in pre-order so that re-reading the file will not create a degenerate tree. } procedure putentry (ref : refptr); begin if ref <> nil then with ref ^ do begin writeln(refile,key,pagenum:6,chapnum:4,secnum:4, subsecnum:4,itemnum:4,entcount:4); putentry(left); putentry(right) end end; { putentry } begin { writerefs } changext(infilename,extref,refilename); rewrite(refilename,refile); putentry(reftable) end; { writerefs } { Read a file of cross-references. } procedure readrefs; var refile : text; key : keytype; ch : char; begin reftable := nil; changext(infilename,extref,refilename); reset(refilename,refile); while not eof(refile) do begin setlength(key,0); read(refile,ch); while ch <> blank do begin append(key,ch); read(refile,ch) end; { while } readln(refile,pagenum,chapnum,secnum,subsecnum,itemnum); pad(key,maxkeylen); makentry(key,reftable) end { while } end; { readrefs } procedure putline; forward; { --------------------- Free store and keep management --------------------- } { The next three procedures handle dynamic storage of lines. There is a stack for saving environments and a queue for storing 'kept' text. The procedure POP is used to remove a line from the stack or the queue. The procedure SAVE is used to insert a line into the stack or the queue, it does not do the pointer updating because it doesn't know whether the line is to go at the back of a queue or the front of a list. } procedure save (var ptr : lineptr); begin if freelist = nil then new(ptr) else begin ptr := freelist; freelist := freelist^.next end; ptr^.suppressing := suppressing; ptr^.textonline := textonline; ptr^.breakline := breakline; ptr^.line := line; ptr^.overline := overline; ptr^.spacing := spacing end; { save } procedure push; var ptr : lineptr; begin save(ptr); ptr^.next := stack; stack := ptr end; { push } procedure pop (var ptr : lineptr); var old : lineptr; begin suppressing := ptr^.suppressing; textonline := ptr^.textonline; breakline := ptr^.breakline; line := ptr^.line; overline := ptr^.overline; spacing := ptr^.spacing; old := ptr; ptr := ptr^.next; old^.next := freelist; freelist := old end; { pop } { Reset the keep pointers and count. This procedure does not affect the contents of the keep queue. } procedure resetkeep; begin first := nil; last := nil; keepcount := 0 end; { resetkeep } { Put a line of text into a keep buffer } procedure keep; var ptr : lineptr; begin save(ptr); keepcount := keepcount + spacing; if first = nil then first := ptr else last^.next := ptr; last := ptr; ptr^.next := nil end; { keep } { End a keep. Write kept lines to output file. } procedure endkeep; var ptr : lineptr; begin ptr := first; resetkeep; while ptr <> nil do begin pop(ptr); putline end { while } end; { endkeep } { ------------------------- Table of Contents management ------------------- } { Write a title in the contents file } procedure putconttitle; var count : byte; begin writeln(cont,chr(FF)); writeln(cont,blank:contmargin,conttitle); for count := 1 to contleadin do writeln(cont); contpage := succ(contpage); contlines := 0 end; { putcontitle } { End a page of the contents file } procedure endcontpage; begin while contlines < contlastline do begin writeln(cont); contlines := succ(contlines) end; { while } writeln(cont,blank:numpos,'C-',contpage:1) end; { endcontpage } { Write blank lines followed by title or section name to contents file; start a new page when necessary. } procedure putcontline (lines, indent : byte; line : linetype); var count : byte; ch : char; begin if contlines + lines > contpagsize then begin endcontpage; putconttitle end else begin for count := 1 to lines do writeln(cont); contlines := contlines + lines end; write(cont,blank:indent); for count := 1 to length(line) do begin ch := line[count]; if ch = hardblank then write(cont,blank) else write(cont,ch) end; { for } if pageintc then write(cont,blank:3,pagenum:1) end; { putcontline } { -------------------------- Page layout ----------------------------------- } { Write a running header or footer } procedure writerunner (runner : linetype); var i : byte; ch : char; begin write(output,blank:firstmargin); for i := 1 to length(runner) do begin ch := runner[i]; if ch = hardblank then write(output,blank) else if ch = pagechar then write(output,pagenum:1) else write(output,ch) end; { for } writeln(output) end; { writerunner } { Start a new page and write header on it. If there are any floating keeps in the list, as many are printed as will fit on the page. When a floating keep has been printed out the memory that it occupied is reclaimed. } procedure startpage; var count : byte; float : floatptr; done : boolean; begin writeln(output,chr(FF)); writerunner(title); for count := 1 to leadin do writeln(output); pagenum := succ(pagenum); pagecount := succ(pagecount); linesonpage := 0; pageready := true; done := false; repeat if firstfloat = nil then done := true else begin count := firstfloat^.keepcount; if (count + linesonpage > maxlines) and (count <= maxlines) then done := true { Not enough space } else begin push; first := firstfloat^.first; last := firstfloat^.last; keepcount := count; endkeep; float := firstfloat; firstfloat := float^.next; float^.next := freefloat; freefloat := float; pop(stack) end end until done end; { startpage } { End a page by filling it with blank lines and writing footer } procedure endpage; begin if pageready then begin while linesonpage < lastline do begin writeln(output); linesonpage := succ(linesonpage) end; { while } writerunner(footer); pageready := false end end; { endpage } { Any floating keeps must be released at the end of a chapter and at the end of the text. } procedure endchap; begin putline; endpage; while firstfloat <> nil do begin startpage; endpage end { while } end; { endchap } { -------------------------- Output management ----------------------------- } { Initialize the current line } procedure resetline; begin setlength(line,0); setlength(overline,0); spacing := linespacing; textonline := false; breakline := false end; { resetline } { Output a completed line. Where the line goes depends on whether we are "keeping" or not. Output blank lines after the line according to the value of SPACING. Reset the line buffers. } procedure putline; var ch : char; count : byte; { Write the left margin. No user text can appear in margin, but it is used for cross-reference entries if \ZR is called. } procedure writemargin; begin if showrefs and (length(currkey) > 0) then begin write(output,currkey,blank:firstmargin - maxkeylen); setlength(currkey,0) end else write(output,blank:firstmargin) end; { writemargin } begin { putline } if keeping then keep else begin if textonline or not suppressing then begin if linesonpage >= maxlines then endpage; if not pageready then startpage; writemargin; for count := 1 to length(line) do begin ch := line[count]; if ch = hardblank then write(output,blank) else write(output,ch) end; { for } if length(overline) > 0 then begin write(output,chr(CR)); writemargin; write(output,overline) end; spacesdone := 0 end; while (spacesdone < spacing) and (linesonpage < maxlines) do begin writeln(output); linesonpage := succ(linesonpage); spacesdone := succ(spacesdone) end; { while } end; resetline end; { putline } { Append one character to a line. Start a new line if necessary. Underline the character if UNDERLINING is true and the character is in the underline set. } procedure putchar (ch : char; underlining : boolean); begin if breakline or (length(line) >= maxwidth) then putline; if not textonline then pad(line,margin); append(line,ch); if underlining and (ch in uscharset) then begin pad(overline,pred(length(line))); append(overline,underscore) end; textonline := true end; { putchar } { Append a positive number to the line buffer without leading or trailing blanks. } procedure putnum (var line : string0; num : integer); var buf : array [1..5] of char; bp, cp : byte; begin bp := 0; repeat bp := succ(bp); buf[bp] := chr(num mod 10 + ord('0')); num := num div 10 until num = 0; for cp := bp downto 1 do append(line,buf[cp]) end; { putnum } { Append a section number to a line } procedure putsecnum (var line : string0; chapnum, secnum, subsecnum : integer); var trailing : boolean; begin trailing := false; if chapnum > 0 then begin putnum(line,chapnum); trailing := true end; if secnum > 0 then begin if trailing then append(line,period); putnum(line,secnum); trailing := true end; if subsecnum > 0 then begin if trailing then append(line,period); putnum(line,subsecnum) end end; { putsecnum } { Append a word to the line buffer. Separate words by: 0 blanks if CONCAT character is last but not only character; 2 blanks if end of sentence; 1 blank otherwise. If first character is underscore then underline entire word. } procedure putword (word : string255); var ch, lastchar : char; wordlen, linelen, count : byte; space : integer; underline, concatenate, sentend : boolean; begin linelen := length(line); if linelen = 0 then begin lastchar := blank; sentend := false; concatenate := false end else begin lastchar := line[linelen]; if (lastchar = concat) and (linelen > 1) and (line[pred(linelen)] <> blank) and (line[pred(linelen)] <> concat) then begin sentend := false; concatenate := true; setlength(line,pred(linelen)) end else begin sentend := lastchar in [period,query,shriek]; concatenate := false end end; wordlen := length(word); underline := (wordlen > 1) and (word[1] = underscore); if underline then wordlen := pred(wordlen); space := maxwidth - linelen - wordlen; if breakline or (sentend and (space <= 6)) or (not sentend and (space <= 1)) then putline; if textonline then begin if sentend then append(line,sentgap) else if not concatenate then append(line,blank) end else pad(line,margin); if underline then begin pad(overline,length(line)); for count := 2 to succ(wordlen) do begin ch := word[count]; append(line,ch); if ch in uscharset then append(overline,underscore) else append(overline,blank) end { for } end else append(line,word); textonline := true; wordcount := succ(wordcount) end; { putword } { Record the need to break a line, and the blank space needed after it } procedure break (spaceneeded : byte); begin breakline := true; if spaceneeded > spacing then spacing := spaceneeded end; { break } { -------------------------- Text Processing ------------------------------- } { Process a file of text. This procedure calls itself recursively to process included files. Global variables are maintained while an included file is processed, but variables local to this procedure are saved implicitly on the stack until the included file has been processed, and are then restored. } procedure process (infilename : filename); var input : text; word : linetype; ch : char; inlinecount : integer; { Get a character from the input file. Translate EOF to NUL (0) and EOL to CR. Count lines read. } procedure getchar; begin if eof(input) then ch := chr(0) else if eoln(input) then begin read(input,ch); ch := chr(CR); inlinecount := succ(inlinecount) end else read(input,ch) end; { getchar } { Get a word from the input file. The first character is already in ch. A word is terminated by blank, EOL, EOF, or TAB. } procedure getword (var word : string0); begin setlength(word,0); repeat append(word,ch); getchar until ch in wordends end; { getword } { Read and store text up to the end of the input line } procedure getline (var line : string0); begin while ch <> chr(CR) do begin append(line,ch); getchar end { while } end; { getline } { ------------------------- Command decoder ------------------------- } { Called when comchar is encountered in text. } procedure command; var infilename : filename; cmd : pair; code : codetype; count : byte; word : linetype; num : integer; key : keytype; ref : refptr; refcode : char; float : floatptr; { Report an error } procedure error (message : string255); begin writeln('Line ',inlinecount:1,', command ',codetable[code],': ',message); errorcount := succ(errorcount) end; { error } { Skip over blanks } procedure skip; begin while ch = blank do getchar end; { skip } { Read an unsigned integer. Skip leading blanks. Any non-digit terminates the number. } procedure getnum (var num : integer); begin num := 0; skip; while ch in ['0'..'9'] do begin num := 10 * num + ord(ch) - ord('0'); getchar end { while } end; { getnum } { Read a number. The following cases are handled: NNN return value of NNN; = return DEFAULT; +NNN return DEFAULT + NNN; -NNN return DEFAULT - NNN. } procedure getdefnum (var num : integer; default : integer); var mode : (plus, minus, abs); begin skip; if ch = '+' then begin mode := plus; getchar end else if ch = '-' then begin mode := minus; getchar end else mode := abs; getnum(num); if (num = 0) and (ch = '=') then begin num := default; getchar end else case mode of plus : num := default + num; minus : num := default - num; abs : end { case } end; { getdefnum } { Read a cross-reference key } procedure getkey (var key : string0); begin setlength(key,0); skip; while ch in ['a'..'z','A'..'Z','0'..'9'] do begin if length(key) < maxkeylen then append(key,ch); getchar end; { while } pad(key,maxkeylen) end; { getkey } { Set vertical spacing parameters based on the value of linespacing } procedure setspacing (linespacing : byte); begin parspacing := 2 * linespacing; beforehead := 3 * linespacing; afterhead := 2 * linespacing; beforedisp := succ(linespacing); afterdisp := succ(linespacing); beforeitem := succ(linespacing); afterlist := succ(linespacing); dispspacing := linespacing end; { setspacing } { This procedure is called when the command processor encounters a command character that is not followed by a letter; ch contains the character following the command character. } procedure putcomchar; var word : linetype; begin if suppressing then if ch in wordends then putword(comchar) else begin setlength(word,0); append(word,comchar); repeat append(word,ch); getchar until ch in wordends; putword(word) end else putchar(comchar,underlining) end; { putcomchar } { Check amount of space on page and start a new page if necessary. No effect in keep mode. } procedure check (linesneeded : byte); begin if not keeping then begin if linesonpage + linesneeded > maxlines then endpage; if not pageready then startpage end end; { check } { Start a new paragraph, on a new page if necessary. } procedure startpara (spaceneeded : byte); begin break(spaceneeded); putline; check(minpara); pad(line,margin + parindent) end; { startpara } { Write a subheading. Write chapter number, section number, subsection number if > 0, title. Title is terminated by EOL or command terminator. Start a new paragraph. } procedure putsubhead (min : byte; numbered : boolean); var word : linetype; begin break(beforehead); putline; check(min); setlength(word,0); if numbered then begin putsecnum(word,chapnum,secnum,subsecnum); if length(word) > 0 then begin append(word,secgap); putword(word) end end; skip; while ch <> chr(CR) do begin getword(word); skip; putword(word) end; { while } if contents and numbered then putcontline(contsection,contmargin+contindent,line); startpara(afterhead) end; { putsubhead } { ---------------------- Command processor --------------------------------- } begin { command } getchar; if not (ch in ['a'..'z','A'..'Z']) then putcomchar else begin cmd[1] := upper(ch); getchar; cmd[2] := upper(ch); getchar; code := zz; codetable[aa] := cmd; while codetable[code] <> cmd do code := pred(code); case code of { Illegal commands } aa, zz : error('invalid command code'); { BD : Begin display } bd : begin margin := margin + disindent; break(beforedisp); displaylevel := succ(displaylevel); if displaylevel = 1 then begin savespacing := linespacing; linespacing := dispspacing; setspacing(linespacing); savewidth := maxwidth; maxwidth := diswidth end end; { BF : Begin floating keep } bf : if keeping then error('already keeping') else begin push; resetline; keeping := true; keepcount := 0 end; { BK : Begin keep } bk : if keeping then error('already keeping') else begin break(0); putline; keeping := true end; { CC : Printer control characters } cc : begin skip; while ch in ['0'..'9'] do begin getnum(num); skip; if (1 <= num) and (num <= 31) then write(output,chr(num)) else begin error('invalid control character'); getchar end end; { while } printwarning := true end; { CE : Print one line centered } ce : begin break(0); putline; setlength(word,0); skip; getline(word); for count := 1 to (maxwidth - length(word)) div 2 do append(line,blank); append(line,word); textonline := true; putline end; { CH : Start a new chapter } cx : begin if keeping then error('floating or keeping'); endchap; chapnum := succ(chapnum); secnum := 0; subsecnum := 0; setlength(title,0); putnum(title,chapnum); append(title,'. '); skip; getline(title); startpage; startpara(chapgap); if contents then putcontline(contchapter,contmargin,title) end; { CO : Comment } co : while ch <> chr(CR) do getchar; { DL : Set display layout } dl : begin getdefnum(beforedisp,defbdisp); getdefnum(afterdisp,defadisp); getdefnum(dispspacing,linespacing); getdefnum(disindent,defdisindent); getdefnum(diswidth,maxwidth) end; { EC : Set escape character (= command character) } ec : begin skip; comchar := ch; getchar end; { ED : End display } ed : if displaylevel > 0 then begin if displaylevel = 1 then begin linespacing := savespacing; setspacing(linespacing); maxwidth := savewidth end; margin := margin - disindent; break(afterdisp); displaylevel := pred(displaylevel) end else error('not displaying'); { EF : End a floating keep. If there are no keeps already in the queue and there is room on this page, then print the contents of the keep; otherwise put it in the queue. } ef : if keeping then begin putline; keeping := false; if (firstfloat <> nil) or (keepcount + linesonpage > maxlines) and (keepcount <= maxlines) then begin if freefloat = nil then new(float) else begin float := freefloat; freefloat := freefloat^.next end; float^.first := first; float^.last := last; float^.keepcount := keepcount; float^.next := nil; if firstfloat = nil then firstfloat := float else lastfloat^.next := float; lastfloat := float; resetkeep end else endkeep; pop(stack) end else error('not keeping'); { EK : End keep. If there is room on the page, then print the keep; otherwise start a new page and then print it. There may be floating keeps waiting to be printed and so we must go on skipping pages until there is enough space for the keep. } ek : if keeping then begin putline; keeping := false; if keepcount <= maxlines then while keepcount + linesonpage > maxlines do begin endpage; if not pageready then startpage end; { while } endkeep end else error('not keeping'); { EL : End a list of items } el : begin margin := 0; break(afterlist); putline; itemnum := 0; itemlist := false end; { EP : End page } ep : if keeping then error('illegal in keep') else begin putline; endpage end; { FL : Define new running footer. The footer is terminated by EOL or command terminator. No entry in table of contents. } fl: begin setlength(footer,0); skip; getline(footer) end; { GP : Get page number from keyboard or parameter } gp : begin skip; if ch = query then begin getchar; if pagenum = 0 then begin write('Enter page number: '); read(num) end else num := succ(pagenum) end else getnum(num); pagenum := pred(num) end; { HL : Set horizontal layout parameters } hl : begin getdefnum(firstmargin,deffirstmargin); getdefnum(maxwidth,defmaxwidth) end; { IC : Include named file } ic : begin setlength(infilename,0); skip; getline(infilename); if index(infilename,period) = 0 then append(infilename,extin); process(infilename) end; { IL : Set itemized list layout } il : begin getdefnum(beforeitem,succ(linespacing)); getdefnum(afterlist,succ(linespacing)); getdefnum(listindent,deflindent); getdefnum(listincr,deflincr) end; { IM : Set immediate margin } im : begin count := length(line); getdefnum(num,count); if count >= num then putline; pad(line,pred(num)); margin := num end; { LI : List item. Put item number and indent. } li : if itemlist then begin itemnum := succ(itemnum); margin := listindent; break(beforeitem); putline; pad(line,margin); putchar('(',false); putnum(line,itemnum); putchar(')',false); margin := margin + listincr; pad(line,pred(margin)) end else error('not in list mode'); { LS : Set linespacing } ls : begin getdefnum(linespacing,deflinespacing); if (1 <= linespacing) and (linespacing <= 3) then begin setspacing(linespacing); if spacing < linespacing then spacing := linespacing end else error('value out of range') end; { MR : make a cross-reference } mr : begin getkey(key); currkey := key; makentry(key,reftable) end; { MV : Set minimum values for starting something near bottom of page } mv : begin getdefnum(minpara,defminpara); getdefnum(minsubsec,defminsubsec); getdefnum(minsec,defminsec) end; { NU : Remove characters from underline set } nu : while ch <> chr(CR) do begin uscharset := uscharset - [ch]; getchar end; { while } { OV : Overlay next two characters } ov : begin skip; if suppressing then append(line,blank); pad(overline,length(line)); append(line,ch); getchar; append(overline,ch); getchar end; { PA : Start a new paragraph } pa : startpara(parspacing); { PL : Set paragraph layout } pl : begin getdefnum(parspacing,defparspacing); getdefnum(parindent,defparindent) end; { RB : Switch to retain blank mode } rb : if suppressing then begin suppressing := false; underlining := false end else error('occurred twice'); { RM : Put next word in right margin } rm : begin skip; getword(word); if length(line) + length(word) > maxwidth then putline; pad(line,maxwidth - length(word)); append(line,word) end; { RR : Retrieve cross-reference data and print it } rr : begin skip; refcode := upper(ch); getchar; getkey(key); lookup(key,reftable,ref); setlength(word,0); if ref = nil then putnum(word,0) else with ref ^ do begin entcount := succ(entcount); case refcode of 'P' : putnum(word,pagenum); 'C' : putnum(word,chapnum); 'S' : putsecnum(word,chapnum,secnum,subsecnum); 'I' : putnum(word,itemnum) end { case } end; while not (ch in wordends) do begin append(word,ch); getchar end; putword(word) end; { SB : Switch to suppress blank and EOL mode } sb : if suppressing then error('occurred twice') else suppressing := true; { SE : Start section } se : begin secnum := succ(secnum); subsecnum := 0; putsubhead(minsec,true) end; { SI : Set item number } si : if itemlist then error('inside list') else begin itemlist := true; getnum(itemnum) end; { SL : Set subheading layout } sl : begin getdefnum(beforehead,defbhead); getdefnum(afterhead,defahead) end; { SM : Set left margin } sm : getdefnum(margin,length(line)); { SP : Force line break and write blank lines. } sp : begin getdefnum(count,linespacing); break(count); putline end; { SS : Start subsection } ss : begin if secnum = 0 then error('no section'); subsecnum := succ(subsecnum); putsubhead(minsubsec,true) end; { SU : Start unnumbered section } su : putsubhead(minsec,false); { TC : write a table of contents. Linespacing in contents file is determined by LS setting when this command is executed. } tc : if contents then error('occurred twice') else begin contents := true; contsection := linespacing; contchapter := 2 * linespacing; changext(outfilename,extcon,contfilename); rewrite(contfilename,cont); setlength(conttitle,0); skip; if ch = '#' then begin pageintc := true; getchar; skip end; getline(conttitle); putconttitle end; { TL : Define new running title. The title is terminated by EOL or command terminator. Make an entry in the table of contents. # will be translated to page number. } tl : begin setlength(title,0); skip; getline(title); if contents then putcontline(contchapter,contmargin,title) end; { TS : Set tab spacing } ts : getdefnum(tabgap,deftabgap); { UL : Add characters to underline set } ul : while ch <> chr(CR) do begin if ch <> blank then uscharset := uscharset + [ch]; getchar end; { while } { VL : Set vertical layout parameters } vl : begin getdefnum(leadin,defleadin); getdefnum(maxlines,defmaxlines); getdefnum(lastline,deflastline); getdefnum(chapgap,defchapgap) end; { ZR : Show references in left margin } zr : showrefs := true; end; { case } skip end end; { command } { ----------------- Main text processing loop ------------------------------ } { If suppressing is true (usual case) the input text is processed word by word. If suppressing is false the text is processed character by character. } begin { process } writeln(infilename,' opened for input.'); reset(infilename,input); inlinecount := 0; getchar; while ch <> chr(0) do begin while ch = comchar do command; if suppressing then if ch in wordends then getchar else begin getword(word); putword(word) end else { retaining blanks and line breaks } begin if ch in wordends then begin wordcount := succ(wordcount); underlining := false end; if ch = chr(CR) then putline else if ch = chr(TAB) then repeat append(line,blank) until length(line) mod tabgap = 0 else if (ch = underscore) and not underlining then underlining := true else putchar(ch,underlining); getchar end end; { while } writeln(infilename,' closed on page ',pagenum:1,'; ', inlinecount:1,' lines read.') end; { process } { ------------------------------- Main program ----------------------------- } begin { Read file names from command line } getfilenames(extin,extout); if length(infilename) = 0 then writeln('No input file.') else begin { Read cross-reference file. This must be done before global variables are initialized because it changes some of them. } readrefs; { Initialize keep space } freelist := nil; stack := nil; resetkeep; firstfloat := nil; lastfloat := nil; freefloat := nil; { Initialize sets. The underline character set contains all characters except the common punctuation characters; this is to prevent the underlining of a punctuation character that follows an underlined word. Blank and rubout cannot be underlined. See \UL and \NU. } wordends := [blank,chr(0),chr(CR),chr(TAB)]; uscharset := [chr(33)..chr(126)] - [',','.',';',':','!','?','-','_']; { Initialize flags } suppressing := true; pageready := false; keeping := false; contents := false; pageintc := false; itemlist := false; underlining := false; printwarning := false; showrefs := false; { Initialize counters and parameters } linesonpage := 0; pagenum := 0; wordcount := 0; chapnum := 0; secnum := 0; subsecnum := 0; contpage := 0; pagecount := 0; margin := 0; spacesdone := 0; errorcount := 0; itemnum := 0; displaylevel := 0; spaceleft := maxint; { Set defaults } comchar := '\'; { Default command character } { Set horizontal defaults } firstmargin := deffirstmargin; { Nothing can be printed left of this } maxwidth := defmaxwidth; { Width of text on page; 6.5" at 12 cpi } parindent := defparindent; { Paragraph indentation } tabgap := deftabgap; { Tabs at X where X mod tabgap = 0 } diswidth := maxwidth; { Default length of displyed lines } disindent := defdisindent; { Display indentation } listindent := deflindent; { Indentation for a numbered list } listincr := deflincr; { Additional indentation for list items } { Set vertical defaults } leadin := defleadin; { Lines between running header and text } maxlines := defmaxlines; { Maximum # of text lines on a page: 8.5" at 6 lpi } lastline := deflastline; { Line #, relative to start of text, for footer } linespacing := deflinespacing; { Normal spacing between lines } dispspacing := linespacing; { Line spacing in a display } parspacing := defparspacing; { Lines before a paragraph } beforehead := defbhead; { Lines before a heading } afterhead := defahead; { Lines after a heading } beforedisp := defbdisp; { Lines before a display } afterdisp := defadisp; { Lines after a display } beforeitem := succ(deflinespacing); { Lines before a list item } afterlist := succ(deflinespacing); { Lines after an itemized list } chapgap := defchapgap; { Lines before first line of chapter } minpara := defminpara; { Limit for starting paragraph } minsubsec := defminsubsec; { Limit for starting subsection } minsec := defminsec; { Limit for starting section } { Initialize line buffers and strings } resetline; setlength(title,0); setlength(footer,0); setlength(currkey,0); { Define code mnemonic table } codetable[bd] := 'BD'; codetable[bf] := 'BF'; codetable[bk] := 'BK'; codetable[cc] := 'CC'; codetable[ce] := 'CE'; codetable[cx] := 'CH'; codetable[co] := 'CO'; codetable[dl] := 'DL'; codetable[ec] := 'EC'; codetable[ed] := 'ED'; codetable[ef] := 'EF'; codetable[ek] := 'EK'; codetable[el] := 'EL'; codetable[ep] := 'EP'; codetable[fl] := 'FL'; codetable[gp] := 'GP'; codetable[hl] := 'HL'; codetable[ic] := 'IC'; codetable[il] := 'IL'; codetable[im] := 'IM'; codetable[li] := 'LI'; codetable[ls] := 'LS'; codetable[mr] := 'MR'; codetable[mv] := 'MV'; codetable[nu] := 'NU'; codetable[ov] := 'OV'; codetable[pa] := 'PA'; codetable[pl] := 'PL'; codetable[rb] := 'RB'; codetable[rm] := 'RM'; codetable[rr] := 'RR'; codetable[sb] := 'SB'; codetable[se] := 'SE'; codetable[si] := 'SI'; codetable[sl] := 'SL'; codetable[sm] := 'SM'; codetable[sp] := 'SP'; codetable[ss] := 'SS'; codetable[su] := 'SU'; codetable[tc] := 'TC'; codetable[tl] := 'TL'; codetable[ts] := 'TS'; codetable[ul] := 'UL'; codetable[vl] := 'VL'; codetable[zr] := 'ZR'; codetable[zz] := 'ZZ'; { Open the output file } writeln(outfilename,' opened for output.'); rewrite(outfilename,output); { Process the input file } process(infilename); endchap; if contents then endcontpage; if reftable <> nil then writerefs; { Display the results } writeln(outfilename,': ',pagecount:1,' pages; ',wordcount:1,' words.'); if contpage > 0 then writeln(contfilename,': ',contpage:1,' pages.'); if space > 0 then writeln('Free memory: ',space:1,' bytes.'); if errorcount > 0 then writeln('Errors: ',errorcount:1,'.'); if printwarning then begin writeln; writeln('WARNING: the output file contains printer control characters!') end end end. { TP } .