home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / forth / dada / dada.pas < prev    next >
Pascal/Delphi Source File  |  1994-05-05  |  50KB  |  1,008 lines

  1.  
  2.  
  3.     {       PROGRAM:              DADA.PAS
  4.  
  5.             AUTHOR:               Brian Hayes
  6.  
  7.             DATE BEGUN:           September 17, 1985
  8.  
  9.             FOR COMPILATION BY:   Turbo Pascal v 3.0
  10.     }
  11.     {       DESCRIPTION:
  12.             A compiler for a toy language called Dada, which adopts much
  13.             of the vocabulary of Pascal but lacks many of its features, most
  14.             notably functions, local variables, defined types and a facility
  15.             for passing parameters to procedures. The "object code" produced
  16.             by the compiler consists of Forth words. For additional detail see
  17.             the comments below and the accompanying file DADA.DOC.
  18.  
  19.             This program is intended for demonstration purposes only. It
  20.             has been compiled and casually tested, but it is certainly not
  21.             guaranteed to be error-free. Testing was done with version 3.0
  22.             of Borland International's Turbo Pascal, but I have tried to avoid
  23.             features peculiar to Borland's implementation. Adaptation to other
  24.             Pascal compilers should be easy.
  25.  
  26.             The compatibility of the object code with various Forth systems
  27.             is more difficult to assess. See the comments on the code gen-
  28.             erator and DADA.DOC Note 7.
  29.     }
  30.  
  31.     {       COMPILER DIRECTIVES  (Valid for Turbo Pascal only)   }
  32. (*
  33. {$B+}   {B+ assigns StdIn/StdOut to CON, B- to TRM; default +}
  34. {$C+}   {C+ allows ^C and ^S during Read/ReadLn; default +}
  35. {$I+}   {I+ enables automatic I/O error checking; default +}
  36. {$R-}   {R+ enables run-time checking of index bounds; default -}
  37. {$V-}   {V+ requires string parameters to match declared length; default +}
  38. {$U-}   {U+ allows ^C interrupt at any time; default -}
  39. {$D+}   {D+ unbuffers I/O for devices; default +}
  40. {$F16}  {Fn sets maximum number of files open simultaneously; default 16}
  41. {$K+}   {K+ enables checking for stack-heap collision; default +}
  42. *)
  43.  
  44.  
  45.  
  46. program Dada;
  47. USES
  48.   Crt;
  49. {*****************************************************************************}
  50. {*****************            GLOBAL CONSTANTS               *****************}
  51. {*****************************************************************************}
  52.  
  53. const
  54.  MaxIdentLen = 31;           { only the first 31 chars saved }
  55.  MaxKeyLen   =  9;           { longest keyword               }
  56.  MaxErrorLen = 35;           { longest error message         }
  57.  
  58.  
  59. {*****************************************************************************}
  60. {*****************              GLOBAL TYPES                 *****************}
  61. {*****************************************************************************}
  62.  
  63. type
  64.  IdentStr = string[MaxIdentLen];
  65.  KeyStr   = string[MaxKeyLen];
  66.  ErrorStr = string[MaxErrorLen];
  67.  ForthStr = string[64];              { for Forth output; see procedure Gen }
  68.  
  69.  
  70.     {       ErrCode identifies error messages in the array ErrorList;
  71.             see DADA.DOC Note 1.   }
  72.  
  73.  ErrCode = (Disk, QChar, XPgm, XIdent, XVar, XInt, XBool, XColon, XType, XSemi,
  74.             XBegin, XSemEnd, XThen, XDo, XAssgn, XStmt, DupDec, UnDec, Match,
  75.             XFactor, XParen, XDot, UnXEOF);
  76.  
  77.  
  78.     {       TokCode lists all symbols that can possibly be returned by the
  79.             scanner. Null is a placeholder that can appear in a few fields
  80.             of symbol-table entries.  }
  81.  
  82.  TokCode = (Null, Ident, Number, PgmSym, VarSym, ProcSym, BeginSym, EndSym,
  83.            IfSym, ThenSym, ElseSym, WhileSym, DoSym, IntSym, BoolSym, TrueSym,
  84.            FalseSym, EQ, GT, GE, NE, LE, LT, Plus, Minus, OrSym, Times, Divide,
  85.            AndSym, ModSym, NotSym, AssignOp, Colon, LeftParen, RightParen,
  86.            Semi, Dot, ReadSym, WriteSym);
  87.  
  88.  TokenRec = record        { Definition of the mailbox where the scanner }
  89.   Name  : IdentStr;       { leaves dope on the current token and where  }
  90.   Code  : TokCode;        { the parser picks it up.                     }
  91.  end;
  92.  
  93.  SymClass  = (Variable, Proc);  { Every symbol must be one or the other. }
  94.  SymPtr    = ^Symbol;           { Points to a symbol-table entry.        }
  95.  
  96.  
  97.     {    Format of a symbol-table entry. See DADA.DOC Note 2 }
  98.  
  99.  Symbol = record
  100.   Name    : IdentStr;      { UpCase string of name as read }
  101.   Class   : SymClass;      { either Variable or Proc       }
  102.   VarType : TokCode;       { either IntSym or BoolSym      }
  103.   Scope   : integer;       { zero for global, then 1,2,3...}
  104.   Next    : SymPtr;        { pointer to next table entry   }
  105.  end;
  106.  
  107.  
  108.     {    The output buffer represents a Forth "screen" of 16 lines
  109.          by 64 characters.    }
  110.  
  111.  OutBufLines = 1..16;
  112.  OutBufChars = 1..64;
  113.  OutBufArray = array[OutBufLines] of array[OutBufChars] of char;
  114.  
  115.  
  116. {*****************************************************************************}
  117. {*****************            GLOBAL VARIABLES               *****************}
  118. {*****************************************************************************}
  119.  
  120. var
  121.  OutLine  : OutBufLines;        { Declared global becaused called by }
  122.  OutPoint : OutBufChars;        { both InitOutBuf and Gen.           }
  123.  OutBuf   : OutBufArray;
  124.  
  125.  InFile  : Text;                 { source code }
  126.  OutFile : file of OutBufArray;  { object code }
  127.  
  128.  TK : TokenRec;         { where dope on the current token is stashed }
  129.  CH : char;             { current scanner input }
  130.  LineCount : integer;   { number of lines in source text }
  131.  
  132.  TypeSet   : set of TokCode;        { sets defined for convenience }
  133.  TFset     : set of TokCode;        { in the parsing logic         }
  134.  RelOpSet  : set of TokCode;
  135.  AddOpSet  : set of TokCode;
  136.  MultOpSet : set of TokCode;
  137.  
  138.  FirstSym : SymPtr;        { link to the start of the symbol-table chain }
  139.  
  140.  CurrentScope : integer;   { nesting depth of procedures }
  141.  
  142.  Keywords  : array[TokCode] of KeyStr;
  143.  ErrorList : array[ErrCode] of ErrorStr;
  144.  
  145.  
  146. {*****************************************************************************}
  147. {*****************************************************************************}
  148. {*****************                                           *****************}
  149. {*****************             UTILITY ROUTINES              *****************}
  150. {*****************                                           *****************}
  151. {*****************************************************************************}
  152. {*****************************************************************************}
  153.  
  154.     {    The Keywords and ErrorList arrays must be initialized when the
  155.          program is started. So must the five small sets used to form
  156.          symbols into groups.    }
  157.  
  158. procedure InitKeywords;
  159.  begin
  160.   Keywords[PgmSym]   := 'PROGRAM';
  161.   Keywords[VarSym]   := 'VAR';
  162.   Keywords[IntSym]   := 'INTEGER';
  163.   Keywords[BoolSym]  := 'BOOLEAN';
  164.   Keywords[BeginSym] := 'BEGIN';
  165.   Keywords[EndSym]   := 'END';
  166.   Keywords[IfSym]    := 'IF';
  167.   Keywords[ThenSym]  := 'THEN';
  168.   Keywords[ElseSym]  := 'ELSE';
  169.   Keywords[WhileSym] := 'WHILE';
  170.   Keywords[DoSym]    := 'DO';
  171.   Keywords[NotSym]   := 'NOT';
  172.   Keywords[OrSym]    := 'OR';
  173.   Keywords[AndSym]   := 'AND';
  174.   Keywords[ModSym]   := 'MOD';
  175.   Keywords[ProcSym]  := 'PROCEDURE';
  176.   Keywords[TrueSym]  := 'TRUE';
  177.   Keywords[FalseSym] := 'FALSE';
  178.   Keywords[ReadSym]  := 'READLN';
  179.   Keywords[WriteSym] := 'WRITELN';
  180.  end;
  181.  
  182. procedure InitErrorList;
  183.  begin
  184.   ErrorList[Disk   ] := 'Trouble with file or disk.';
  185.   ErrorList[QChar  ] := 'Unrecognized character in input.';
  186.   ErrorList[Xpgm   ] := 'No program header.';
  187.   ErrorList[XIdent ] := 'Identifier expected.';
  188.   ErrorList[XVar   ] := 'Variable expected.';
  189.   ErrorList[XInt   ] := 'Integer value expected.';
  190.   ErrorList[XBool  ] := 'Boolean value expected.';
  191.   ErrorList[XColon ] := 'Colon expected.';
  192.   ErrorList[XType  ] := 'Type designator expected.';
  193.   ErrorList[XSemi  ] := 'Semicolon expected.';
  194.   ErrorList[XBegin ] := '"Begin" expected.';
  195.   ErrorList[XSemEnd] := 'Semicolon or "end" expected.';
  196.   ErrorList[XThen  ] := '"Then" expected.';
  197.   ErrorList[XDo    ] := '"Do" expected.';
  198.   ErrorList[XAssgn ] := 'Assignment statement expected.';
  199.   ErrorList[XStmt  ] := 'Statement expected.';
  200.   ErrorList[DupDec ] := 'Duplicate declaration.';
  201.   ErrorList[UnDec  ] := 'Undeclared variable or procedure.';
  202.   ErrorList[Match  ] := 'Type mismatch.';
  203.   ErrorList[XFactor] := 'Factor expected.';
  204.   ErrorList[XParen ] := 'Closing parenthesis expected.';
  205.   ErrorList[XDot   ] := 'Period expected.';
  206.   ErrorList[UnXEOF ] := 'Unexpected end of file.';
  207.  end;
  208.  
  209. procedure InitSets;
  210.  begin
  211.   TypeSet   := [IntSym, BoolSym];
  212.   TFset     := [TrueSym, FalseSym];
  213.   RelOpSet  := [EQ..LT];
  214.   AddOpSet  := [Plus..OrSym];
  215.   MultOpSet := [Times..ModSym];
  216.  end;
  217.  
  218.  
  219.     {    The error-handling given here is minimal. Procedure Error is
  220.          handed a code and prints the corresponding string. The only
  221.          information supplied on what might have caused the error is
  222.          a line number. The program then halts. See DADA.DOC Note 3.   }
  223.  
  224. procedure Error(Problem : ErrCode);
  225.  begin
  226.   WriteLn('ERROR IN LINE ',LineCount,':  ',ErrorList[Problem]);
  227.   WriteLn; WriteLn('Compilation aborted.');
  228.   Halt;
  229.  end;
  230.  
  231. procedure SayHello;
  232.  begin
  233.   ClrScr;
  234.   WriteLn;
  235.   WriteLn;
  236.   WriteLn('DADA: A demonstration compiler');
  237.   WriteLn;
  238.   WriteLn('This program is described in Computer Language, December, 1985');
  239.   WriteLn;
  240.   WriteLn;
  241.   WriteLn;
  242.  end;
  243.  
  244.  
  245.     {    The file handling is as rudimentary as the error routine. Further-
  246.          more, the version given here depends on features peculiar to Turbo
  247.          Pascal. See DADA.DOC Note 4.      }
  248.  
  249. procedure OpenFiles;
  250.  var
  251.   FileOK : boolean;
  252.   InFileName  : string[14];
  253.   OutFileName : string[14];
  254.  begin
  255.   Write('Enter the name of the file to be compiled: ');
  256.   ReadLn(InFileName);
  257.   Assign(InFile, InFileName);
  258.   {$I-} Reset(InFile); {$I+}
  259.   FileOK := (IoResult = 0); if not FileOK then Error(Disk);
  260.   WriteLn;
  261.   Write('Enter the name of the output file:         ');
  262.   ReadLn(OutFileName);
  263.   Assign(OutFile, OutFileName);
  264.   {$I-} ReWrite(OutFile); {$I+};
  265.   FileOK := (IoResult = 0); if not FileOK then Error(Disk);
  266.   WriteLn; WriteLn;
  267.  end;
  268.  
  269. procedure CloseFiles;
  270.  begin
  271.   Close(InFile);
  272.   Close(OutFile);
  273.  end;
  274.  
  275.  
  276. {*****************************************************************************}
  277. {*****************************************************************************}
  278. {*****************                                           *****************}
  279. {*****************             LEXICAL ANALYZER              *****************}
  280. {*****************                                           *****************}
  281. {*****************************************************************************}
  282. {*****************************************************************************}
  283.  
  284.     {     Procedure GetTK constitutes the scanner, or lexical analyzer. It
  285.           calls on GetCH to read the next character from the input stream
  286.           and uses Recognize to set up the two global-variable fields
  287.           TK.Code and TK.Name. The main routine first strips out all com-
  288.           ments and whitespace characters and then enters a state deter-
  289.           mined by the first character of the remaining input. Each state
  290.           corresponds to one clause of the case statement. Note that GetCH
  291.           converts all alphabetic characters to upper case, so that the
  292.           compiler in insensitive to case. Two errors can be issued by the
  293.           scanner. Error(QChar) is called if a character outside the recog
  294.           nized set appears in the input (except in comments). Error(UnXEOF)
  295.           is reported if the scanner encounters end-of-file. Since GetTK
  296.           is not called after the final period marking the end of a pro-
  297.           gram, the scanner should never read the end of the file.
  298.  
  299.           NOTE: Each time the scanner is called, it goes to work on the
  300.           character already in variable CH, not on the next character
  301.           from the stream. When GetTK exits, CH holds the first char-
  302.           acter beyond the token returned. In other words, the file
  303.           pointer is pre-incremented.
  304.  
  305.           See also DADA.DOC Note 5.      }
  306.  
  307. procedure GetTK;
  308.  var
  309.   I : TokCode;                     { used in a FOR loop to check for keywords }
  310.  
  311.  procedure GetCH;
  312.   begin
  313.    if Eof(InFile) then CH := #0 else Read(InFile, CH);  { get next if possible}
  314.    CH := Upcase(CH);                                    { make case immaterial}
  315.    if CH = #13 then LineCount := LineCount + 1;         { count for Error     }
  316.   end;
  317.  
  318.  procedure Recognize(Tok: TokCode);        { Called once for each character   }
  319.   begin                                    { scanned, adding it to the string }
  320.    TK.Code := Tok;                         { in TK.Name and recording the     }
  321.    TK.Name := Concat(TK.Name,CH);          { current analysis in TK.Code. Note}
  322.    GetCH;                                  { that TK.Code is not actually     }
  323.   end;                                     { valid until GetTK returns.       }
  324.  
  325.  
  326.     {    The first section of GetTK strips out comments and the whitespace
  327.          characters #9 (tab), #10 (line feed), #12 (form feed), #13 (carriage
  328.          return) and $32 (space). For comments any characters following a
  329.          left brace are ignored up to the first right brace. Note that this
  330.          means comments cannot be nested: Any number of opening braces will
  331.          be canceled by the first closing brace. The nested while loops are
  332.          needed because comments and whitespace can be interspersed in any
  333.          sequence.      }
  334.  
  335.  begin   { GetTK }
  336.   while (CH in ['{',#9,#10,#12,#13,#32]) do       { loop while comment, space }
  337.    begin
  338.     if CH = '{' then repeat GetCH until CH = '}'; { eat up the comment        }
  339.     GetCH;                                        { toss out the right brace  }
  340.     while (CH in [#9,#10,#12,#13,#32]) do GetCH;  { eat the whitespace        }
  341.    end;
  342.   TK.Name := '';                        { reset the identifier string to null }
  343.   case CH of                            { look at the current char from stream}
  344.    'A'..'Z' : begin                                         { Ident or keyword}
  345.                while (CH in ['A'..'Z','0'..'9']) do         { add chars to the}
  346.                 Recognize(Ident);                           { TK.Name string  }
  347.                for I := PgmSym to WriteSym do               { An Ident unless }
  348.                 if Keywords[I] = TK.Name then TK.Code := I; { listed here     }
  349.               end;
  350.    '0'..'9' : while (CH in ['0'..'9']) do Recognize(Number); { numeric literal}
  351.    '>' : begin
  352.           Recognize(GT);                              { With two-symbol oper- }
  353.           if CH = '=' then Recognize(GE);             { ators, start by assum-}
  354.          end;                                         { ing the one-symbol    }
  355.    '<' : begin                                        { form and then revise  }
  356.           Recognize(LT);                              { the verdict if the    }
  357.           if CH = '>' then Recognize(NE)              { second character is   }
  358.           else if CH = '=' then Recognize(LE)         { found.                }
  359.          end;
  360.    ':' : begin
  361.           Recognize(Colon);
  362.           if CH = '=' then Recognize(AssignOp);
  363.          end;
  364.    '=' : Recognize(EQ);
  365.    '+' : Recognize(Plus);
  366.    '-' : Recognize(Minus);
  367.    '*' : Recognize(Times);
  368.    '/' : Recognize(Divide);
  369.    '(' : Recognize(LeftParen);
  370.    ')' : Recognize(RightParen);
  371.    ';' : Recognize(Semi);
  372.    '.' : Recognize(Dot);
  373.    #0  : Error(UnXEOF);      { Program has ended without a period }
  374.    else  Error(QChar);       { Queer character; can't digest it }
  375.   end;
  376.  end;
  377.  
  378.  
  379. {*****************************************************************************}
  380. {*****************************************************************************}
  381. {*****************                                           *****************}
  382. {*****************               SYMBOL TABLE                *****************}
  383. {*****************                                           *****************}
  384. {*****************************************************************************}
  385. {*****************************************************************************}
  386.  
  387.     {    The three routines Find, Declare and Blot manage the symbol
  388.          table. The table is organized as a linked list in which
  389.          FirstSym always points to the most recently added entry. The
  390.          Next field points to the next-youngest entry, so that
  391.          following the chain of Nexts ultimately leads to the first
  392.          entry, which is always the declaration of the program
  393.          header. Because all variables in Dada are global, the
  394.          symbol table has a fixed, predictable structure: the program
  395.          declaration is followed by variable declarations and then by
  396.          procedure declarations. See DADA.DOC Note 6.   }
  397.  
  398.     {    Find is passed an identifier string and returns either a
  399.          pointer to the corresponding symbol-table entry or nil if
  400.          the identifier does not exist. It traverses the chain of
  401.          entries beginning with FirstSym, and so the first matching
  402.          entry will be found.   }
  403.  
  404. function Find(ID: IdentStr): SymPtr;
  405.  var
  406.   ThisSym : SymPtr;
  407.  begin
  408.   ThisSym := FirstSym;                    { start with the latest entry     }
  409.   while ((ID<>ThisSym^.Name) and          { loop if no match and...         }
  410.         (ThisSym<>nil)) do                { we're not at the end of list    }
  411.           ThisSym := ThisSym^.Next;       { get next record                 }
  412.   Find := ThisSym;                        { a match if there is one, or nil }
  413.  end;
  414.  
  415.  
  416.     {    Declare installs both variable names and procedure names in
  417.          the symbol table. ID is the name of the Identifier, as given
  418.          in TK.Name; CL is either "Proc" or "Variable"; Kind is "IntSym"
  419.          or "BoolSym" for variables, "Null" for procedures.  }
  420.  
  421. procedure Declare(ID: IdentStr; CL: SymClass; Kind: TokCode);
  422.  var
  423.   ThisSym : SymPtr;
  424.  begin
  425.   ThisSym := Find(ID);                        { See if it already exists     }
  426.   if ThisSym <> nil then Error(DupDec);       { Call error & halt if it does }
  427.   New(ThisSym);                               { Create a new record          }
  428.   ThisSym^.Next := FirstSym;                  { Swap pointers to put the...  }
  429.   FirstSym := ThisSym;                        { ...new record first in list  }
  430.   with FirstSym^ do
  431.    begin
  432.     Name    := ID;                            { Plug in the values passed... }
  433.     Class   := CL;                            { ...as arguments...           }
  434.     VarType := Kind;
  435.     Scope   := CurrentScope;                  { ...and a value from a global }
  436.    end;
  437.  end;
  438.  
  439.  
  440.     {    Blot is called when the "end" of a block is reached and removes
  441.          from the symbol table all names whose scope is confined to that
  442.          block. The global variable CurrentScope is initialized to zero
  443.          and incremented each time ParseBlock is called. Blot decrements
  444.          CurrentScope and unlinks from the symbol table any entry whose
  445.          Scope field is numerically greater than CurrentScope.   }
  446.  
  447. procedure Blot;
  448.  var
  449.   TrashSym : SymPtr;
  450.  begin
  451.   CurrentScope := CurrentScope - 1;        { back to scope of next outer block}
  452.   while FirstSym^.Scope > CurrentScope do  { erase entries for closed block   }
  453.    begin
  454.     TrashSym := FirstSym;                         { Give the pointer an alias }
  455.     FirstSym := FirstSym^.Next;                   { Unlink the record         }
  456.     Dispose(TrashSym);                            { Free the allocated memory }
  457.    end;
  458.  end;
  459.  
  460.  
  461. {*****************************************************************************}
  462. {*****************************************************************************}
  463. {*****************                                           *****************}
  464. {*****************              CODE GENERATOR               *****************}
  465. {*****************                                           *****************}
  466. {*****************************************************************************}
  467. {*****************************************************************************}
  468.  
  469.     {    The code generator is simple to the point of triviality, largely
  470.          because the Forth virtual machine offers a very powerful assembly
  471.          language. All address calculations, for instance, are done by the
  472.          Forth interpreter. With a Forth system that accepts input as a
  473.          sequence of CR/LF-delimited lines, the code generator could be
  474.          reduced to a one-line procedure: WriteLn(OutFile,Forth). The
  475.          routines given here produce Forth "screens," or blocks of 1,024
  476.          bytes filled out with blanks (ASCII #32). For more on this for-
  477.          matting see DADA.DOC Note 7.
  478.  
  479.          The main procedure of the code generator is Gen, which is called
  480.          by the various parsing routines; the argument is a string to be
  481.          written to the output file. The string is actually appended to
  482.          a buffer that holds 16 lines of 64 characters (the standard .SCR
  483.          format). When a line exceeds 62 characters, a new line is started;
  484.          when line 16 is reached, the continuation symbol "-->" is written
  485.          and the buffer is flushed to the disk and then reset to all blanks.
  486.          To make the generated code more readable, the symbol "|" is defined
  487.          as a control character that forces Gen to start a new line. The
  488.          parsing routines issue Gen('|') after each colon definition.
  489.  
  490.          InitOutBuf sets the 1,024 bytes of the output buffer to the
  491.          ASCII blank character (#32) and resets the two array indices
  492.          OutLine and OutPoint to 1, which corresponds to the upper
  493.          left-hand corner of a Forth screen. The procedure is not made
  494.          local to Gen because it is called from the main initializing
  495.          routine at program startup.    }
  496.  
  497. procedure InitOutBuf;
  498.  begin
  499.   for OutLine := 1 to 16 do
  500.    for OutPoint := 1 to 64 do OutBuf[Outline,OutPoint] := #32;
  501.   OutLine := 1; OutPoint := 1;
  502.  end;
  503.  
  504. procedure Gen(Forth : ForthStr);
  505.  var
  506.   FileOK : boolean;
  507.   I, TempPoint, TempLine : integer;   { two temps for testing length }
  508.  
  509.  
  510.     {    WriteBuf, like OpenFiles, is written with a Turbo-specific
  511.          error-checking method. It simply writes the accumulated
  512.          buffer to the output file and, if there is no disk error,
  513.          calls InitOutBuf to reinitialize the array.    }
  514.  
  515.  procedure WriteBuf;
  516.   begin
  517.    {$I-} Write(OutFile,OutBuf); {$I+}
  518.    FileOK := (IoResult = 0); if not FileOK then Error(Disk);
  519.    InitOutBuf;
  520.   end;
  521.  
  522.  
  523.     {    NewLine resets the character counter and tests the line count;
  524.          if we are on line 15, the recursive call Gen('-->') flushes
  525.          the buffer and starts a new screen.   }
  526.  
  527.  procedure NewLine;
  528.   begin
  529.    OutPoint := 1; TempLine := OutLine + 1;
  530.    if TempLine >= 15 then Gen('-->') else OutLine := TempLine;
  531.   end;
  532.  
  533.  begin   { Gen }
  534.   if Forth = '|' then begin NewLine; exit; end;  { force new line & leave     }
  535.   TempPoint := OutPoint + Length(Forth);         { Temp avoids out-of-range   }
  536.   if TempPoint > 62 then NewLine;                { 62 (not 64) to allow blanks}
  537.   for I := 1 to Length(Forth) do
  538.    begin
  539.     OutBuf[OutLine,OutPoint] := Forth[I];        { copy the string into buffer}
  540.     OutPoint := OutPoint + 1;
  541.    end;
  542.   OutPoint := OutPoint + 1;                      { allow one blank after code }
  543.   if ((Forth = '-->') or (Forth = ';S')) then WriteBuf;
  544.  end;
  545.  
  546.  
  547.     {    GenHeader creates a "run-time library" that precedes the object
  548.          code for all Dada programs. Some Forth systems may need additional
  549.          or different definitions here. The READ routine provides keyboard
  550.          input of signed integers. It could readily be improved.   }
  551.  
  552. procedure GenHeader(PgmName : IdentStr);
  553.  begin
  554.   Gen('( Output of Dada compiler )'); Gen('|');    {  Screen 0 comments     }
  555.   Gen(Concat('( To execute type: 1 LOAD ',PgmName,' )')); Gen(';S');
  556.   Gen('FORTH DEFINITIONS DECIMAL'); Gen('|');
  557.   Gen('1 CONSTANT TRUE  ');                        {                        }
  558.   Gen('0 CONSTANT FALSE');                         {  These synonyms will   }
  559.   Gen(': NEGATE MINUS ;'); Gen('|');               {  not be needed by all  }
  560.   Gen(': NOT 0= ;');                               {  Forth systems; others }
  561.   Gen(': <> = NOT ;');                             {  may be required.      }
  562.   Gen(': >= < NOT ;');                             {                        }
  563.   Gen(': <= > NOT ;'); Gen('|');                   {                        }
  564.   Gen(': READ KEY DUP 45 = IF TRUE SWAP EMIT KEY ELSE FALSE SWAP'); Gen('|');
  565.   Gen('  THEN 0 SWAP BEGIN DUP 13 = NOT WHILE DUP 48 < OVER'); Gen('|');
  566.   Gen('  57 > OR IF DROP 7 EMIT ELSE DUP EMIT 48 - SWAP 10 * +'); Gen('|');
  567.   Gen('  THEN KEY REPEAT DROP SWAP IF NEGATE THEN SWAP ! ;'); Gen('|');
  568.   Gen(': WRITE @ . CR ;');
  569.   Gen('-->');
  570.  end;
  571.  
  572.  
  573. {*****************************************************************************}
  574. {*****************************************************************************}
  575. {*****************                                           *****************}
  576. {*****************                  PARSER                   *****************}
  577. {*****************                                           *****************}
  578. {*****************************************************************************}
  579. {*****************************************************************************}
  580.  
  581.     {    ParseProgram and the routines nested under it constitute the main
  582.          driver of DADA.PAS. The organization is outlined in DADA.DOC Note 8.
  583.          Each routine calls on GetTK (the scanner). Statements haveing to do
  584.          with parsing proper are interleaved with those for type checking
  585.          and code generation.    }
  586.  
  587. procedure ParseProgram;
  588.  var
  589.   HoldID : IdentStr;                { hangs onto the program name }
  590.  
  591.  
  592.     {    ParseVariables is called once by ParseProgram. If the current
  593.          token is not "var," there are no variables in the program and
  594.          the routine exits. Otherwise each declaration is checked for
  595.          proper form and a statement "0 VARIABLE IDENT" is generated to
  596.          allocate 16 bits of storage and record its address under the
  597.          name IDENT in the Forth dictionary.   }
  598.  
  599.  procedure ParseVariables;
  600.   var
  601.    HoldVar : IdentStr;
  602.   begin
  603.    if TK.Code = VarSym then             { else no variables in entire program }
  604.     begin
  605.      GetTK;                                             { eat the "var" token }
  606.      repeat                          { loop for arbitrary number of variables }
  607.       if TK.Code <> Ident then Error(XIdent);      { format is "Ident: Type;" }
  608.       HoldVar := TK.Name; GetTK;                   { hang onto identifier     }
  609.       if TK.Code <> Colon then Error(XColon); GetTK;
  610.       if not (TK.Code in TypeSet) then Error(XType); { TypeSet=IntSym,BoolSym }
  611.       Declare(HoldVar,Variable,TK.Code); GetTK;      { install in symbol table}
  612.       Gen(Concat('0 VARIABLE ',HoldVar)); Gen('|');  { gen code & new line    }
  613.       if TK.Code <> Semi then Error(XSemi); GetTK;   { every decl. must have  }
  614.      until (TK.Code in [ProcSym,BeginSym]);          { no more variables      }
  615.     end;
  616.   end;
  617.  
  618.  procedure ParseBlock(Caller: IdentStr);    { "Caller" will be the Ident gen-}
  619.   var                                       { erated when "begin" is reached.}
  620.    HoldID : IdentStr;                       { HoldID passed as Caller to     }
  621.                                             { next nested block.             }
  622.  
  623.   procedure ParseStatement;
  624.    var
  625.     IdentPtr : SymPtr;                      { used to check symbol table      }
  626.     HoldID   : IdentStr;                    { hold while class & type checked }
  627.     HoldType : TokCode;                     { hold while exp. type is checked }
  628.  
  629.  
  630.     {    All the routines from ParseExpression on down are defined as
  631.          functions rather than procedures. They return the type (integer
  632.          or boolean) deduced from the operations specified. The "HoldOp"
  633.          variables are needed to delay code generation for postfix notation.
  634.          The "HoldType" variables record the type of the first operand so
  635.          that it can be compared with the type of the second operand.   }
  636.  
  637.    function ParseExpression: TokCode;
  638.     var
  639.      HoldRelOp : IdentStr;
  640.      HoldType  : TokCode;
  641.  
  642.     function ParseSimpleExpr: TokCode;
  643.      var
  644.       HoldAddOp : IdentStr;
  645.       HoldType  : TokCode;
  646.  
  647.      function ParseTerm: TokCode;
  648.       var
  649.        HoldMultOp : IdentStr;
  650.        HoldType   : TokCode;
  651.  
  652.       function ParseSignedFactor: TokCode;
  653.        var
  654.         IdentPtr : SymPtr;
  655.         HoldType : TokCode;
  656.  
  657.  
  658.     {    ParseFactor is the lowest-level routine in the parser. For a factor
  659.          to be recognized as valid it must be either a boolean literal (TRUE
  660.          of FALSE), a numeric literal, an identifier that designates a var-
  661.          iable or a parenthesized expression. The case statement considers
  662.          each of these possibilities in turn.   }
  663.  
  664.        function ParseFactor: TokCode;
  665.         var
  666.          IdentPtr: SymPtr;               { needed to consult the symbol table }
  667.         begin
  668.          case TK.Code of
  669.           TrueSym,
  670.           FalseSym  : begin
  671.                        ParseFactor := BoolSym;          { return type boolean }
  672.                        Gen(TK.Name); GetTK;             { Gen TRUE or FLASE   }
  673.                       end;
  674.           Number    : begin
  675.                        ParseFactor := IntSym;           { return type integer }
  676.                        Gen(TK.Name); GetTK;             { Gen numeric literal }
  677.                       end;
  678.           Ident     : begin
  679.                        IdentPtr := Find(TK.Name);          { look up the name }
  680.                        if IdentPtr = nil then Error(UnDec)       { not found? }
  681.                        else begin
  682.                         if IdentPtr^.Class <> Variable        { can't be proc }
  683.                          then Error(XVar)
  684.                          else begin
  685.                           ParseFactor := IdentPtr^.VarType; { rtn Int or Bool }
  686.                           Gen(ConCat(TK.Name,' @')); GetTK; { code to fetch   }
  687.                         end;
  688.                        end;
  689.                       end;
  690.           LeftParen : begin                { call ParseExpression recursively }
  691.                        GetTK;                           { and return the type }
  692.                        ParseFactor := ParseExpression;      { that it returns }
  693.                        if TK.Code <> RightParen then Error(XParen);
  694.                        GetTK;                                   { eat the ")" }
  695.                       end;
  696.           else        Error(XFactor);  { if none of above, not a valid factor }
  697.          end;
  698.         end;
  699.  
  700.  
  701.     {    ParseSignedFactor is introduced into the chain of expression-
  702.          parsing functions merely to handle a unary plus, minus or logical
  703.          NOT preceding a factor. If none of these is found, the code drops
  704.          through directly to ParseFactor. If one of them is found, the
  705.          appropriate code is generated after ParseFactor returns, thereby
  706.          converting the notation to postfix form.    }
  707.  
  708.        begin   {ParseSignedFactor}
  709.         case TK.Code of
  710.          Plus      : begin
  711.                       GetTK;                               { eat the + sign   }
  712.                       HoldType := ParseFactor;             { parse & get type }
  713.                       if HoldType <> IntSym
  714.                        then Error(XInt)                    { +boolean illegal }
  715.                        else ParseSignedFactor := IntSym;   { HoldType=Int     }
  716.                      end;
  717.          Minus     : begin
  718.                       GetTK;                               { eat the - sign   }
  719.                       HoldType := ParseFactor;             { parse & get type }
  720.                       if HoldType <> IntSym
  721.                        then Error(XInt)                    { -boolean illegal }
  722.                        else begin
  723.                         ParseSignedFactor := IntSym;       { HoldType = Int   }
  724.                         Gen('NEGATE');                     { code toggles sign}
  725.                        end;
  726.                      end;
  727.          NotSym    : begin
  728.                       GetTK;                              { eat NOT symbol    }
  729.                       HoldType := ParseFactor;            { parse & get type  }
  730.                       if HoldType <> BoolSym
  731.                        then Error(XBool)                  { NOT number illegal}
  732.                        else begin
  733.                         ParseSignedFactor := BoolSym;     { HoldType = boolean}
  734.                         Gen('NOT');                       { code to invert    }
  735.                        end;
  736.                      end;
  737.          else        ParseSignedFactor := ParseFactor;    { no +, -, NOT found}
  738.         end;
  739.        end;
  740.  
  741.  
  742.     {    ParseTerm recognizes either "SignedFactor" or a subexpression of
  743.          the form "SignedFactor MultOp Term". Thus it will always call
  744.          ParseSignedFactor, and if the next token is a MultOp, it will
  745.          also call itself recursively.    }
  746.  
  747.     {    For a lacuna in type-checking, see DADA.DOC Note 9.    }
  748.  
  749.       begin   {ParseTerm}
  750.        HoldType := ParseSignedFactor;     { parse & get type first operand }
  751.        if (TK.Code in MultOpSet) then     { TK = *, /, OR?                 }
  752.         begin
  753.          HoldMultOp := TK.Name;           { save the Op for postfix        }
  754.          GetTK;                           { and eat it                     }
  755.          if not (HoldType = ParseTerm)    { parse & get type 2d operand    }
  756.           then Error(Match);              { 1st & 2d operands same type?   }
  757.          Gen(HoldMultOp);                 { issue the saved operator       }
  758.         end;
  759.        ParseTerm := HoldType;             { return the operand type        }
  760.       end;
  761.  
  762.  
  763.     {    ParseSimpleExpr recognizes either "Term" or a subexpression of
  764.          the form "Term AddOp SimpleExpr". It always calls ParseTerm
  765.          and if the next token is an AddOp, it also calls itself.    }
  766.  
  767.      begin   {ParseSimpleExpr}
  768.       HoldType := ParseTerm;                   { parse & get type 1st operand }
  769.       if (TK.Code in AddOpSet) then            { TK = +, -, AND?              }
  770.        begin
  771.         HoldAddOp := TK.Name;                  { save the Op for postfix      }
  772.         GetTK;                                 { and eat it                   }
  773.         if not (HoldType = ParseSimpleExpr)    { parse & get type 2d operand  }
  774.          then Error(Match);                    { 1st & 2d operands same type? }
  775.         Gen(HoldAddOp);                        { issue the save operator      }
  776.        end;
  777.       ParseSimpleExpr := HoldType;             { return the operand type      }
  778.      end;
  779.  
  780.  
  781.     {    ParseExpression recognizes either "SimpleExpr" or a sub-
  782.          expression of the form "SimpleExpr RelOp SimpleExpr." It always
  783.          calls ParseSimpleExpr once, and if the next token is a RelOp, it
  784.          also makes a second call to ParseSimpleExpr. Note that this scheme
  785.          is slightly different from the recursive pattern in the lower-
  786.          level functions. On that model one would expect "SimpleExpr RelOp
  787.          Expression," so that to parse the second operand the function would
  788.          call itself. Such a construction, however, would allow expressions
  789.          of the form A > B < C = D, and so on. It would be easy enough to
  790.          assign a meaning to these expressions, but the language definition
  791.          does not supply one.    }
  792.  
  793.     begin                           {ParseExpression}
  794.      HoldType := ParseSimpleExpr;        { parse & get type 1st operand    }
  795.      ParseExpression := HoldType;        { type to be returned if no RelOp }
  796.      if (TK.Code in RelOpSet) then       { TK is >, <, =, etc. ?           }
  797.       begin
  798.        HoldRelOp := TK.Name;             { save operator for postfix       }
  799.        GetTK;                            { and eat it                      }
  800.        if not (HoldType = ParseSimpleExpr) { parse & get type 2d operand   }
  801.         then Error(Match);               { 1st & 2d operands same type ?   }
  802.        ParseExpression := BoolSym;       { if Expr has Relop, type = bool  }
  803.        Gen(HoldRelOp);                   { issue the saved operator        }
  804.       end;
  805.     end;
  806.  
  807.  
  808.     {    ParseStatement is the most elaborate routine in the parser. The
  809.          grammar for Dada specifies five constructs to be recognized as
  810.          valid statements: a compound statement delimited by "begin" and
  811.          "end," an assignment statement, a procedure call, an "if" state-
  812.          ment and a "while" statement. The parser actually includes two
  813.          more possibilities: "Read" and "Write" statements, which can be
  814.          viewed as predefined procedures. With one exception the grammar
  815.          allows these possibilities to be distinguished on the basis of
  816.          the first token presented to ParseStatement. The exception is
  817.          the discrimination between assignment statements and procedure
  818.          calls, which both begin with an identifier. The parser chooses
  819.          its path by checking the identifier's class in the symbol table:
  820.          a value can be assigned only to a variable, and only a procedure
  821.          can be called.     }
  822.  
  823.     {    See also DADA.DOC Note 10    }
  824.  
  825.    begin   {ParseStatement}
  826.     case TK.Code of
  827.      BeginSym : begin                                 { must be compound   }
  828.                  GetTK;                               { eat the "BEGIN"    }
  829.                  while TK.Code <> EndSym do           { loop while stmts   }
  830.                   begin
  831.                    ParseStatement;                    { calls itself       }
  832.                    if not (TK.Code in [Semi,EndSym])  { delimiter expected }
  833.                     then Error(XSemEnd);
  834.                    if TK.Code = Semi then GetTK;      { go back for another}
  835.                   end;
  836.                  GetTK;                         { TK must be "END"; eat it }
  837.                 end;
  838.      IfSym    : begin                                { must be If statement}
  839.                  GetTK;                              { eat the "IF"        }
  840.                  if not (BoolSym = ParseExpression)  { parse expr & ck type}
  841.                   then Error(XBool);                 { only boolean allowed}
  842.                  Gen('IF');                          { Forth IF after expr }
  843.                  if TK.Code <> ThenSym               { must have then part }
  844.                   then Error(XThen); GetTK;          { if present, eat it  }
  845.                  ParseStatement;                     { calls itself        }
  846.                  if TK.Code = ElseSym then           { else is optional    }
  847.                   begin                              { if present, Gen code}
  848.                    Gen('ELSE'); GetTK;               { and eat the token   }
  849.                    ParseStatement;                   { calls itself again  }
  850.                   end;
  851.                  Gen('THEN');                        { end of Forth cond.  }
  852.                 end;
  853.      WhileSym : begin                                  { this is a while loop}
  854.                  Gen('BEGIN'); GetTK;                  { Gen marker; eat tok }
  855.                  if not (BoolSym = ParseExpression)    { parse and check type}
  856.                   then Error(XBool);                   { must be boolean     }
  857.                  if TK.Code <> DoSym then Error(XDo);  { must have Do part   }
  858.                  Gen('WHILE'); GetTK;                  { eat; Gen Forth test }
  859.                  ParseStatement;                       { recursive call      }
  860.                  Gen('REPEAT');                        { end of Forth block  }
  861.                 end;
  862.      Ident    : begin                                  { assignment or call  }
  863.                  IdentPtr := Find(TK.Name);            { look up in table    }
  864.                  if IdentPtr = nil then Error(UnDec);  { can't find it       }
  865.                  if IdentPtr^.Class = Variable then    { must be assignment  }
  866.                   begin
  867.                    HoldType := IdentPtr^.VarType;      { save Ident type...  }
  868.                    HoldID := TK.Name; GetTK;           { and name for postfix}
  869.                    if TK.Code <> AssignOp              { must have := sign   }
  870.                     then Error(XAssgn); GetTK;         { if so, eat it       }
  871.                    if not (HoldType = ParseExpression) { parse expr & ck type}
  872.                     then Error(Match);                 { report mismatch     }
  873.                    Gen(Concat(HoldID,' !'));           { code to store value }
  874.                   end
  875.                  else                                  { must be proc call   }
  876.                   begin                                { invoke the Forth    }
  877.                    Gen(TK.Name); GetTK;                { word and consume    }
  878.                   end;                                 { the token           }
  879.                 end;
  880.      ReadSym  : begin                               { predefined READ proc   }
  881.                  GetTK;                             { eat token              }
  882.                  if TK.Code <> Ident                { must name variable...  }
  883.                   then Error(XIdent);               { to hold the value read }
  884.                  IdentPtr := Find(TK.Name);         { look up in table       }
  885.                  if IdentPtr^.Class <> Variable     { cannot be proc Ident   }
  886.                   then Error(XVar);
  887.                  if IdentPtr^.VarType <> IntSym     { only integers can...   }
  888.                   then Error(XInt);                 { be read in Dada        }
  889.                  Gen(Concat(TK.Name,' READ'));      { issue the call in Forth}
  890.                  GetTK;                             { eat up the Ident       }
  891.                 end;
  892.      WriteSym : begin                               { predefined WRITE proc  }
  893.                  GetTK;                             { eat token              }
  894.                  if TK.Code <> Ident                { must name variable...  }
  895.                   then Error(XIdent);               { to be written          }
  896.                  IdentPtr := Find(TK.Name);         { look it up             }
  897.                  if IdentPtr^.Class <> Variable     { cannot be Proc name    }
  898.                   then Error(XVar);
  899.                  if IdentPtr^.VarType <> IntSym     { only integers can...   }
  900.                   then Error(XInt);                 { be written             }
  901.                  Gen(Concat(TK.Name,' WRITE'));     { issue the call         }
  902.                  GetTK;                             { consume the Ident      }
  903.                 end;
  904.      else       Error(XStmt);                       { if none of the above   }
  905.     end;
  906.    end;
  907.  
  908.  
  909.     {    ParseBlock has two parts. It first checks for a procedure declar-
  910.          ation; if it finds one, it parses the header and calls itself again.
  911.          Ultimately, the BEGIN symbol that marks the statement part of a block
  912.          must be reached. Each statement is then processed in turn (by Parse-
  913.          Statement) until the matching END is reached. The possible nesting
  914.          of blocks within blocks is accommodated automatically by the re-
  915.          cursive organization of the routines. Recall that ParseBlock is passed
  916.          an identifier as an argument, namely the Ident of the procedure or
  917.          program that issued the call. This Ident is written into the code as
  918.          the designator of a Forth word when "begin" is reached.    }
  919.  
  920.   begin   { ParseBlock }
  921.    CurrentScope := CurrentScope + 1;             { bump up nesting count      }
  922.    while TK.Code = ProcSym do                    { proc declarations          }
  923.     begin
  924.      GetTK;                                      { eat "procedure" token      }
  925.      if TK.Code <> Ident then Error(XIdent);     { proc must have name        }
  926.      HoldID := TK.Name;                          { save to pass to next level }
  927.      Declare(TK.Name,Proc,Null);                 { put in table as proc name  }
  928.      GetTK;                                      { eat the Ident              }
  929.      if TK.Code <> Semi then Error(XSemi);       { must have a semi           }
  930.      GetTK;                                      { throw the semi away        }
  931.      ParseBlock(HoldID);                         { call again, pass proc name }
  932.      if TK.Code <> Semi then Error(XSemi);       { proc block must have semi  }
  933.      GetTK;                                      { eat it up                  }
  934.     end;
  935.    if TK.Code <> BeginSym then Error(XBegin);    { block begins "BEGIN"       }
  936.    Gen(Concat(': ',Caller));                     { start colon definition     }
  937.    GetTK;                                        { eat the "BEGIN"            }
  938.    while TK.Code <> EndSym do                    { loop for all statements    }
  939.     begin
  940.      ParseStatement;                             { call for each stmt         }
  941.      if not (TK.Code in [Semi,EndSym])           { separator or terminator... }
  942.       then Error(XSemEnd);                       { need after each one        }
  943.      if TK.Code = Semi then GetTK;               { if semi, eat & go back     }
  944.     end;                                         { TK must have been "END"    }
  945.    GetTK;                                        { eat the END                }
  946.    Gen(';'); Gen('|');                           { end Forth def, force CR    }
  947.    Blot;                                         { clean up symbol table      }
  948.   end;
  949.  
  950.  
  951.     {    ParseProgram sets the entire compiler in motion. It first handles
  952.          the program header, saving the program name (which will be the
  953.          last Forth word generated). The program is declared in the symbol
  954.          table as a procedure like any other, except that its scope field
  955.          has a value of zero, which no other procedure can have. ParseProgram
  956.          then calls ParseVariables and ParseBlock, which process the body
  957.          of the program. Finally there is a check for the final dot.    }
  958.  
  959.  begin   { ParseProgram }
  960.   if TK.Code <> PgmSym then Error(XPgm);        { must begin "PROGRAM"        }
  961.   GetTK;                                        { dispose of that token       }
  962.   if TK.Code <> Ident then Error(XIdent);       { program must have a name    }
  963.   HoldID := TK.Name;                            { save, pass to ParseBlock    }
  964.   Declare(TK.Name,Proc,Null);                   { install in table            }
  965.   GenHeader(TK.Name);                           { output the Forth prelude    }
  966.   GetTK;                                        { eat the Ident               }
  967.   if TK.Code <> Semi then Error(XSemi);         { header must end with semi   }
  968.   GetTK;                                        { toss out the semi           }
  969.   ParseVariables;                               { do the global declarations  }
  970.   ParseBlock(HoldID);                           { give Block the program name }
  971.   if TK.Code <> Dot then Error(XDot);           { not done until "." read     }
  972.   Gen(';S');                                    { tell Forth to stop          }
  973.  end;
  974.  
  975.  
  976. {*****************************************************************************}
  977. {*****************************************************************************}
  978. {*****************                                           *****************}
  979. {*****************               MAIN BLOCK                  *****************}
  980. {*****************                                           *****************}
  981. {*****************************************************************************}
  982. {*****************************************************************************}
  983.  
  984.     {    The main driver routine has little to do: initialize some global
  985.          variables, open the files and crank up the parser.     }
  986.  
  987. procedure Initialize;
  988.  begin
  989.   InitErrorList;            { fill up one static array... }
  990.   InitKeywords;             { and then another            }
  991.   InitSets;                 { define sets of tokens       }
  992.   InitOutBuf;               { set up a clean slate        }
  993.   FirstSym := nil;          { make pointer point nowhere  }
  994.   CurrentScope := 0;        { at start scope is global    }
  995.   LineCount := 1;           { start on first source line  }
  996.   SayHello;                 { paint the screen            }
  997.  end;
  998.  
  999. begin    { main block }
  1000.  Initialize;
  1001.  OpenFiles;
  1002.  Read(InFile,CH); CH := Upcase(CH);    { get first char for scanner }
  1003.  GetTK;                                { and first token for parser }
  1004.  ParseProgram;
  1005.  CloseFiles;
  1006.  WriteLn('Compilation complete.');
  1007. end.
  1008.