PROGRAM PrettyPrinter;

(*
**  Filename:       PRETTY.PAS
**  Language:       Turbo Pascal
**  Target machine: Tested on H89 & CP/M 2.2, but should work on any
**                      computer or operating system which runs Turbo.
**  By:             Don McCrady (June 27, 1985)
**  Updated:        July 14, 1985
**
**  This program is a "Pascal Program Spiffyizer".  It takes an
**  ordinary Pascal program and produces a copy of it with all
**  reserved words in upper case.  (If the source file is written
**  entirely in upper case, then this program will have no effect
**  at all on it.)
**
**  The output from PRETTY can be written to the terminal, the printer,
**  a disk file, or all three at once.
**
**  The user can turn off the marking of reserved words, and the page
**  formatting if printer output is selected.  If disk file output is
**  requested, the user can also tell the program to erase the original
**  file when finished.
**
**  There is one bug:  If the source file contains a word which is longer
**  than 16 characters, the pretty printer will drop characters.  A word
**  with 16 characters is pretty long, so the bug shouldn't present much
**  of a problem with most Pascal programs.
*)

CONST   NumReserved = 41;          { Number of reserved words in Turbo. }
       StrLength = 16;      { Maximum word length.  This program won't }
       bell = ^G;            { work properly if there are any words in }
       cr = ^M;             { the source file which are larger than 16 }
       lf = ^J;                                          { characters. }
       esc = ^[;
       tab = ^I;
       ff = ^L;
       space = ' ';
       blank16 = '                ';                      { 16 spaces. }

TYPE    str = PACKED ARRAY [1..StrLength] OF char;
       string15 = STRING[15];
       string80 = STRING[80];
       CharSet = SET OF char;

CONST   AlphaNum : CharSet = ['A'..'Z','a'..'z','0'..'9'];
       (* WARNING:     To modify the following list, change the        *)
       (*      NumReserved constant to the new number of reserved      *)
       (*      words.  Then insert/delete reserved words in the        *)
       (*      following declaration -- but MAKE SURE THAT THE         *)
       (*      NEW LIST REMAINS IN ALPHABETICAL ORDER!!!               *)
       KeyWord : ARRAY [1..NumReserved] OF str =
         ('ABSOLUTE        ',  'AND             ',   'ARRAY           ',
          'BEGIN           ',  'CASE            ',   'CONST           ',
          'DIV             ',  'DO              ',   'DOWNTO          ',
          'ELSE            ',  'END             ',   'EXTERNAL        ',
          'FILE            ',  'FOR             ',   'FORWARD         ',
          'FUNCTION        ',  'GOTO            ',   'IF              ',
          'IN              ',  'LABEL           ',   'MOD             ',
          'NIL             ',  'NOT             ',   'OF              ',
          'OR              ',  'PACKED          ',   'PROCEDURE       ',
          'PROGRAM         ',  'RECORD          ',   'REPEAT          ',
          'SET             ',  'SHL             ',   'SHR             ',
          'STRING          ',  'THEN            ',   'TO              ',
          'TYPE            ',  'UNTIL           ',   'VAR             ',
          'WHILE           ',  'WITH            ');

VAR infile,outfile : text;
   InfileName,OutfileName,OldInfileName : string15;
   NextCh : char;
   FormatPage,               { Boolean flags... control output format. }
   MarkReserved,
   EraseOld,
   ConOut,
   FileOut,
   ListOut : Boolean;
   LineNum,
   PageNum : byte;

{ Read the next character from the source file.  Store the look-ahead   }
{  character into the global variable NextCh.                           }
PROCEDURE ReadChar(VAR ch : char);
BEGIN
   ch := NextCh;
   read(infile,NextCh)
END;

{ Convert a PACKED ARRAY string to uppercase.                           }
PROCEDURE ToUpper(VAR s : str);
VAR wptr : byte;
BEGIN
   FOR wptr := 1 TO StrLength DO
       s[wptr] := upcase(s[wptr])
END;

{ Write a character (ch) to the output device(s).                       }
PROCEDURE out(ch : char);
CONST   MaxLine = 60;
BEGIN
   IF ConOut THEN
       write(con,ch);
   IF ListOut THEN
       BEGIN
       IF FormatPage THEN
           BEGIN
           IF ch = ^M THEN
               LineNum := succ(LineNum);
           IF LineNum = MaxLine THEN
               BEGIN
               LineNum := 1;
               PageNum := succ(PageNum);
               write(lst,cr,ff,InfileName,cr,InfileName);
               write(lst,tab,tab,tab,tab,tab,tab,tab,tab,'Page ',PageNum);
               writeln(lst,lf,lf)
               END
           END;
       write(lst,ch)
       END;
   IF FileOut THEN
       write(outfile,ch)
END;

{ Sound terminal bell.                                                  }
PROCEDURE beep;
BEGIN
   write(bell)
END;

{ Display error message (msg), sound terminal bell, and exit.           }
PROCEDURE error(msg : string80);
BEGIN
   beep;
   writeln(msg);
   halt
END;

{ Read a single character from keyboard.  The only acceptable chara-    }
{  acters are SPACE, CR, ESCAPE, Y, and N.  If the parameter "default"  }
{  is "false", then SPACE, CR, or ESCAPE will produce the same result   }
{  as typing N.  If "default" is "true", then SPACE, CR, or ESCAPE will }
{  be the same as typing Y.                                             }
{ If the user enters Y, the function will write "Yes" to the terminal   }
{  and return a value of true; otherwise it will write "No" and return  }
{  a value of false.  If an unacceptable key is entered, the terminal   }
{  bell is sounded, and the function will await a legal response.       }
FUNCTION yes(default : Boolean) : Boolean;
VAR ch : char;
BEGIN
   REPEAT
       read(kbd,ch);
       IF ch IN [cr,space,esc] THEN
           IF default = false THEN
               ch := 'N'
           ELSE
               ch := 'Y';
       ch := upcase(ch);
       CASE ch OF
           'Y':    BEGIN
                       yes := true;
                       writeln('Yes')
                   END;
           'N':    BEGIN
                       yes := false;
                       writeln('No')
                   END
           ELSE    beep
       END{case}
   UNTIL ch IN ['Y','N']
END;

{ If the parameter string "fname" does not have an extension, then the  }
{  default extension '.PAS' is appended to it.                          }
PROCEDURE MakeFileName(VAR fname : string15);
VAR ExtPos : byte;
BEGIN
   ExtPos := pos('.',fname);
   IF ExtPos = 0 THEN
       fname := fname + '.PAS'
END;

{ Opens a text file for input or output, depending on the parameter     }
{  "mode".  MODE is either "I" for input or "O" for output.             }
PROCEDURE open(mode : char; VAR f : text; name : string15);
BEGIN
   {$I-}
   assign(f,name);
   CASE upcase(mode) OF
       'I':    BEGIN
                   reset(f);
                   IF IOresult <> 0 THEN
                       error('Can''t open '+name)
               END;
       'O':    BEGIN
                   reset(f);
                   IF IOresult = 0 THEN
                       BEGIN
                       beep;
                       write('File ',name,' exists.  Overwrite? ');
                       IF NOT yes(false) THEN
                           error('Aborting')
                       END
                   ELSE
                       rewrite(f)
               END
       ELSE    error('Bad file mode')
   END{case}
   {$I+}
END;  { open }

PROCEDURE MakeBackup(VAR InfileName : string15);
VAR i : byte;
BEGIN
   OldInfileName := InfileName;
   assign(infile,InfileName);
   i := pos('.',InfileName);
   IF i <> 0 THEN
       InfileName := copy(InfileName,1,i) + 'BAK'
   ELSE
       InfileName := InfileName + '.BAK';
   rename(infile,InfileName)
END;

{ Set Boolean flags.                                                    }
PROCEDURE SetParams;
BEGIN
   FormatPage := true;
   MarkReserved := true;
   ConOut := true;
   ListOut := false;
   FileOut := false;
   EraseOld := false;
   writeln;
   write('Source file name? ');
   readln(InfileName);
   MakeFileName(InfileName);
   MakeBackup(InfileName);
   open('i',infile,InfileName);
   writeln;
   write('Suppress marking of reserved words? ');
   IF yes(NOT MarkReserved) THEN
       MarkReserved := NOT MarkReserved;
   write('Disk file output? ');
   IF yes(FileOut) THEN
       FileOut := NOT FileOut;
   IF FileOut THEN
       BEGIN
       write(tab,'Output file name? ');
       readln(OutfileName);
       MakeFileName(OutfileName);
       open('o',outfile,OutfileName);
       write(tab,'Erase original file? ');
       IF yes(false) THEN
           EraseOld := true
       END;
   write('Console output? ');
   IF NOT yes(ConOut) THEN
       ConOut := NOT ConOut;
   write('Printer output? ');
   IF yes(ListOut) THEN
       ListOut := NOT ListOut;
   IF ListOut THEN
       BEGIN
       write('Suppress page formatting? ');
       IF yes(NOT FormatPage) THEN
           FormatPage := NOT FormatPage
       END
END;  { SetParams }

{ Main procedure.  Maps any reserved words to upper case.               }
PROCEDURE PrettyPrint;
VAR ch : char;
   state : (InWord,InStr,InComment,copying);
   word,TestWord : str;
   wptr : byte;

   { Display a PACKED ARRAY string to the output device(s) with all    }
   {  trailing blanks removed.                                         }
   PROCEDURE PrintWord(word : str);
   VAR i : byte;
   BEGIN
       i := 1;
       WHILE (word[i] <> ' ') AND (i <= StrLength) DO
           BEGIN
           out(word[i]);
           i := succ(i)
           END
   END;

   { Binary searches the KEYWORD list (global) to see if the parameter }
   {  "word" is a reserved word.                                       }
   FUNCTION IsReserved(word : str) : Boolean;
   VAR top,bottom,mid : byte;
   BEGIN
       top := NumReserved;
       bottom := 1;
       WHILE top > bottom DO
           BEGIN
           mid := (top + bottom) SHR 1;  { Same as (top+bottom) DIV 2. }
           IF word > KeyWord[mid] THEN
               bottom := succ(mid)
           ELSE
               top := mid
           END;{while}
       IF word = KeyWord[top] THEN
           IsReserved := true
       ELSE
           IsReserved := false
   END;  { IsReserved }

BEGIN { PrettyPrint }
   state := copying;
   word := blank16;
   read(infile,NextCh);                { Initialize the global NextCh. }
   WHILE NOT eof(infile) DO
       BEGIN
       ReadChar(ch);
       CASE state OF
           copying:    BEGIN
                           IF ((ch='(') AND (NextCh='*')) OR (ch='{') THEN
                               BEGIN
                               state := InComment;
                               out(ch)
                               END{if}
                           ELSE IF ch = '''' THEN
                               BEGIN
                               state := InStr;
                               out(ch)
                               END{if}
                           ELSE IF ch IN AlphaNum THEN
                               BEGIN
                               word := blank16;
                               state := InWord;
                               wptr := 1;
                               word[wptr] := ch
                               END{if}
                           ELSE
                               out(ch)
                       END;{case copying}
           InComment:  BEGIN
                           IF ((ch='*') AND (NextCh=')')) OR (ch = '}') THEN
                               state := copying;
                           out(ch)
                       END;{case InComment}
           InStr:      BEGIN
                           IF ch = '''' THEN
                               state := copying;
                           out(ch)
                       END;{case InStr}
           InWord:     BEGIN
                           WHILE (ch IN AlphaNum) AND (wptr <= StrLength) DO
                               BEGIN
                               wptr := succ(wptr);
                               word[wptr] := ch;
                               ReadChar(ch)
                               END;{while}
                           IF MarkReserved THEN
                               BEGIN
                               TestWord := word;
                               ToUpper(TestWord);
                               IF IsReserved(TestWord) THEN
                                   PrintWord(TestWord)
                               ELSE
                                   PrintWord(word)
                               END{if}
                           ELSE
                               PrintWord(word);
                           word := blank16;
                           out(ch);
                           IF ((ch='(') AND (NextCh='*')) OR (ch = '{') THEN
                               state := InComment
                           ELSE
                               state := copying
                       END{case InWord}
           END{case}
       END{while}
END;  { PrettyPrint }

BEGIN   (* Main Program *)
   SetParams;
   IF FormatPage AND ListOut THEN
       BEGIN
       PageNum := 1;
       LineNum := 1;
       write(lst,InfileName,cr,InfileName);
       write(lst,tab,tab,tab,tab,tab,tab,tab,tab,'Page ',PageNum);
       writeln(lst,lf,lf)
       END;
   IF ConOut THEN
       ClrScr;
   PrettyPrint;
   IF FileOut THEN
       BEGIN
       close(outfile);
       IF EraseOld THEN
           erase(infile)
       END
   ELSE
       rename(infile,OldInfileName)
END.











��y�"Sz!�9�� �