home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume3 / modula_pp / m2p.mod < prev    next >
Encoding:
Text File  |  1986-11-30  |  32.5 KB  |  1,283 lines

  1. MODULE Modula2PrettyPrinter;
  2.  
  3. FROM InOut IMPORT
  4.     Done, Read, Write, WriteLn, WriteString;
  5.  
  6. (*
  7. **      Modula-2 Prettyprinter, November 1985.
  8. **
  9. **      by Ken Yap, U of Rochester, CS Dept.
  10. **
  11. **      Permission to copy, modify, and distribute, but not for profit,
  12. **      is hereby granted, provided that this note is included.
  13. **
  14. **      adapted from a Pascal Program Formatter
  15. **      by J. E. Crider, Shell Oil Company,
  16. **      Houston, Texas 77025
  17. **
  18. **      This program formats Modula-2 programs according
  19. **      to structured formatting principles
  20. **
  21. **      A valid Modula-2 program is read from the input and
  22. **      a formatted program is written to the output.
  23. **      It is basically a recursive descent parser with actions
  24. **      intermixed with syntax scanning.
  25. **
  26. **      The actions of the program are as follows:
  27. **
  28. **      FORMATTING:  Each structured statement is formatted
  29. **      in the following pattern (with indentation "indent"):
  30. **
  31. **                XXXXXX header XXXXXXXX
  32. **                        XXXXXXXXXXXXXXXXXX
  33. **                        XXXXX body XXXXXX
  34. **                        XXXXXXXXXXXXXXXXXX
  35. **                END
  36. **
  37. **      where the header is one of:
  38. **
  39. **                IF <expression> THEN
  40. **                ELSIF <expression> THEN
  41. **                ELSE
  42. **                WHILE <expression> DO
  43. **                FOR <control variable> := <FOR list> DO
  44. **                WITH <RECORD variable> DO
  45. **                REPEAT
  46. **                LOOP
  47. **                CASE <expression> OF
  48. **                <CASE label list>:
  49. **
  50. **      and the last line begins with UNTIL or is END.
  51. **      Other program parts are formatted similarly.  The headers are:
  52. **
  53. **                <MODULE/PROCEDURE heading>;
  54. **                CONST
  55. **                TYPE
  56. **                VAR
  57. **                BEGIN
  58. **                (various FOR records AND RECORD variants)
  59. **
  60. **      COMMENTS:  Each comment that starts before or on a specified
  61. **      column on an input line (program constant "commthresh") is
  62. **      copied without shifting or reformatting.  Each comment that
  63. **      starts after "commthresh" is reformatted and left-justified
  64. **      following the aligned comment base column ("alcommbase").
  65. **
  66. **      SPACES AND BLANK LINES:  Spaces not at line breaks are copied from
  67. **      the input.  Blank lines are copied from the input if they appear
  68. **      between statements (or appropriate declaration units).  A blank
  69. **      line is inserted above each significant part of each program/
  70. **      procedure if one is not already there.
  71. **
  72. **      CONTINUATION:  Lines that are too long for an output line are
  73. **      continued with additional indentation ("contindent").
  74. *)
  75.  
  76. CONST
  77.     TAB = 11C;
  78.     NEWLINE = 12C;                  (* for Unix *)
  79.     FF = 14C;
  80.     maxrwlen = 15;                  (* size of reserved word strings *)
  81.     ordminchar = 0;                 (* ord of lowest char in char set *)
  82.     ordmaxchar = 127;               (* ord of highest char in char set *)
  83. (* The following parameters may be adjusted for the installation: *)
  84.     maxinlen = 255;                 (* maximum width of input line + 1 *)
  85.     maxoutlen = 128;                (* maximum width of output line *)
  86.     tabinterval = 8;                (* interval between tab columns *)
  87.     initmargin = 0;                 (* initial value of output margin *)
  88.     commthresh = tabinterval;       (* column threshhold in input for comments to be aligned *)
  89.     alcommbase = 40;                (* aligned comments in output start after this column *)
  90.     indent = tabinterval;           (* RECOMMENDED indentation increment *)
  91.     contindent = tabinterval;       (* continuation indentation, >indent *)
  92.     commindent = tabinterval;       (* comment continuation indentation *)
  93.  
  94. TYPE
  95.     natural = [-1..1000000];        (* kludge because compiler doesn't *)
  96.     inrange = [-1..maxinlen];       (* recognize qualified subranges *)
  97.     outrange = [-1..maxoutlen];
  98.  
  99.     errortype = (longline, noendcomm, notquote, longword, notdo, notof, notend, notthen, notbegin, notuntil, notident,
  100.     notsemicolon, notcolon, notperiod, notparen, noeof);
  101.  
  102.     chartype = (illegal, special, chapostrophe, chleftparen, chrightparen, chperiod, digit, chcolon, chsemicolon,
  103.     chlessthan, chgreaterthan, letter, chleftbrace, chbar);
  104.  
  105.     chartypeset = SET OF chartype;  (* for reserved word recognition *)
  106.  
  107.     resword = (                     (* reserved words ordered by length *)
  108.     rwif, rwdo, rwof, rwto, rwin, rwor,
  109.                     (* length: 2 *)
  110.     rwend, rwfor, rwvar, rwdiv, rwmod, rwset, rwand, rwnot, rwnil,
  111.                     (* length: 3 *)
  112.     rwthen, rwelse, rwwith, rwcase, rwtype, rwloop, rwfrom,
  113.                     (* length: 4 *)
  114.     rwbegin, rwelsif, rwuntil, rwwhile, rwarray, rwconst,
  115.                     (* length: 5 *)
  116.     rwrepeat, rwrecord, rwmodule, rwimport, rwexport,
  117.                     (* length: 6 *)
  118.     rwpointer,                      (* length: 7 *)
  119.     rwprocedure, rwqualified,       (* length: 9 *)
  120.     rwdefinition,                   (* length: 10 *)
  121.     rwimplementation,               (* length: 14 *)
  122.     rwx);                           (* length: 15 for table sentinel *)
  123.     rwstring =  ARRAY [1..maxrwlen] OF CHAR;
  124.  
  125.     firstclass = (                  (* class of word if on new line *)
  126.     newclause,                      (* start of new clause *)
  127.     continue,                       (* continuation of clause *)
  128.     alcomm,                         (* start of aligned comment *)
  129.     contalcomm,                     (* continuation of aligned comment *)
  130.     uncomm,                         (* start of unaligned comment *)
  131.     contuncomm);                    (* continuation of unaligned comment *)
  132.  
  133.     wordtype = RECORD               (* data record for word *)
  134.         whenfirst : firstclass; (* class of word if on new line *)
  135.         puncfollows : BOOLEAN;  (* to reduce dangling punctuation *)
  136.         blanklncount : natural; (* number of preceding blank lines *)
  137.         spaces : INTEGER;       (* number of spaces preceding word *)
  138.         base : [-1..maxinlen];  (* inline.buf[base] precedes word *)
  139.         size : inrange;
  140.     END;                            (* length of word in inline.buf *)
  141.  
  142.     symboltype = (                  (* symbols for syntax analysis *)
  143.     symodule, sydefinition, syimplementation, syfrom, syimport, syexport, syqual, syproc, declarator, sybegin, syend, syif,
  144.     sythen, syelsif, syelse, syloop, sycase, syof, syuntil, syrepeat, forwhilewith, sydo, syrecord, ident, intconst,
  145.     semicolon, leftparen, rightparen, period, colon, bar, othersym, otherword, comment, syeof);
  146.     symbolset = SET OF symboltype;
  147.  
  148. VAR
  149.     inline : RECORD                 (* input line data *)
  150.         endoffile : BOOLEAN;    (* end of file on input? *)
  151.         ch : CHAR;              (* current char, buf[index] *)
  152.         index : inrange;        (* subscript of current char *)
  153.         len : natural;          (* length of input line in buf *)
  154.         buf : ARRAY [1..maxinlen] OF CHAR;
  155.     END;
  156.     outline : RECORD                (* output line data *)
  157.         blanklns : natural;     (* number of preceding blank lines *)
  158.         len : outrange;         (* number of chars in buf *)
  159.         buf : ARRAY [1..maxoutlen] OF CHAR;
  160.     END;
  161.     curword : wordtype;             (* current word *)
  162.     margin : outrange;              (* left margin *)
  163.     lnpending : BOOLEAN;            (* new line before next symbol? *)
  164.     inheader : BOOLEAN;             (* are we scanning a proc header? *)
  165.     symbol : symboltype;            (* current symbol *)
  166.  
  167.   (* Structured Constants *)
  168.     headersyms : symbolset;         (* headers for program parts *)
  169.     strucsyms : symbolset;          (* symbols that begin structured statements *)
  170.     stmtbeginsyms : symbolset;      (* symbols that begin statements *)
  171.     stmtendsyms : symbolset;        (* symbols that follow statements *)
  172.     stopsyms : symbolset;           (* symbols that stop expression scan *)
  173.     recendsyms : symbolset;         (* symbols that stop record scan *)
  174.     datawords : symbolset;          (* to reduce dangling punctuation *)
  175.     firstrw : ARRAY [1..maxrwlen] OF resword;
  176.     rwword : ARRAY [rwif..rwimplementation] OF rwstring;
  177.     rwsy : ARRAY [rwif..rwimplementation] OF symboltype;
  178.     charclass : ARRAY CHAR OF chartype;
  179.     symbolclass : ARRAY chartype OF symboltype;
  180.  
  181. PROCEDURE StructConsts;
  182. (* establish values of structured constants *)
  183. VAR
  184.     i : [ordminchar..ordmaxchar];   (* loop index *)
  185.     ch : CHAR;                      (* loop index *)
  186.  
  187. PROCEDURE BuildResWord(rw : resword; symword : rwstring; symbol : symboltype);
  188. BEGIN
  189.     rwword[rw] := symword;          (* reserved word string *)
  190.     rwsy[rw] := symbol;             (* map to symbol *)
  191. END BuildResWord;
  192.  
  193. BEGIN                                   (* StructConsts *)
  194. (* symbol sets for syntax analysis *)
  195.     headersyms := symbolset{symodule, syproc, declarator, sybegin, syend, syeof};
  196.     strucsyms := symbolset{sycase, syrepeat, syif, forwhilewith, syloop};
  197.     stmtbeginsyms := strucsyms + symbolset{ident};
  198.     stmtendsyms := symbolset{semicolon, bar, syend, syuntil, syelsif, syelse, syeof};
  199.     stopsyms := headersyms + strucsyms + stmtendsyms;
  200.     recendsyms := symbolset{rightparen, syend, syeof};
  201.     datawords := symbolset{otherword, intconst, ident, syend};
  202.  
  203. (* constants for recognizing reserved words *)
  204.     firstrw[1] := rwif;             (* length: 1 *)
  205.     firstrw[2] := rwif;             (* length: 2 *)
  206.     BuildResWord(rwif, 'IF             ', syif);
  207.     BuildResWord(rwdo, 'DO             ', sydo);
  208.     BuildResWord(rwof, 'OF             ', syof);
  209.     BuildResWord(rwto, 'TO             ', othersym);
  210.     BuildResWord(rwin, 'IN             ', othersym);
  211.     BuildResWord(rwor, 'OR             ', othersym);
  212.     firstrw[3] := rwend;            (* length: 3 *)
  213.     BuildResWord(rwend, 'END            ', syend);
  214.     BuildResWord(rwfor, 'FOR            ', forwhilewith);
  215.     BuildResWord(rwvar, 'VAR            ', declarator);
  216.     BuildResWord(rwdiv, 'DIV            ', othersym);
  217.     BuildResWord(rwmod, 'MOD            ', othersym);
  218.     BuildResWord(rwset, 'SET            ', othersym);
  219.     BuildResWord(rwand, 'AND            ', othersym);
  220.     BuildResWord(rwnot, 'NOT            ', othersym);
  221.     BuildResWord(rwnil, 'NIL            ', otherword);
  222.     firstrw[4] := rwthen;           (* length: 4 *)
  223.     BuildResWord(rwthen, 'THEN           ', sythen);
  224.     BuildResWord(rwelse, 'ELSE           ', syelse);
  225.     BuildResWord(rwwith, 'WITH           ', forwhilewith);
  226.     BuildResWord(rwloop, 'LOOP           ', syloop);
  227.     BuildResWord(rwfrom, 'FROM           ', syfrom);
  228.     BuildResWord(rwcase, 'CASE           ', sycase);
  229.     BuildResWord(rwtype, 'TYPE           ', declarator);
  230.     firstrw[5] := rwbegin;          (* length: 5 *)
  231.     BuildResWord(rwbegin, 'BEGIN          ', sybegin);
  232.     BuildResWord(rwelsif, 'ELSIF          ', syelsif);
  233.     BuildResWord(rwuntil, 'UNTIL          ', syuntil);
  234.     BuildResWord(rwwhile, 'WHILE          ', forwhilewith);
  235.     BuildResWord(rwarray, 'ARRAY          ', othersym);
  236.     BuildResWord(rwconst, 'CONST          ', declarator);
  237.     firstrw[6] := rwrepeat;         (* length: 6 *)
  238.     BuildResWord(rwrepeat, 'REPEAT         ', syrepeat);
  239.     BuildResWord(rwrecord, 'RECORD         ', syrecord);
  240.     BuildResWord(rwmodule, 'MODULE         ', symodule);
  241.     BuildResWord(rwimport, 'IMPORT         ', syimport);
  242.     BuildResWord(rwexport, 'EXPORT         ', syexport);
  243.     firstrw[7] := rwpointer;        (* length: 7 *)
  244.     BuildResWord(rwpointer, 'POINTER        ', othersym);
  245.     firstrw[8] := rwprocedure;      (* length: 8 *)
  246.     firstrw[9] := rwprocedure;      (* length: 9 *)
  247.     BuildResWord(rwprocedure, 'PROCEDURE      ', syproc);
  248.     BuildResWord(rwqualified, 'QUALIFIED      ', syqual);
  249.     firstrw[10] := rwdefinition;    (* length: 10 *)
  250.     BuildResWord(rwdefinition, 'DEFINITION     ', sydefinition);
  251.     firstrw[11] := rwimplementation;(* length: 11 *)
  252.     firstrw[12] := rwimplementation;(* length: 12 *)
  253.     firstrw[13] := rwimplementation;(* length: 13 *)
  254.     firstrw[14] := rwimplementation;(* length: 14 *)
  255.     BuildResWord(rwimplementation, 'IMPLEMENTATION ', syimplementation);
  256.     firstrw[15] := rwx;             (* length: 15 FOR table sentinel *)
  257.  
  258. (* constants for lexical scan *)
  259.     FOR i := ordminchar TO ordmaxchar DO
  260.         charclass[CHR(i)] := illegal;
  261.     END;
  262.     FOR ch := 'a' TO 'z' DO
  263.         charclass[ch] := letter;
  264.         charclass[CAP(ch)] := letter;
  265.     END;
  266.     FOR ch := '0' TO '9' DO
  267.         charclass[ch] := digit;
  268.     END;
  269.     charclass[' '] := special;
  270.     charclass['"'] := chapostrophe;
  271.     charclass['#'] := special;
  272.     charclass['&'] := special;
  273.     charclass["'"] := chapostrophe;
  274.     charclass['('] := chleftparen;
  275.     charclass[')'] := chrightparen;
  276.     charclass['*'] := special;
  277.     charclass['+'] := special;
  278.     charclass[','] := special;
  279.     charclass['-'] := special;
  280.     charclass['.'] := chperiod;
  281.     charclass['/'] := special;
  282.     charclass[':'] := chcolon;
  283.     charclass[';'] := chsemicolon;
  284.     charclass['<'] := chlessthan;
  285.     charclass['='] := special;
  286.     charclass['>'] := chgreaterthan;
  287.     charclass['@'] := special;
  288.     charclass['['] := special;
  289.     charclass[']'] := special;
  290.     charclass['^'] := special;
  291.     charclass['{'] := special;
  292.     charclass['|'] := chbar;
  293.     charclass['}'] := special;
  294.     symbolclass[illegal] := othersym;
  295.     symbolclass[special] := othersym;
  296.     symbolclass[chapostrophe] := otherword;
  297.     symbolclass[chleftparen] := leftparen;
  298.     symbolclass[chrightparen] := rightparen;
  299.     symbolclass[chperiod] := period;
  300.     symbolclass[digit] := intconst;
  301.     symbolclass[chcolon] := colon;
  302.     symbolclass[chsemicolon] := semicolon;
  303.     symbolclass[chlessthan] := othersym;
  304.     symbolclass[chgreaterthan] := othersym;
  305.     symbolclass[chbar] := bar;
  306.     symbolclass[letter] := ident;
  307. END StructConsts;
  308.  
  309. (* FlushLine/WriteError/ReadLine convert between files and lines. *)
  310.  
  311. PROCEDURE FlushLine;
  312. (* Write buffer into output file *)
  313. VAR
  314.     i, j, vircol : outrange;        (* loop index *)
  315.     nonblankseen : BOOLEAN;
  316. BEGIN
  317.     WITH outline DO
  318.         WHILE blanklns > 0 DO
  319.             WriteLn;
  320.             blanklns := blanklns - 1;
  321.         END;
  322.         IF len > 0 THEN
  323.             vircol := 0;
  324.             nonblankseen := FALSE;
  325.                     (* set this to TRUE if you don't want blanks to tab conversion *)
  326.             FOR i := 0 TO len - 1 DO
  327.                 IF buf[i+1] <> ' ' THEN
  328.                     IF NOT nonblankseen THEN
  329.                         LOOP
  330.                             j := (vircol DIV tabinterval + 1) * tabinterval;
  331.                             IF j > i THEN
  332.                                 EXIT;
  333.                             END;
  334.                             Write(TAB);
  335.                             vircol := j;
  336.                         END;
  337.                     END;
  338.                     nonblankseen := TRUE;
  339.                     WHILE vircol < i DO
  340.                         Write(' ');
  341.                         vircol := vircol + 1;
  342.                     END;
  343.                     Write(buf[i+1]);
  344.                     vircol := i + 1;
  345.                 END;
  346.             END;
  347.             WriteLn;
  348.             len := 0;
  349.         END;
  350.     END;
  351. END FlushLine;
  352.  
  353. PROCEDURE WriteError(error : errortype);
  354. (* report error to output *)
  355. VAR
  356.     i, ix : inrange;                (* loop index, limit *)
  357. BEGIN
  358.     FlushLine;
  359.     WriteString('(* !!! error, ');
  360.     CASE error OF
  361.     longline:
  362.         WriteString('shorter line');
  363.     | noendcomm:
  364.         WriteString('END OF comment');
  365.     | notquote:
  366.         WriteString("final ' on line");
  367.     | longword:
  368.         WriteString('shorter word');
  369.     | notdo:
  370.         WriteString('"DO"');
  371.     | notof:
  372.         WriteString('"OF"');
  373.     | notend:
  374.         WriteString('"END"');
  375.     | notthen:
  376.         WriteString('"THEN"');
  377.     | notbegin:
  378.         WriteString('"BEGIN"');
  379.     | notuntil:
  380.         WriteString('"UNTIL"');
  381.     | notident:
  382.         WriteString('"identifier"');
  383.     | notsemicolon:
  384.         WriteString('";"');
  385.     | notperiod:
  386.         WriteString('"."');
  387.     | notcolon:
  388.         WriteString('":"');
  389.     | notparen:
  390.         WriteString('")"');
  391.     | noeof:
  392.         WriteString('END OF file');
  393.     END;
  394.     WriteString(' expected');
  395.     IF error >= longword THEN
  396.         WriteString(', NOT "');
  397.         WITH inline DO
  398.             WITH curword DO
  399.                 IF size > maxrwlen THEN
  400.                     ix := maxrwlen
  401.                 ELSE
  402.                     ix := size;
  403.                 END;
  404.                 FOR i := 1 TO ix DO
  405.                     Write(buf[base + i]);
  406.                 END;
  407.             END;
  408.         END;
  409.         Write('"');
  410.     END;
  411.     IF error = noeof THEN
  412.         WriteString(', FORMATTING STOPS');
  413.     END;
  414.     WriteString(' !!! *)');
  415.     WriteLn;
  416. END WriteError;
  417.  
  418. PROCEDURE ReadLine;
  419. (* Read line into input buffer *)
  420. VAR
  421.     c : CHAR;                       (* input character *)
  422.     nonblank : BOOLEAN;             (* is char other than space? *)
  423.     i : INTEGER;
  424. BEGIN
  425.     WITH inline DO
  426.         len := 0;
  427.         LOOP
  428.             Read(c);
  429.             IF Done THEN
  430.                 endoffile := Done;
  431.                 EXIT;
  432.             END;
  433.             IF c = NEWLINE THEN
  434.                 EXIT;
  435.             END;
  436.             IF c < ' ' THEN (* convert ISO control chars (except leading form feed) to spaces *)
  437.                 IF c = TAB THEN
  438.                     (* ISO TAB char *)
  439.                     c := ' ';
  440.                     (* add last space at end *)
  441.                     WHILE len MOD 8 <> 7 DO
  442.                         len := len + 1;
  443.                         IF len < maxinlen THEN
  444.                             buf[len] := c;
  445.                         END;
  446.                     END;
  447.                     (* END tab handling *)
  448.                 ELSIF (c <> FF) OR (len > 0) THEN
  449.                     c := ' ';
  450.                 END;
  451.             END;            (* END ISO control char conversion *)
  452.             len := len + 1;
  453.             IF len < maxinlen THEN
  454.                 buf[len] := c;
  455.             END;
  456.         END;
  457.         IF NOT endoffile THEN
  458.             IF len >= maxinlen THEN
  459.                     (* input line too long *)
  460.                 WriteError(longline);
  461.                 len := maxinlen - 1;
  462.             END;
  463.             WHILE (len > 0) AND (buf[len] = ' ') DO
  464.                 len := len - 1;
  465.             END;
  466.         END;
  467.         len := len + 1;         (* add exactly ONE trailing blank *)
  468.         buf[len] := ' ';
  469.         index := 0;
  470.     END;
  471. END ReadLine;
  472.  
  473. PROCEDURE GetChar;
  474. (* get next char from input buffer *)
  475. BEGIN
  476.     WITH inline DO
  477.         index := index + 1;
  478.         ch := buf[index];
  479.     END;
  480. END GetChar;
  481.  
  482. PROCEDURE NextChar() : CHAR;
  483. (* look at next char in input buffer *)
  484. BEGIN
  485.     RETURN inline.buf[inline.index + 1];
  486. END NextChar;
  487.  
  488. PROCEDURE StartWord(startclass : firstclass);
  489. (* note beginning of word, and count preceding lines and spaces *)
  490. VAR
  491.     first : BOOLEAN;                (* is word the first on input line? *)
  492. BEGIN
  493.     first := FALSE;
  494.     WITH inline DO
  495.         WITH curword DO
  496.             whenfirst := startclass;
  497.             blanklncount := 0;
  498.             WHILE (index >= len) AND NOT endoffile DO
  499.                 IF len = 1 THEN
  500.                     blanklncount := blanklncount + 1;
  501.                 END;
  502.                 IF startclass = contuncomm THEN
  503.                     FlushLine
  504.                 ELSE
  505.                     first := TRUE;
  506.                 END;
  507.                 ReadLine;
  508.                     (* with exactly ONE trailing blank *)
  509.                 GetChar;
  510.                 IF ch = FF THEN
  511.                     FlushLine;
  512.                     Write(FF);
  513.                     blanklncount := 0;
  514.                     GetChar;
  515.                 END;
  516.             END;
  517.             spaces := 0;    (* count leading spaces *)
  518.             IF NOT endoffile THEN
  519.                 WHILE ch = ' ' DO
  520.                     spaces := spaces + 1;
  521.                     GetChar;
  522.                 END;
  523.             END;
  524.             IF first THEN
  525.                 spaces := 1;
  526.             END;
  527.             base := index - 1;
  528.         END;
  529.     END;
  530. END StartWord;
  531.  
  532. PROCEDURE FinishWord;
  533. (* note end of word *)
  534. BEGIN
  535.     WITH inline DO
  536.         WITH curword DO
  537.             puncfollows := (symbol IN datawords) AND (ch <> ' ');
  538.             size := index - base - 1;
  539.         END;
  540.     END;
  541. END FinishWord;
  542.  
  543. PROCEDURE CopyWord(newline : BOOLEAN; pword : wordtype);
  544. (* copy word from input buffer into output buffer *)
  545. VAR
  546.     i : INTEGER;                    (* outline.len excess, loop index *)
  547. BEGIN
  548.     WITH pword DO
  549.         WITH outline DO
  550.             i := maxoutlen - len - spaces - size;
  551.             IF newline OR (i < 0) OR ((i = 0) AND puncfollows) THEN
  552.                 FlushLine;
  553.             END;
  554.             IF len = 0 THEN (* first word on output line *)
  555.                 blanklns := blanklncount;
  556.                 CASE whenfirst OF
  557.                     (* update LOCAL word.spaces *)
  558.                 newclause:
  559.                     spaces := margin;
  560.                 | continue:
  561.                     spaces := margin;
  562.                 | alcomm:
  563.                     spaces := alcommbase;
  564.                 | contalcomm:
  565.                     spaces := alcommbase + commindent;
  566.                 | uncomm:
  567.                     spaces := base;
  568.                 | contuncomm:
  569.                     (* spaces := spaces *);
  570.                 END;
  571.                 IF spaces + size > maxoutlen THEN
  572.                     spaces := maxoutlen - size;
  573.                     (* reduce spaces *)
  574.                     IF spaces < 0 THEN
  575.                         WriteError(longword);
  576.                         size := maxoutlen;
  577.                         spaces := 0;
  578.                     END;
  579.                 END;
  580.             END;
  581.             FOR i := 1 TO spaces DO
  582.                     (* put out spaces *)
  583.                 len := len + 1;
  584.                 buf[len] := ' ';
  585.             END;
  586.             FOR i := 1 TO size DO
  587.                     (* copy actual word *)
  588.                 len := len + 1;
  589.                 buf[len] := inline.buf[base + i];
  590.             END;
  591.         END;
  592.     END;
  593. END CopyWord;
  594.  
  595. PROCEDURE DoComment;                    (* copy aligned or unaligned comment *)
  596.  
  597. PROCEDURE CopyComment(commclass : firstclass; commbase : inrange);
  598. (* copy words of comment *)
  599. VAR
  600.     endcomment : BOOLEAN;           (* end of comment? *)
  601. BEGIN
  602.     WITH curword DO                 (* copy comment begin symbol *)
  603.         whenfirst := commclass;
  604.         spaces := commbase - outline.len;
  605.         CopyWord((spaces < 0) OR (blanklncount > 0), curword);
  606.     END;
  607.     commclass := VAL(firstclass, ORD(commclass)+1);
  608.     WITH inline DO
  609.         REPEAT                  (* loop for successive words *)
  610.             StartWord(commclass);
  611.             endcomment := endoffile;
  612.                     (* premature end? *)
  613.             IF endcomment THEN
  614.                 WriteError(noendcomm)
  615.             ELSE
  616.                 REPEAT
  617.                     IF ch = '*' THEN
  618.                         GetChar;
  619.                         IF ch = ')' THEN
  620.                             endcomment := TRUE;
  621.                             GetChar;
  622.                         END;
  623.                     ELSE
  624.                         GetChar;
  625.                     END;
  626.                 UNTIL (ch = ' ') OR endcomment;
  627.             END;
  628.             FinishWord;
  629.             CopyWord(FALSE, curword)
  630.         UNTIL endcomment;
  631.     END;
  632. END CopyComment;
  633.  
  634. BEGIN                                   (* DoComment *)
  635.     IF curword.base < commthresh THEN
  636.                     (* copy comment without alignment *)
  637.         CopyComment(uncomm, curword.base)
  638.     ELSE                            (* align AND format comment *)
  639.         CopyComment(alcomm, alcommbase);
  640.     END;
  641. END DoComment;
  642.  
  643. PROCEDURE GetSymbol;
  644. (* get next non-comment symbol *)
  645.  
  646. PROCEDURE CopySymbol(symbol : symboltype; pword : wordtype);
  647. (* copy word(s) of symbol *)
  648. BEGIN
  649.     IF symbol = comment THEN
  650.         DoComment;              (* NOTE: DoComment uses global word! *)
  651.         lnpending := TRUE;
  652.     ELSIF symbol = semicolon THEN
  653.         CopyWord(FALSE, pword);
  654.         lnpending := NOT inheader;
  655.     ELSE
  656.         CopyWord(lnpending, pword);
  657.         lnpending := FALSE;
  658.     END;
  659. END CopySymbol;
  660.  
  661. PROCEDURE FindSymbol;
  662. (* find next symbol in input buffer *)
  663.  
  664. VAR
  665.     termch : CHAR;                  (* string terminator *)
  666.     chclass : chartype;             (* classification of leading char *)
  667.  
  668. PROCEDURE CheckResWord;
  669. (* check if current identifier is reserved word/symbol *)
  670. VAR
  671.     rw, rwbeyond : resword;         (* loop index, limit *)
  672.     symword : rwstring;             (* copy of symbol word *)
  673.     i : [-1..maxrwlen];             (* loop index *)
  674. BEGIN
  675.     WITH curword DO
  676.         WITH inline DO
  677.             size := index - base - 1;
  678.             IF size < maxrwlen THEN
  679.                 symword := '               ';
  680.                 FOR i := 1 TO size DO
  681.                     symword[i] := CAP(buf[ base + i]);
  682.                 END;
  683.                 rw := firstrw[size];
  684.                 rwbeyond := firstrw[size + 1];
  685.                 symbol := semicolon;
  686.                 REPEAT
  687.                     IF rw >= rwbeyond THEN
  688.                         symbol := ident
  689.                     ELSIF symword = rwword[rw] THEN
  690.                         symbol := rwsy[rw]
  691.                     ELSE
  692.                         rw := VAL(resword,ORD(rw)+1);
  693.                     END;
  694.                 UNTIL symbol <> semicolon;
  695.             END;
  696.             whenfirst := newclause;
  697.         END;
  698.     END;
  699. END CheckResWord;
  700.  
  701. PROCEDURE GetName;
  702. BEGIN
  703.     WHILE charclass[inline.ch] IN chartypeset{letter, digit} DO
  704.         GetChar;
  705.     END;
  706.     CheckResWord;
  707. END GetName;
  708.  
  709. PROCEDURE GetNumber;
  710. BEGIN
  711.     WITH inline DO
  712.         WHILE charclass[ch] = digit DO
  713.             GetChar;
  714.         END;
  715.         IF ch = '.' THEN
  716.             IF charclass[NextChar()] = digit THEN
  717.                     (* NOTE: NextChar is a function! *)
  718.                 symbol := otherword;
  719.                 GetChar;
  720.                 WHILE charclass[ch] = digit DO
  721.                     GetChar;
  722.                 END;
  723.             END;
  724.         END;
  725.         IF CAP(ch) = 'E' THEN
  726.             symbol := otherword;
  727.             GetChar;
  728.             IF (ch = '+') OR (ch = '-') THEN
  729.                 GetChar;
  730.             END;
  731.             WHILE charclass[ch] = digit DO
  732.                 GetChar;
  733.             END;
  734.         END;
  735.     END;
  736. END GetNumber;
  737.  
  738. PROCEDURE GetStringLiteral;
  739. VAR
  740.     endstring : BOOLEAN;            (* end of string literal? *)
  741. BEGIN
  742.     WITH inline DO
  743.         endstring := FALSE;
  744.         REPEAT
  745.             GetChar;
  746.             IF ch = termch THEN
  747.                 endstring := TRUE;
  748.             ELSIF index >= len THEN
  749.                     (* error, final "'" not on line *)
  750.                 WriteError(notquote);
  751.                 symbol := syeof;
  752.                 endstring := TRUE;
  753.             END;
  754.         UNTIL endstring;
  755.         GetChar;
  756.     END;
  757. END GetStringLiteral;
  758.  
  759. BEGIN                                   (* FindSymbol *)
  760.     StartWord(continue);
  761.     WITH inline DO
  762.         IF endoffile THEN
  763.             symbol := syeof
  764.         ELSE
  765.             termch := ch;   (* save for string literal routine *)
  766.             chclass := charclass[ch];
  767.             symbol := symbolclass[chclass];
  768.             GetChar;        (* second CHAR *)
  769.             CASE chclass OF
  770.             chsemicolon, chrightparen, chleftbrace, special, illegal: ;
  771.             | letter:
  772.                 GetName;
  773.             | digit:
  774.                 GetNumber;
  775.             | chapostrophe:
  776.                 GetStringLiteral;
  777.             | chcolon:
  778.                 IF ch = '=' THEN
  779.                     symbol := othersym;
  780.                     GetChar;
  781.                 END;
  782.             | chlessthan:
  783.                 IF (ch = '=') OR (ch = '>') THEN
  784.                     GetChar;
  785.                 END;
  786.             | chgreaterthan:
  787.                 IF ch = '=' THEN
  788.                     GetChar;
  789.                 END;
  790.             | chleftparen:
  791.                 IF ch = '*' THEN
  792.                     symbol := comment;
  793.                     GetChar;
  794.                 END;
  795.             | chperiod:
  796.                 IF ch = '.' THEN
  797.                     symbol := colon;
  798.                     GetChar;
  799.                 END;
  800.             END;
  801.             FinishWord;
  802.         END;
  803.     END;                            (* FindSymbol *)
  804. END FindSymbol;
  805.  
  806. BEGIN                                   (* GetSymbol *)
  807.     REPEAT
  808.         CopySymbol(symbol, curword);
  809.                     (* copy word for symbol to output *)
  810.         FindSymbol              (* get next symbol *)
  811.     UNTIL symbol <> comment;
  812. END GetSymbol;
  813.  
  814. PROCEDURE StartClause;
  815. (* (this may be a simple clause, or the start of a header) *)
  816. BEGIN
  817.     curword.whenfirst := newclause;
  818.     lnpending := TRUE;
  819. END StartClause;
  820.  
  821. PROCEDURE PassSemicolons;
  822. (* pass consecutive semicolons *)
  823. BEGIN
  824.     WHILE symbol = semicolon DO
  825.         GetSymbol;
  826.         StartClause;
  827.     END;
  828. END PassSemicolons;
  829.  
  830. PROCEDURE StartPart;
  831. (* start program part *)
  832. BEGIN
  833.     WITH curword DO
  834.         IF blanklncount = 0 THEN
  835.             blanklncount := 1;
  836.         END;
  837.     END;
  838. END StartPart;
  839.  
  840. PROCEDURE StartBody;
  841. (* finish header, start body of structure *)
  842. BEGIN
  843.     StartClause;
  844.     margin := margin + indent;
  845. END StartBody;
  846.  
  847. PROCEDURE FinishBody;
  848. (* retract margin *)
  849. BEGIN
  850.     margin := margin - indent;
  851. END FinishBody;
  852.  
  853. PROCEDURE PassPhrase(finalsymbol : symboltype);
  854. (* process symbols until significant symbol encountered *)
  855. VAR
  856.     endsyms : symbolset;            (* complete set of stopping symbols *)
  857. BEGIN
  858.     IF symbol <> syeof THEN
  859.         endsyms := stopsyms;
  860.         INCL(endsyms, finalsymbol);
  861.         REPEAT
  862.             GetSymbol
  863.         UNTIL symbol IN endsyms;
  864.     END;
  865. END PassPhrase;
  866.  
  867. PROCEDURE Expect(expectedsym : symboltype; error : errortype; syms : symbolset);
  868. (* fail if current symbol is not the expected one, then recover *)
  869. BEGIN
  870.     IF symbol = expectedsym THEN
  871.         GetSymbol
  872.     ELSE
  873.         WriteError(error);
  874.         INCL(syms, expectedsym);
  875.         WHILE NOT (symbol IN syms) DO
  876.             GetSymbol;
  877.         END;
  878.         IF symbol = expectedsym THEN
  879.             GetSymbol;
  880.         END;
  881.     END;
  882. END Expect;
  883.  
  884. PROCEDURE Heading;
  885. (* process heading for program or procedure *)
  886.  
  887. PROCEDURE MatchParens;                  (* process parentheses in heading *)
  888. VAR
  889.     endsyms : symbolset;
  890. BEGIN
  891.     GetSymbol;
  892.     WHILE NOT (symbol IN recendsyms) DO
  893.         IF symbol = leftparen THEN
  894.             MatchParens
  895.         ELSE
  896.             GetSymbol;
  897.         END;
  898.     END;
  899.     endsyms := stopsyms + recendsyms;
  900.     Expect(rightparen, notparen, endsyms);
  901. END MatchParens;
  902.  
  903. BEGIN                                   (* heading *)
  904.     GetSymbol;
  905.     PassPhrase(leftparen);
  906.     IF symbol = leftparen THEN
  907.         inheader := TRUE;
  908.         MatchParens;
  909.         inheader := FALSE;
  910.     END;
  911.     IF symbol = colon THEN
  912.         PassPhrase(semicolon);
  913.     END;
  914.     Expect(semicolon, notsemicolon, stopsyms);
  915.  
  916. END Heading;
  917.  
  918. PROCEDURE DoRecord;
  919. (* process record declaration *)
  920. BEGIN
  921.     GetSymbol;
  922.     StartBody;
  923.     PassFields(FALSE);
  924.     FinishBody;
  925.     Expect(syend, notend, recendsyms);
  926. END DoRecord;
  927.  
  928. PROCEDURE DoVariant;
  929. (* process (case) variant part *)
  930. BEGIN
  931.     PassPhrase(syof);
  932.     Expect(syof, notof, stopsyms);
  933.     StartBody;
  934.     PassFields(TRUE);
  935.     FinishBody;
  936. END DoVariant;
  937.  
  938. PROCEDURE DoParens(forvariant : BOOLEAN);
  939. (* process parentheses in record *)
  940. BEGIN
  941.     GetSymbol;
  942.     IF forvariant THEN
  943.         StartBody;
  944.     END;
  945.     PassFields(FALSE);
  946.     lnpending := FALSE;             (* for empty field list *)
  947.     Expect(rightparen, notparen, recendsyms);
  948.     IF forvariant THEN
  949.         FinishBody;
  950.     END;
  951. END DoParens;
  952.  
  953. PROCEDURE PassFields(forvariant : BOOLEAN);
  954. (* process declarations *)
  955. BEGIN
  956.     WHILE NOT (symbol IN recendsyms) DO
  957.         IF symbol = semicolon THEN
  958.             PassSemicolons
  959.         ELSIF symbol = syrecord THEN
  960.             DoRecord
  961.         ELSIF symbol = sycase THEN
  962.             DoVariant
  963.         ELSIF symbol = leftparen THEN
  964.             DoParens(forvariant)
  965.         ELSE
  966.             GetSymbol;
  967.         END;
  968.     END;
  969. END PassFields;
  970.  
  971. PROCEDURE Statement;
  972. (* process statement *)
  973. BEGIN
  974.     CASE symbol OF
  975.     sycase:
  976.         CaseStatement;
  977.         Expect(syend, notend, stmtendsyms);
  978.     | syif:
  979.         IfStatement;
  980.         Expect(syend, notend, stmtendsyms);
  981.     | syloop:
  982.         LoopStatement;
  983.         Expect(syend, notend, stmtendsyms);
  984.     | syrepeat:
  985.         RepeatStatement;
  986.     | forwhilewith:
  987.         ForWhileWithStatement;
  988.         Expect(syend, notend, stmtendsyms);
  989.     | ident:
  990.         AssignmentProccall;
  991.     | semicolon: ;
  992.     END;
  993. END Statement;
  994.  
  995. PROCEDURE AssignmentProccall;
  996. (* pass an assignment statement or procedure call *)
  997. BEGIN
  998.     WHILE NOT (symbol IN stmtendsyms) DO
  999.         GetSymbol;
  1000.     END;
  1001. END AssignmentProccall;
  1002.  
  1003. PROCEDURE StatementSequence;
  1004. (* process sequence of statements *)
  1005. BEGIN
  1006.     Statement;
  1007.     LOOP
  1008.         IF symbol <> semicolon THEN
  1009.             EXIT;
  1010.         END;
  1011.         GetSymbol;
  1012.         Statement;
  1013.     END;
  1014. END StatementSequence;
  1015.  
  1016. PROCEDURE IfStatement;
  1017. (* process if statement *)
  1018. BEGIN
  1019.     PassPhrase(sythen);
  1020.     Expect(sythen, notthen, stopsyms);
  1021.     StartBody;
  1022.     StatementSequence;
  1023.     FinishBody;
  1024.     WHILE symbol = syelsif DO
  1025.         StartClause;
  1026.         PassPhrase(sythen);
  1027.         Expect(sythen, notthen, stopsyms);
  1028.         StartBody;              (* new line after 'THEN' *)
  1029.         StatementSequence;
  1030.         FinishBody;
  1031.     END;
  1032.     IF symbol = syelse THEN
  1033.         StartClause;
  1034.         GetSymbol;
  1035.         StartBody;              (* new line after 'ELSE' *)
  1036.         StatementSequence;
  1037.         FinishBody;
  1038.     END;
  1039. END IfStatement;
  1040.  
  1041. PROCEDURE CaseStatement;
  1042. (* process case statement *)
  1043. BEGIN
  1044.     PassPhrase(syof);
  1045.     Expect(syof, notof, stopsyms);
  1046.     StartClause;
  1047.     OneCase;
  1048.     WHILE symbol = bar DO
  1049.         GetSymbol;
  1050.         OneCase;
  1051.     END;
  1052.     IF symbol = syelse THEN
  1053.         GetSymbol;
  1054.         StartBody;
  1055.         StatementSequence;
  1056.         FinishBody;
  1057.     END;
  1058. END CaseStatement;
  1059.  
  1060. PROCEDURE OneCase;
  1061. (* process one case clause *)
  1062. BEGIN
  1063.     IF NOT (symbol IN symbolset{bar, syelse}) THEN
  1064.         PassPhrase(colon);
  1065.         Expect(colon, notcolon, stopsyms);
  1066.         StartBody;              (* new line, indent after colon *)
  1067.         StatementSequence;
  1068.         FinishBody;             (* left-indent after case *)
  1069.     END;
  1070. END OneCase;
  1071.  
  1072. PROCEDURE RepeatStatement;
  1073. (* process repeat statement *)
  1074. BEGIN
  1075.     GetSymbol;
  1076.     StartBody;                      (* new line, indent after 'REPEAT' *)
  1077.     StatementSequence;
  1078.     FinishBody;                     (* left-ident after UNTIL *)
  1079.     StartClause;                    (* new line before UNTIL *)
  1080.     Expect(syuntil, notuntil, stmtendsyms);
  1081.     PassPhrase(semicolon);
  1082. END RepeatStatement;
  1083.  
  1084. PROCEDURE LoopStatement;
  1085. (* process loop statement *)
  1086. BEGIN
  1087.     GetSymbol;
  1088.     StartBody;                      (* new line, indent after LOOP *)
  1089.     StatementSequence;
  1090.     FinishBody;                     (* left-ident before END *)
  1091. END LoopStatement;
  1092.  
  1093. PROCEDURE ForWhileWithStatement;
  1094. (* process for, while, or with statement *)
  1095. BEGIN
  1096.     PassPhrase(sydo);
  1097.     Expect(sydo, notdo, stopsyms);
  1098.     StartBody;
  1099.     StatementSequence;
  1100.     FinishBody;
  1101. END ForWhileWithStatement;
  1102.  
  1103. PROCEDURE ProcedureDeclaration;
  1104. (* pass a procedure declaration *)
  1105. BEGIN
  1106.     ProcedureHeading;
  1107.     Block;
  1108.     Expect(ident, notident, stmtendsyms);
  1109.     Expect(semicolon, notsemicolon, stmtendsyms);
  1110. END ProcedureDeclaration;
  1111.  
  1112. PROCEDURE ProcedureHeading;
  1113. BEGIN
  1114.     StartClause;
  1115.     Heading;
  1116. END ProcedureHeading;
  1117.  
  1118. PROCEDURE Block;
  1119. BEGIN
  1120.     WHILE symbol IN symbolset{declarator, symodule, syproc} DO
  1121.         Declaration;
  1122.     END;
  1123.     IF symbol = sybegin THEN
  1124.         GetSymbol;
  1125.         StartBody;
  1126.         StatementSequence;
  1127.         FinishBody;
  1128.     END;
  1129.     Expect(syend, notend, stmtendsyms);
  1130. END Block;
  1131.  
  1132. PROCEDURE Declaration;
  1133. BEGIN
  1134.     IF symbol = declarator THEN
  1135.         StartClause;            (* CONST, TYPE, VAR *)
  1136.         GetSymbol;
  1137.         StartBody;
  1138.         REPEAT
  1139.             PassPhrase(syrecord);
  1140.             IF symbol = syrecord THEN
  1141.                 DoRecord;
  1142.             END;
  1143.             IF symbol = semicolon THEN
  1144.                 PassSemicolons;
  1145.             END;
  1146.         UNTIL symbol IN headersyms;
  1147.         FinishBody;
  1148.     ELSIF symbol = symodule THEN
  1149.         ModuleDeclaration;
  1150.     ELSIF symbol = syproc THEN
  1151.         ProcedureDeclaration;
  1152.     END;
  1153. END Declaration;
  1154.  
  1155. PROCEDURE ModuleDeclaration;
  1156. BEGIN
  1157.     PassPhrase(semicolon);
  1158.     PassSemicolons;
  1159.     WHILE symbol IN symbolset{syimport, syexport, syfrom} DO
  1160.         ImportExport;
  1161.     END;
  1162.     Block;
  1163.     Expect(ident, notident, stmtendsyms);
  1164. END ModuleDeclaration;
  1165.  
  1166. PROCEDURE ImportExport;
  1167. BEGIN
  1168.     IF symbol = syfrom THEN
  1169.         PassPhrase(syimport);
  1170.     END;
  1171.     IF symbol = syimport THEN
  1172.         GetSymbol;
  1173.     ELSIF symbol = syexport THEN
  1174.         GetSymbol;
  1175.         IF symbol = syqual THEN
  1176.             GetSymbol;
  1177.         END;
  1178.     END;
  1179.     StartBody;
  1180.     PassPhrase(semicolon);
  1181.     FinishBody;
  1182.     GetSymbol;
  1183. END ImportExport;
  1184.  
  1185. PROCEDURE OneDefinition;
  1186. BEGIN
  1187.     IF symbol = declarator THEN
  1188.         Declaration;
  1189.     ELSIF symbol = syproc THEN
  1190.         ProcedureHeading;
  1191.     END;
  1192. END OneDefinition;
  1193.  
  1194. PROCEDURE DefinitionModule;
  1195. BEGIN
  1196.     GetSymbol;
  1197.     PassPhrase(semicolon);
  1198.     GetSymbol;
  1199.     WHILE symbol IN symbolset{syimport, syexport, syfrom} DO
  1200.         ImportExport;
  1201.     END;
  1202.     WHILE symbol IN symbolset{declarator, syproc} DO
  1203.         OneDefinition;
  1204.     END;
  1205.     Expect(syend, notend, stmtendsyms);
  1206.     GetSymbol;
  1207.     Expect(period, notperiod, stmtendsyms);
  1208. END DefinitionModule;
  1209.  
  1210. PROCEDURE ProgramModule;
  1211. BEGIN
  1212.     ModuleDeclaration;
  1213.     Expect(period, notperiod, stmtendsyms);
  1214. END ProgramModule;
  1215.  
  1216. PROCEDURE CompilationUnit;
  1217. BEGIN
  1218.     IF symbol = syimplementation THEN
  1219.         GetSymbol;
  1220.         ProgramModule;
  1221.     ELSIF symbol = sydefinition THEN
  1222.         DefinitionModule;
  1223.     ELSE
  1224.         ProgramModule;
  1225.     END;
  1226. END CompilationUnit;
  1227.  
  1228. PROCEDURE CopyRemainder;
  1229. (* copy remainder of input *)
  1230. BEGIN
  1231.     WriteError(noeof);
  1232.     WITH inline DO
  1233.         REPEAT
  1234.             CopyWord(FALSE, curword);
  1235.             StartWord(contuncomm);
  1236.             IF NOT endoffile THEN
  1237.                 REPEAT
  1238.                     GetChar
  1239.                 UNTIL ch = ' ';
  1240.             END;
  1241.             FinishWord;
  1242.         UNTIL endoffile;
  1243.     END;
  1244. END CopyRemainder;
  1245.  
  1246. PROCEDURE Initialize;
  1247. (* initialize global variables *)
  1248. BEGIN
  1249.     WITH inline DO
  1250.         endoffile := FALSE;
  1251.         ch := ' ';
  1252.         index := 0;
  1253.         len := 0;
  1254.     END;
  1255.     WITH outline DO
  1256.         blanklns := 0;
  1257.         len := 0;
  1258.     END;
  1259.     WITH curword DO
  1260.         whenfirst := contuncomm;
  1261.         puncfollows := FALSE;
  1262.         blanklncount := 0;
  1263.         spaces := 0;
  1264.         base := 0;
  1265.         size := 0;
  1266.     END;
  1267.     margin := initmargin;
  1268.     lnpending := FALSE;
  1269.     symbol := othersym;
  1270. END Initialize;
  1271.  
  1272. BEGIN
  1273.     StructConsts;
  1274.     Initialize;
  1275. (* Files may be opened here. *)
  1276.     GetSymbol;
  1277.     CompilationUnit;
  1278.     IF NOT inline.endoffile THEN
  1279.         CopyRemainder;
  1280.     END;
  1281.     FlushLine;
  1282. END Modula2PrettyPrinter.
  1283.