{ Very simple script interpreter. Not useful by itself, but a jumping off point for a more useful interpreter. programmer: Greg Savin (gsavin@cs.pdx.edu) As written it takes input from a file named 'test' and interprets the file. Language: A script is one or more statements separated by semicolons. A statement is either an assignment, a WRITELN statement, a FOR statement, or a compound statement. WRITELN can't handle any formatting information, just vars and constants. All variables are of type "real" and are implicitly declared upon their first use. Uninitialized variables have the value zero. Assignments, for statements, compound statements have the same syntax as their Pascal equivalents. An expression is either a simple constant or a variable name (there are no expression operators) Sample script: for i := 1 to 5 do begin a := i; writeln(a,i); end; } uses Objects; type PSymbol = ^TSymbol; TSymbol = Record Key : PString; Value : Real; Next : PSymbol; end; TSymTab = Object Head : PSymbol; constructor Init; destructor Done; function Lookup(const Key : String) : PSymbol; function Install(const Key : String) : PSymbol; end; var SymTab : TSymTab; constructor TSymtab.Init; begin Head := nil; end; destructor TSymtab.Done; var P : PSymbol; begin while Head <> nil do begin P := Head; Head := Head^.Next; if P^.Key <> nil then DisposeStr(P^.Key); Dispose(P); end; end; function TSymTab.Lookup(const Key : String) : PSymbol; var P : PSymbol; begin Lookup := nil; P := Head; while P <> nil do begin if P^.Key^ = Key then begin Lookup := P; exit; end; P := P^.Next; end; end; function TSymTab.Install(const Key : String) : PSymbol; var P : PSymbol; begin P := Lookup(Key); if P = nil then begin new(P); P^.Key := newstr(Key); P^.Value := 0.0; P^.Next := Head; Head := P; end; Install := P; end; type Token = ( tVAR, tCONST, tSEQ, tFOR, tASSIGN, tWRITELN, tTO, tDO, tBEGIN, tEND, tCOMMA, tSEMICOLON, tLPAREN, tRPAREN, tEOF ); PTree = ^TTree; TTree = record case Kind: Token of tVAR : ( Sym : PSymbol ); tCONST : ( Value : Real ); tSEQ,tASSIGN,tWRITELN : ( Left, Right : PTree ); tFOR : ( Expr, Low, High, Stmt : PTree); end; function MakeVar( Sym : PSymbol ) : Ptree; var P : Ptree; begin new(P); P^.Kind := tVAR; P^.Sym := Sym; MakeVar := P; end; function MakeConst(Value : Real) : Ptree; var P : Ptree; begin New(P); P^.Kind := tCONST; P^.Value := Value; MakeConst := P; end; function MakeBinaryNode( Kind : Token; Left, Right : Ptree) : Ptree; var P : Ptree; begin New(P); P^.Kind := Kind; P^.Left := Left; P^.Right := Right; MakeBinaryNode := P; end; function MakeFor ( Expr, Low, High, Stmt : Ptree) : Ptree; var P : Ptree; begin New(P); P^.Kind := tFOR; P^.Expr := Expr; P^.Low := Low; P^.High := High; P^.Stmt := Stmt; MakeFor := P; end; type KeyString = Record Str : String[7]; Tok : Token; end; const NumKeyWords = 6; KeyWords : Array[1..NumKeyWords] of KeyString = ( (Str:'FOR'; Tok:tFOR), (Str:'TO'; Tok:tTO), (Str:'WRITELN'; Tok:tWRITELN), (Str:'DO'; Tok:tDO), (Str:'BEGIN'; Tok:tBEGIN), (Str:'END'; Tok:tEND) ); var InFile : File of Char; BackChar : Char; const Line : Integer = 1; var Lookahead : Token; LexVal : record case Token of tVAR: ( sval : string); tCONST : ( rval : real ); end; const HaveBackChar : Boolean = False; EOF = char(-1); procedure error(const msg : string); begin write('Error (line ', Line, ') :'); writeln(msg); halt; end; function GetChar : Char; var ch : char; begin if HaveBackChar then begin HaveBackChar := False; GetChar := BackChar; exit; end; {$I-} Read(InFile, ch); {$I+} ch := upcase(ch); { make it case insensitive } if ioresult = 0 then GetChar := ch else GetChar := EOF; end; procedure PutBackChar(Ch : Char); begin BackChar := Ch; HaveBackChar := True; end; function IsAlpha(Ch : Char) : Boolean; begin IsAlpha := Ch in ['A'..'Z','a'..'z']; end; function IsDigit(Ch : Char) : Boolean; begin IsDigit := Ch in ['0'..'9']; end; function IsSpace(Ch : Char) : Boolean; begin IsSpace := Ch in [' ', #9, #10, #11, #12, #13]; end; function NextToken : Token; var ch : char; Code : integer; Rval : real; i : integer; begin ch := GetChar; if IsSpace(ch) then while IsSpace(ch) do begin if ch = #13 then Inc(Line); ch := GetChar; end; if ch = EOF then NextToken := tEOF else if ch = '(' then NextToken := tLPAREN else if ch = ')' then NextToken := tRPAREN else if ch = ',' then NextToken := tCOMMA else if ch = ';' then NextToken := tSEMICOLON else if IsAlpha(ch) then begin LexVal.sval := ''; while IsAlpha(ch) do begin LexVal.sval := LexVal.sval + ch; ch := GetChar; end; PutBackChar(ch); { see if a keyword or variable } NextToken := tVAR; for i := 1 to NumKeyWords do if KeyWords[i].str = LexVal.sval then NextToken := KeyWords[i].Tok; end else if IsDigit(ch) then begin LexVal.sval := ''; while IsDigit(ch) or (ch = '.') do begin LexVal.sval := LexVal.sval + ch; ch := GetChar; end; PutBackChar(ch); Val(Lexval.sval, rval, code); if code <> 0 then error('error in constant constant'); Lexval.rval := rval; NextToken := tCONST; end else if ch = ':' then begin ch := GetChar; if ch = '=' then NextToken := tASSIGN else error('syntax error'); end else error('syntax error'); end; procedure match(T : Token); begin if Lookahead = T then Lookahead := NextToken else error('syntax error'); end; function Statement : Ptree; forward; function WriteStatement : Ptree; forward; function ForStatement : Ptree; forward; function AssignStatement : Ptree; forward; function Expression : Ptree; begin if Lookahead = tCONST then begin Expression := MakeConst(LexVal.rval); match(tCONST) end else if Lookahead = tVAR then begin Expression := MakeVar( Symtab.Install(Lexval.sval) ); match(tVAR) end else error('error in expression'); end; function CompoundStatement : PTree; var P : Ptree; NewStmt : Ptree; begin P := nil; match(tBEGIN); if Lookahead <> tEND then begin P := Statement; while Lookahead <> tEND do begin match(tSEMICOLON); NewStmt := Statement; if NewStmt <> nil then P := MakeBinaryNode(tSEQ, P, NewStmt); end; end; match(tEND); CompoundStatement := P; end; function ForStatement : Ptree; var Exp, Low, High, Stmt : Ptree; P : Ptree; begin match(tFOR); Exp := expression; if Exp^.Kind <> tVAR then error('Variable expected after FOR'); match(tASSIGN); Low := expression; match(tTO); High := expression; match(tDO); Stmt := statement; ForStatement := makefor ( exp, low, high, Stmt); end; function Statement : PTree; begin case Lookahead of tFOR : Statement := ForStatement; tWRITELN : Statement := WriteStatement; tBEGIN : Statement := CompoundStatement; tVAR, tCONST : Statement := AssignStatement; tSEMICOLON, tEND : Statement := nil; else error('error in statement'); end; end; function WriteStatement : Ptree; var Writechain : Ptree; begin Writechain := nil; match(tWRITELN); if Lookahead = tLPAREN then begin match(tLPAREN); Writechain := expression; while Lookahead = tCOMMA do begin match(tCOMMA); Writechain := MakeBinaryNode(tSEQ, Writechain, expression); end; match(tRPAREN); end; WriteStatement := MakeBinaryNode( tWRITELN, Writechain, nil); end; function AssignStatement : Ptree; var Left, Right : Ptree; begin Left := expression; if Left^.Kind <> tVAR then error('Variable expected on left side of assignment'); match(tASSIGN); Right := expression; AssignStatement := MakeBinaryNode(tASSIGN, Left, Right); end; procedure DumpTree( P : Ptree; Depth : integer); var i : integer; begin for i := 1 to depth do write(' '); if P = nil then writeln('(nil)') else if P^.Kind = tCONST then writeln( 'CONST: ', P^.Value : 0 : 2) else if P^.Kind = tVAR then writeln( 'VAR: ', P^.Sym^.Key^ ) else if P^.Kind = tSEQ then begin writeln( 'SEQ' ); DumpTree(P^.Left, Depth+1); Dumptree(P^.Right, Depth + 1); end else if P^.Kind = tASSIGN then begin writeln('ASSIGN'); DumpTree(P^.Left, Depth+1); Dumptree(P^.Right, Depth + 1); end else if P^.Kind = tFOR then begin writeln('FOR'); DumpTree(P^.Expr, Depth+1); DumpTree(P^.Low, Depth+1); Dumptree(P^.High, Depth + 1); DumpTree(P^.Stmt, Depth+1); end else if P^.Kind = tWRITELN then begin writeln('WRITELN'); DumpTree(P^.Left, Depth+1); end; end; function Value(P : Ptree) : Real; begin if P^.Kind = tCONST then Value := P^.Value else if P^.Kind = tVAR then Value := P^.Sym^.Value else writeln('something is wrong in "value"'); end; procedure DoWrite( P : Ptree); begin if p = nil then exit; if P^.Kind = tSEQ then begin DoWrite(P^.Left); DoWrite(P^.Right); end else begin write( Value(P): 0 : 2 ); write(' '); end; end; procedure Interpret(P : Ptree); var i : integer; begin if P = nil then exit; case P^.Kind of tSEQ: begin Interpret(P^.Left); Interpret(P^.Right); end; tFOR: For i := Trunc(Value(P^.Low)) to Trunc(Value(P^.High)) do begin P^.Expr^.Sym^.Value := i; Interpret(P^.Stmt); end; tASSIGN: P^.Left^.Sym^.Value := Value(P^.Right); tWRITELN: begin DoWrite(P^.left); writeln; end; end; end; procedure FreeTree(P : Ptree); begin if P^.Kind in [tSEQ, tASSIGN] then begin FreeTree(P^.Left); FreeTree(P^.Right); end else if P^.Kind = tWRITELN then FreeTree(P^.Left) else if P^.Kind = tFOR then begin FreeTree(P^.Expr); FreeTree(P^.Low); FreeTree(P^.High); FreeTree(P^.Stmt); end; Dispose(P); end; var Script : Ptree; NewStmt : Ptree; begin Assign(Infile, 'test'); Reset(infile); Symtab.Init; Lookahead := NextToken; Script := statement; while Lookahead <> tEOF do begin NewStmt := Statement; if NewStmt <> nil then Script := makeBinaryNode(tSEQ, Script, NewStmt); if Lookahead = tSEMICOLON then match(tSEMICOLON); end; { To debug tree: DumpTree(Script,0); } Interpret(Script); FreeTree(Script); Symtab.Done; end.