(* Copyright (C) 1992, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* *) (* Last modified on Fri Dec 29 11:16:34 PST 1995 by heydon *) (* modified on Sat Feb 18 19:57:03 PST 1995 by gnelson *) (* modified on Fri Aug 7 21:54:03 PDT 1992 by myers *) MODULE JunoParse; IMPORT JunoValue; IMPORT JunoAST, JunoLex, JunoToken, JunoASTUtils; IMPORT Rd, Text; (* The procedures in this module are structured as follows. Not counting the public procedures Unit(), Command(), and Expression(), there is roughly a one-to-one correspondence between procedures in this module and non-terminals in the LL(1) Juno grammar described in the file "Juno.bnf". To make it easier to check that any given procedure correctly parses its specified non-terminal, each procedure includes the relevant productions from the grammar in its comment. Each parsing procedure may raise one of two errors: Rd.Failure and Error. Rd.Failure is raised when there is a problem encountered reading from their argument stream. Error is raised when either a lex- or parse-error occurs. In the event that Error is raised, we need to "return" as much of the partial AST constructed up to the point where the error occurred. An overriding concern of this implementation to guarantee that any token that has been successfully parsed is incorporated in the result AST, even in the event that Error is raised. In this way, the JunoUnparse procedures will be able to pretty-print all of the tokens that were read successfully. To facilitate error recovery, most procedures in this module return their result as a VAR (*OUT*) parameter. In some cases, the result is returned as a VAR (*INOUT*) (or VAR (*IO*)) parameter; in these cases, the parameter is presumed to contain a non-NIL object, some of whose fields may have already been filled in at the time of the call. Some procedures also take additional READONLY parameters. The tokens represented by such parameters have not been incorporated into the result AST at the time the procedure is called, so if some parse error occurs, it is the responsibility of the procedure to incorporate such READONLY parameters into the result AST before raising Error so as to conform to the rule described in the previous paragraph. For example, see the procedure PH2(). The grammar contains two versions of most of the Formula/Expression nonterminals. The "normal" nonterminals have names like "Formula", "Form1", "Expr", and "Expr1". The mirrors of these nonterminals are special because they apply only in the case where a QId (qualified identifier) has already been parsed and so the formula is known to start with a QId. The nonterminals in this case have names formed by appending the "normal" formula/expression nonterminal names with "QId". Rather than implementing separate procedures for these nonterminals, each of the formula/expression procedures takes an optional READONLY QId argument. If the argument is NIL, then parsing occurs as for the "normal" nonterminal. Otherwise, parsing occurs as for the "QId" version of the nonterminal. Both productions are listed in the comment for these procedures. Note also that "Form2" and "Expr3" have no "QId" counterparts, so they don't take an optional READONLY argument. There are two procedures provided for matching the current token: Match() and MatchKind(). The former should only be used when the type of the current token is known. The latter is used when the type of the current token is not known, but is expected to have a particular type. *) TYPE LookAhead = RECORD s: JunoLex.Stream; (* token stream *) t: JunoToken.T; (* most recently read token *) cnt: CARDINAL := 0; (* count of number of tokens parsed *) END; VAR NilRef: REFANY := NIL; (* for use as arg to MatchKind() *) End: JunoAST.T; (* = JunoAST.End *) (* ========================= TOP-LEVEL PROCEDURES ========================== *) REVEAL IterativeParse = BRANDED "JunoParse.IterativeParse" OBJECT la: LookAhead END; PROCEDURE StartIterativeParse(READONLY rd: Rd.T): IterativeParse RAISES { Rd.Failure, JunoLex.Error } = VAR lookAhead := LookAhead{s := JunoLex.New(rd), t := NIL}; ip := NEW(IterativeParse, la := lookAhead); BEGIN ip.la.t := ip.la.s.next(); INC(ip.la.cnt); RETURN ip; END StartIterativeParse; PROCEDURE FinishIterativeParse(ip: IterativeParse) = BEGIN EVAL JunoLex.Close(ip.la.s); END FinishIterativeParse; PROCEDURE GetIndex(ip: IterativeParse): INTEGER = BEGIN RETURN ip.la.s.lastPos END GetIndex; CONST BlockSet = SET OF JunoToken.Kind { JunoToken.Kind.Module, JunoToken.Kind.UI, JunoToken.Kind.Private, JunoToken.Kind.Import, JunoToken.Kind.Comment, JunoToken.Kind.Const, JunoToken.Kind.Var, JunoToken.Kind.Pred, JunoToken.Kind.Func, JunoToken.Kind.Proc }; PROCEDURE Block( ip: IterativeParse; VAR (*OUT*) ast: JunoAST.Block; VAR (*OUT*) tokenCnt: CARDINAL) RAISES {Error, JunoLex.Error, Rd.Failure} = BEGIN TRY ip.la.cnt := 0; IF ip.la.t.kind = JunoToken.Kind.EndMarker THEN ast := NIL; RETURN END; INC(ip.la.cnt); IF ip.la.t.kind IN BlockSet THEN Block2(ip.la, ast) ELSE RaiseError(ip.la) END FINALLY tokenCnt := ip.la.cnt END; END Block; PROCEDURE Command( READONLY rd: Rd.T; VAR (*OUT*) ast: JunoAST.Cmd; VAR (*OUT*) tokenCnt: CARDINAL) RAISES {Error, JunoLex.Error, Rd.Failure} = VAR la: LookAhead; BEGIN TRY la.s := JunoLex.New(rd); TRY la.t := la.s.next() FINALLY ast := NIL END; (* prime the stream *) INC(la.cnt); Cmd(la, ast); IF la.t.kind = JunoToken.Kind.EndMarker THEN DEC(la.cnt) ELSE RaiseError(la) END; EVAL JunoLex.Close(la.s) FINALLY tokenCnt := la.cnt END; END Command; PROCEDURE Expression( READONLY rd: Rd.T; VAR (*OUT*) ast: JunoAST.Expr; VAR (*OUT*) tokenCnt: CARDINAL) RAISES {Error, JunoLex.Error, Rd.Failure} = VAR la: LookAhead; BEGIN TRY la.s := JunoLex.New(rd); TRY la.t := la.s.next() FINALLY ast := NIL END; (* prime the stream *) INC(la.cnt); Expr(la, ast); IF la.t.kind = JunoToken.Kind.EndMarker THEN DEC(la.cnt) ELSE RaiseError(la) END; EVAL JunoLex.Close(la.s) FINALLY tokenCnt := la.cnt END; END Expression; PROCEDURE FoldHeader( READONLY rd: Rd.T; VAR (*OUT*) ast: JunoAST.PredHeader; VAR (*OUT*) tokenCnt: CARDINAL) RAISES {Error, JunoLex.Error, Rd.Failure} = VAR la: LookAhead; BEGIN TRY la.s := JunoLex.New(rd); TRY la.t := la.s.next() FINALLY ast := NIL END; (* prime the stream *) INC(la.cnt); FoldHeader2(la, ast); IF la.t.kind = JunoToken.Kind.EndMarker THEN DEC(la.cnt) ELSE RaiseError(la) END; EVAL JunoLex.Close(la.s) FINALLY tokenCnt := la.cnt END; END FoldHeader; PROCEDURE IdList( READONLY rd: Rd.T; VAR (*OUT*) ast: JunoAST.IdList; VAR (*OUT*) tokenCnt: CARDINAL) RAISES {Error, JunoLex.Error, Rd.Failure} = VAR la: LookAhead; BEGIN TRY la.s := JunoLex.New(rd); TRY la.t := la.s.next() FINALLY ast := NIL END; (* prime the stream *) IF la.t.kind = JunoToken.Kind.EndMarker THEN ast := JunoAST.EmptyIdList; ELSE INC(la.cnt); IdList0(la, ast); IF la.t.kind = JunoToken.Kind.EndMarker THEN DEC(la.cnt) ELSE RaiseError(la) END END; EVAL JunoLex.Close(la.s) FINALLY tokenCnt := la.cnt END END IdList; (* =============================== BLOCKS ================================== *) PROCEDURE Block2(VAR (*IO*) la: LookAhead; VAR (*OUT*) block: JunoAST.Block) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind IN BlockSet". | Block = MODULE Id ";" | | [ FROM Id ] IMPORT IDList ";" | | Comment | | UI UIDecl ";" | | [ PRIVATE ] Decl ";". *) BEGIN CASE la.t.kind OF | JunoToken.Kind.Module => Module(la, block) | JunoToken.Kind.Import => Import(la, block) | JunoToken.Kind.UI => UIDecl(la, block) | JunoToken.Kind.Comment => Comment(la, block) ELSE VAR private := la.t.kind = JunoToken.Kind.Private; BEGIN IF private THEN VAR raised := TRUE; BEGIN TRY Match(la); raised := FALSE FINALLY (* skip "PRIVATE" *) IF raised THEN (* use arbitrary JunoAST.Decl in case of error *) block := NEW(JunoAST.ConstDecl, private := private, bp := End) END END END END; Decl(la, block, private); (* skip decl *) MatchKind(la, JunoToken.Kind.Semi, NilRef) (* skip ";" *) END END END Block2; PROCEDURE Module(VAR (*IO*) la: LookAhead; VAR (*OUT*) block: JunoAST.Block) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Module". | Block = MODULE Id ";". *) VAR module := NEW(JunoAST.Module, bp := End); id: REFANY; BEGIN block := module; Match(la); (* skip "MODULE" *) TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip name *) module.name := NARROW(id, JunoAST.Id) END; MatchKind(la, JunoToken.Kind.Semi, NilRef); (* skip ";" *) END Module; PROCEDURE Import( VAR (*IO*) la: LookAhead; VAR (*OUT*) block: JunoAST.Block) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Import". | Block = IMPORT IDList ";". *) VAR import := NEW(JunoAST.Import, bp := End); BEGIN block := import; Match(la); (* skip "IMPORT" *) IdList0(la, import.idList); (* skip Id's *) MatchKind(la, JunoToken.Kind.Semi, NilRef); (* skip ";" *) END Import; PROCEDURE UIDecl(VAR (*IO*) la: LookAhead; VAR (*OUT*) block: JunoAST.Block) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.UI". | Block = UI UIDecl ";" | UIDecl = Id "(" [ ExprList ] ")". *) VAR ui := NEW(JunoAST.UIDecl, bp := End); id: REFANY; BEGIN block := ui; Match(la); (* skip "UI" *) TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip Id *) ui.name := NARROW(id, JunoAST.Id) END; MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IF la.t.kind # JunoToken.Kind.RPren THEN ExprList(la, ui.args) (* parse args *) ELSE ui.args := JunoAST.EmptyExprList (* make args empty *) END; MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) MatchKind(la, JunoToken.Kind.Semi, NilRef); (* skip ";" *) END UIDecl; PROCEDURE FoldHeader2( VAR (*IO*) la: LookAhead; VAR (*OUT*) res: JunoAST.PredHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Parse something like a "PredHeader", but the trailing parentheses are optional if there are no arguments. | Id [ "(" [ IDList ] ")" ]. If only "Id" is read, then the "ins" field of the result will be "NIL". If "Id()" is read, then the "ins" field of the result will be "JunoAST.EmptyIdList". *) VAR id: REFANY; BEGIN res := NEW(JunoAST.PredHeader, bp := End); TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* read Id *) res.name := NARROW(id, JunoAST.Id) END; IF la.t.kind = JunoToken.Kind.EndMarker THEN res.ins := NIL; RETURN END; MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IF la.t.kind # JunoToken.Kind.RPren THEN IdList0(la, res.ins) (* parse args *) ELSE res.ins := JunoAST.EmptyIdList (* make args empty *) END; MatchKind(la, JunoToken.Kind.RPren, NilRef) (* skip ")" *) END FoldHeader2; PROCEDURE Comment(VAR (*IO*) la: LookAhead; VAR (*OUT*) block: JunoAST.Block) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Requires "la.t.kind = JunoToken.Kind.Comment". *) VAR comment := NEW(JunoAST.Comment, bp := End); txt: REFANY; BEGIN block := comment; TRY MatchKind(la, JunoToken.Kind.Comment, txt) FINALLY comment.txt := NARROW(txt, TEXT); comment.private := (Text.GetChar(comment.txt, 0) = '/') END END Comment; PROCEDURE Decl( VAR (*IO*) la: LookAhead; VAR (*OUT*) decl: JunoAST.Block; private: BOOLEAN) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Decl = CONST ConstDecl | | VAR VarDecl | | PRED PredDecl | | FUNC FuncDecl | | PROC ProcDecl. *) BEGIN CASE la.t.kind OF JunoToken.Kind.Const => ConstDecl(la, decl, private) | JunoToken.Kind.Var => VarDecl(la, decl, private) | JunoToken.Kind.Pred => PredDecl(la, decl, private) | JunoToken.Kind.Func => FuncDecl(la, decl, private) | JunoToken.Kind.Proc => ProcDecl(la, decl, private) ELSE RaiseError(la) END END Decl; PROCEDURE ConstDecl( VAR (*IO*) la: LookAhead; VAR (*OUT*) decl: JunoAST.Block; priv: BOOLEAN) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Const". | Decl = CONST ConstDecl. | ConstDecl = ConstDeclItem { "," ConstDeclItem }. *) VAR constant := NEW(JunoAST.ConstDecl, private := priv, bp := End); BEGIN decl := constant; Match(la); (* skip "CONST" *) INC(constant.size); ConstDeclItem(la, constant.head); (* skip ConstDeclItem *) VAR curr := constant.head; BEGIN WHILE la.t.kind = JunoToken.Kind.Comma DO INC(constant.size); Match(la); (* skip "," *) ConstDeclItem(la, curr.next); (* skip ConstDeclItem *) curr := curr.next END END END ConstDecl; PROCEDURE ConstDeclItem( VAR (*IO*) la: LookAhead; VAR (*OUT*) item: JunoAST.ConstDeclItem) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | ConstDeclItem = Id "=" ConstExpr. *) VAR id: REFANY; BEGIN item := NEW(JunoAST.ConstDeclItem); TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip Id *) item.name := NARROW(id, JunoAST.Id) END; MatchKind(la, JunoToken.Kind.Equals, NilRef); (* skip "=" *) Expr(la, item.value); (* skip Expr *) END ConstDeclItem; PROCEDURE VarDecl( VAR (*IO*) la: LookAhead; VAR (*OUT*) decl: JunoAST.Block; priv: BOOLEAN) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Var". | Decl = VAR VarDecl. | VarDecl = VarDeclItem { "," VarDeclItem }. *) VAR var := NEW(JunoAST.VarDecl, private := priv, bp := End); BEGIN decl := var; Match(la); (* skip "VAR" *) INC(var.size); VarDeclItem(la, var.head); (* skip VarDeclItem *) VAR curr := var.head; BEGIN WHILE la.t.kind = JunoToken.Kind.Comma DO INC(var.size); Match(la); (* skip "," *) VarDeclItem(la, curr.next); (* skip VarDeclItem *) curr := curr.next END END END VarDecl; PROCEDURE VarDeclItem( VAR (*IO*) la: LookAhead; VAR (*OUT*) item: JunoAST.VarDeclItem) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | VarDeclItem = Id [ ":=" Expr ]. *) VAR id: REFANY; BEGIN item := NEW(JunoAST.VarDeclItem); TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip Id *) item.name := NARROW(id, JunoAST.Id) END; IF la.t.kind = JunoToken.Kind.Assign THEN MatchKind(la, JunoToken.Kind.Assign, NilRef); (* skip ":=" *) Expr(la, item.value) (* skip Expr *) ELSE item.value := JunoAST.NilExpr END END VarDeclItem; PROCEDURE PredDecl( VAR (*IO*) la: LookAhead; VAR (*OUT*) decl: JunoAST.Block; priv: BOOLEAN) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Pred". | Decl = PRED PredDecl | PredDecl = PredHead IS Formula END. *) VAR pred := NEW(JunoAST.PredDecl, private := priv, bp := End); BEGIN decl := pred; Match(la); (* skip "PRED" *) PredHead(la, pred.header); (* skip header *) Match(la); (* skip "IS" *) Formula(la, pred.body); (* skip body *) MatchKind(la, JunoToken.Kind.End, NilRef) (* skip "END" *) END PredDecl; PROCEDURE PredHead( VAR (*IO*) la: LookAhead; VAR (*OUT*) header: JunoAST.PredHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | PredHead = Id "(" [ IDList ] ")". *) VAR id: REFANY; BEGIN header := NEW(JunoAST.PredHeader, bp := End); TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip pred name *) header.name := NARROW(id, JunoAST.Id) END; MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IF la.t.kind # JunoToken.Kind.RPren THEN IdList0(la, header.ins) (* skip in parameters *) ELSE header.ins := JunoAST.EmptyIdList (* make IN params empty *) END; MatchKind(la, JunoToken.Kind.RPren, NilRef) (* skip ")" *) END PredHead; PROCEDURE FuncDecl( VAR (*IO*) la: LookAhead; VAR (*OUT*) decl: JunoAST.Block; priv: BOOLEAN) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Func". | Decl = FUNC FuncDecl. | FuncDecl = FuncHead IS Constraint END. *) VAR func := NEW(JunoAST.FuncDecl, private := priv, bp := End); BEGIN decl := func; Match(la); (* skip "FUNC" *) FuncHead(la, func.header); (* skip header *) Match(la); (* skip "IS" *) Formula(la, func.body); (* skip body *) MatchKind(la, JunoToken.Kind.End, NilRef) (* skip "END" *) END FuncDecl; PROCEDURE FuncHead( VAR (*IO*) la: LookAhead; VAR (*OUT*) header: JunoAST.FuncHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | FuncHead = Id "(" [ IDList ] ")" "=" Id. *) VAR id: REFANY; BEGIN header := NEW(JunoAST.FuncHeader, bp := End); TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip result name *) header.result := NARROW(id, JunoAST.Id) END; MatchKind(la, JunoToken.Kind.Equals, NilRef); (* skip "=" *) TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip func name *) header.name := NARROW(id, JunoAST.Id) END; MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IF la.t.kind # JunoToken.Kind.RPren THEN IdList0(la, header.ins) (* skip in parameters *) ELSE header.ins := JunoAST.EmptyIdList (* make IN params empty *) END; MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) END FuncHead; PROCEDURE ProcDecl( VAR (*IO*) la: LookAhead; VAR (*OUT*) decl: JunoAST.Block; priv: BOOLEAN) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Proc". | Decl = PROC ProcDecl. | ProcDecl = ProcHead IS Cmd END. *) VAR proc := NEW(JunoAST.ProcDecl, private := priv, bp := End); BEGIN decl := proc; Match(la); (* skip "PROC" *) ProcHead(la, proc.header); (* skip header *) Match(la); (* skip "IS" *) Cmd(la, proc.body); (* skip body *) MatchKind(la, JunoToken.Kind.End, NilRef) (* skip "END" *) END ProcDecl; PROCEDURE ProcHead( VAR (*IO*) la: LookAhead; VAR (*OUT*) header: JunoAST.ProcHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | ProcHead = Id PH2 | PH4. | PH4 = "(" IDList ")" PH5. *) BEGIN header := NEW(JunoAST.ProcHeader, bp := End); CASE la.t.kind OF | JunoToken.Kind.Id => VAR newId: JunoAST.Id; id: REFANY; raised := TRUE; BEGIN TRY MatchKind(la, JunoToken.Kind.Id, id); raised := FALSE FINALLY newId := NARROW(id, JunoAST.Id); IF raised THEN header.outs := NewIdList(newId) END END; PH2(la, newId, header) END | JunoToken.Kind.LPren => header.outs := JunoAST.EmptyIdList; (* make OUT params an empty list *) PH4(la, header) (* skip inouts and prochead *) ELSE RaiseError(la) END END ProcHead; PROCEDURE PH2( VAR (*IO*) la: LookAhead; READONLY id: JunoAST.Id; VAR (*IO*) header: JunoAST.ProcHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "header # NIL", so if we don't find a token we expect, we can simply raise "Error" after saving the value "id" in "header". "Id" is the most recently parsed token. It has a different meaning depending on the PH2 alternative taken. In the case of the first alternative, "Id" is the first of a sequence of OUT parameters. In the case of the second alternative, "Id" is a single INOUT parameter. In the case of the third alternative, "Id" is the procedure name. | PH2 = { "," Id } ":=" PH3 | PH5 | PH6. | PH5 = ":" Id PH6 | PH6 = "(" [ IDList ] ")". *) BEGIN CASE la.t.kind OF | JunoToken.Kind.Comma, JunoToken.Kind.Assign => IdList0(la, header.outs, id); (* skip out params *) MatchKind(la, JunoToken.Kind.Assign, NilRef); (* skip ":=" *) PH3(la, header); | JunoToken.Kind.Colon => header.outs := JunoAST.EmptyIdList; (* make OUT list empty *) header.inouts := NewIdList(id); (* initialize INOUTs *) PH5(la, id, header) (* parse proc name *) | JunoToken.Kind.LPren => header.outs := JunoAST.EmptyIdList; (* make OUT list empty *) header.inouts := JunoAST.EmptyIdList; (* make INOUT list empty *) header.name := id; PH6(la, id, header) ELSE header.outs := NewIdList(id); RaiseError(la) END END PH2; PROCEDURE PH3( VAR (*IO*) la: LookAhead; VAR (*IO*) header: JunoAST.ProcHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "header # NIL", so if we don't find a token we expect, we can simply raise "Error". Also assumes "header.outs # NIL", i.e., the OUT parameters slot has of "header" has already been filled in. | PH3 = Id (PH5 | PH6) | PH4. | PH4 = "(" IDList ")" PH5. | PH5 = ":" Id PH6. | PH6 = "(" [ IDList ] ")". *) BEGIN CASE la.t.kind OF | JunoToken.Kind.Id => VAR newId: JunoAST.Id; id: REFANY; BEGIN TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY header.name := NARROW(id, JunoAST.Id) END; newId := NARROW(id, JunoAST.Id); CASE la.t.kind OF | JunoToken.Kind.Colon => PH5(la, newId, header) (* skip proc name and args *) | JunoToken.Kind.LPren => header.inouts := JunoAST.EmptyIdList; (* make INOUTs empty *) PH6(la, newId, header) (* skip proc name and args *) ELSE RaiseError(la) END END | JunoToken.Kind.LPren => PH4(la, header) ELSE RaiseError(la) END END PH3; PROCEDURE PH4( VAR (*IO*) la: LookAhead; VAR (*IO*) header: JunoAST.ProcHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "header # NIL", so if we don't find a token we expect, we can simply raise "Error". This happens implicitly in the calls to "MatchKind()". | PH4 = "(" IDList ")" PH5. *) BEGIN header.inout_prens := TRUE; MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IdList0(la, header.inouts); (* skip inout params *) MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) PH5(la, NIL, header); END PH4; PROCEDURE PH5( VAR (*IO*) la: LookAhead; READONLY inout: JunoAST.Id; VAR (*IO*) header: JunoAST.ProcHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "header # NIL". If "inout # NIL", it is installed as the (1-item) list of in-out parameters in "header.inouts". Otherwise, it is assumed that "header.inouts # NIL", i.e., it has already been filled in. | PH5 = ":" Id PH6. *) VAR id: REFANY; BEGIN IF inout # NIL THEN header.inouts := NewIdList(inout) END; MatchKind(la, JunoToken.Kind.Colon, NilRef); (* skip ":" *) TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip proc name *) header.name := NARROW(id, JunoAST.Id) END; PH6(la, NIL, header) END PH5; PROCEDURE PH6( VAR (*IO*) la: LookAhead; READONLY name: JunoAST.Id; VAR (*IO*) header: JunoAST.ProcHeader) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "header # NIL". If "name # NIL", it is installed as the procedure name in "header.name". Otherwise, it is assumed that the procedure name has already been filled in. | PH6 = "(" [ IDList ] ")". *) BEGIN IF name # NIL THEN header.name := name END; MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IF la.t.kind # JunoToken.Kind.RPren THEN IdList0(la, header.ins) (* skip in parameters *) ELSE header.ins := JunoAST.EmptyIdList (* make IN params empty *) END; MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) END PH6; (* =============================== COMMANDS ================================ *) PROCEDURE Cmd(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Cmd = Cmd2 [ "|" Cmd ]. *) BEGIN Cmd2(la, ast); IF la.t.kind = JunoToken.Kind.Else THEN VAR elseCmd := NEW(JunoAST.Else, c1 := ast, bp := End); BEGIN ast := elseCmd; Match(la); (* skip "|" *) Cmd(la, elseCmd.c2) END END END Cmd; PROCEDURE Cmd2(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Cmd2 = Cmd3 | QId QIdCmdTail. *) BEGIN IF la.t.kind = JunoToken.Kind.Id THEN VAR qid: JunoAST.QId := NIL; raised := TRUE; BEGIN TRY QID(la, qid); raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.Assign, bp := End, vars := JunoASTUtils.NewQIdList(qid, bp := End)); END END; QIdCmdTail(la, qid, ast) END ELSE Cmd3(la, ast) END END Cmd2; PROCEDURE Cmd3(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* The possible tokens with which the non-terminal "FormulaX" may begin are given by the constant "FormulaFirstSet" below. | Cmd3 = Cmd4 | FormulaX FormTail. *) CONST FormulaFirstSet = SET OF JunoToken.Kind { JunoToken.Kind.LitReal, JunoToken.Kind.LitText, JunoToken.Kind.LPren, JunoToken.Kind.LBracket, JunoToken.Kind.Minus, JunoToken.Kind.Nil, JunoToken.Kind.True, JunoToken.Kind.False, JunoToken.Kind.Not, JunoToken.Kind.Real..JunoToken.Kind.Min }; BEGIN IF la.t.kind IN FormulaFirstSet THEN (* In this case, we must be able to parse a Formula *) VAR f: JunoAST.Expr; raised := TRUE; BEGIN TRY Formula(la, f); raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.Guard, grd := f, bp := End) END END; FormTail(la, f, ast); END ELSE Cmd4(la, ast) END END Cmd3; PROCEDURE Cmd4(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Cmd4 = Cmd6 Cmd5. *) BEGIN Cmd6(la, ast); Cmd5(la, ast, ast) END Cmd4; PROCEDURE Cmd5( VAR (*IO*) la: LookAhead; VALUE cmd: JunoAST.Cmd; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* "Cmd" is the previously parsed command. If the current token is ";", then "cmd" is concatenated with the next command in the stream to form a command sequence. Otherwise, "ast" is set to "cmd". | Cmd5 = [ ";" Cmd2 ]. *) BEGIN IF la.t.kind = JunoToken.Kind.Semi THEN VAR seq := NEW(JunoAST.Seq, c1 := cmd, bp := End); BEGIN ast := seq; Match(la); (* skip ";" *) Cmd2(la, seq.c2) END ELSE ast := cmd END END Cmd5; PROCEDURE Cmd6(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Cmd6 = SKIP | ABORT | | VAR NearVarList IN Cmd END | | DO Cmd OD | IF Cmd FI | | SAVE Id IN Cmd END | "{" Cmd "}". *) BEGIN CASE la.t.kind OF | JunoToken.Kind.Skip => ast := JunoAST.SkipVal; Match(la) (* skip "SKIP" *) | JunoToken.Kind.Abort => ast := JunoAST.AbortVal; Match(la) (* skip "ABORT" *) | JunoToken.Kind.Var => VAR proj := NEW(JunoAST.Proj, bp := End); BEGIN ast := proj; Match(la); (* skip "VAR" *) NearVarList(la, proj.vars); (* skip variables *) MatchKind(la, JunoToken.Kind.In, NilRef); (* skip "IN" *) Cmd(la, proj.body); (* skip body *) MatchKind(la, JunoToken.Kind.End, NilRef) (* skip "END" *) END | JunoToken.Kind.Do => VAR doCmd := NEW(JunoAST.Do, bp := End); BEGIN ast := doCmd; Match(la); (* skip "DO" *) Cmd(la, doCmd.body); (* skip body *) MatchKind(la, JunoToken.Kind.Od, NilRef) (* skip "OD" *) END | JunoToken.Kind.If => VAR ifCmd := NEW(JunoAST.If, bp := End); BEGIN ast := ifCmd; Match(la); (* skip "IF" *) Cmd(la, ifCmd.body); (* skip body *) MatchKind(la, JunoToken.Kind.Fi, NilRef) (* skip "FI" *) END | JunoToken.Kind.Save => VAR save := NEW(JunoAST.Save, bp := End); id: REFANY; BEGIN ast := save; Match(la); (* skip "SAVE" *) TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip Id *) save.nm := NEW(JunoAST.QId, bp := End, id0 := JunoAST.NilId, id1 := id) END; MatchKind(la, JunoToken.Kind.In, NilRef); (* skip "IN" *) Cmd(la, save.body); (* skip body *) MatchKind(la, JunoToken.Kind.End, NilRef) (* skip "END" *) END | JunoToken.Kind.LBrace => VAR grp := NEW(JunoAST.GroupedCmd, bp := End); BEGIN ast := grp; Match(la); (* skip "{" *) Cmd(la, grp.body); (* skip body *) MatchKind(la, JunoToken.Kind.RBrace, NilRef) (* skip "}" *) END ELSE ast := NIL; RaiseError(la) END END Cmd6; PROCEDURE QIdCmdTail( VAR (*IO*) la: LookAhead; READONLY qid: JunoAST.QId; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* "QId" is the most recently parsed qualified identifier. | QIdCmdTail = { "," QId } QIdCT2 | | FormulaQId FormTail. | QIdCT2 = ":=" ExprList Cmd5. *) BEGIN IF la.t.kind=JunoToken.Kind.Comma OR la.t.kind=JunoToken.Kind.Assign THEN VAR qids: JunoAST.QIdList; raised := TRUE; BEGIN TRY QIdList(la, qid, qids); raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.Assign, bp := End, vars := qids) END END; QIdCT2(la, qids, ast) END ELSE VAR f: JunoAST.Expr; raised := TRUE; BEGIN TRY Formula(la, f, qid); raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.Guard, grd := f, bp := End) END END; FormTail(la, f, ast); END END END QIdCmdTail; PROCEDURE QIdCT2( VAR (*IO*) la: LookAhead; READONLY qids: JunoAST.QIdList; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* "Qids" is the most recently parsed list of qualified identifiers. Raises Error if "la.t.kind # JunoToken.Kind.Assign". | IDCT9 = ":=" ExprList Cmd5. *) VAR assign := NEW(JunoAST.Assign, vars := qids, bp := End); BEGIN ast := assign; MatchKind(la, JunoToken.Kind.Assign, NilRef); (* skip ":=" *) ExprList(la, assign.exprs); (* skip terms *) (* * Convert assignment to procedure call if we are certain it must be a * procedure call, namely, if there is more than one variable on the left * and exactly one call on the right. In the absence of semantic * information, we can't be guaranteed it's a procedure call otherwise, so * be conservative and treat it as an assignment. Note that an expression * of the form: "var := Proc(args)" can be treated *either* as an * assignment or as a procedure call. *) IF assign.vars.size > 1 AND assign.exprs.size = 1 THEN TYPECASE assign.exprs.head.expr OF JunoAST.Call(c) => VAR proc := CallToProcCall(c); BEGIN proc.outs := assign.vars; ast := proc END ELSE (* SKIP *) END END; Cmd5(la, ast, ast); END QIdCT2; PROCEDURE FormTail( VAR (*IO*) la: LookAhead; READONLY f: JunoAST.Expr; VAR (*OUT*) ast: JunoAST.Cmd) RAISES {Error, JunoLex.Error, Rd.Failure} = (* "F" is the most recently parsed formula. If it is followed by "->", then "ast" is set to a guarded command with guard "f". Otherwise, "f" must be a call expression (if not, Error is raised), and "ast" is set to the procedure call command equivalent to "f". | FormTail = Cmd5 (* proc call *) | | "->" Cmd2. (* guard *) *) BEGIN IF la.t.kind = JunoToken.Kind.Guard THEN VAR grd := NEW(JunoAST.Guard, grd := f, bp := End); BEGIN ast := grd; Match(la); (* skip "->" *) Cmd2(la, grd.body) (* skip guard body *) END ELSE TYPECASE f OF | JunoAST.Call(c) => Cmd5(la, CallToProcCall(c), ast); ELSE ast := NEW(JunoAST.Guard, grd := f, bp := End); (* If the error message produced by the "expected '->' token" is too specific, we can change the following to simply RAISE Error with argument ParseError(la). *) MatchKind(la, JunoToken.Kind.Guard, NilRef); (* signal error *) END END END FormTail; PROCEDURE CallToProcCall(call: JunoAST.Call): JunoAST.ProcCall = (* Convert a call expression into a procedure call command. *) BEGIN RETURN NEW(JunoAST.ProcCall, outs := JunoAST.EmptyQIdList, inouts := call.inouts, inout_parens := call.inout_parens, name := call.name, ins := call.ins, bp := End) END CallToProcCall; (* =============================== FORMULAS ================================ *) PROCEDURE Formula( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr; READONLY qid: JunoAST.QId := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Formula = Form1 [ OR Formula ]. | FormulaQId = Form1QId [ OR Formula ]. *) BEGIN Form1(la, ast, qid); IF la.t.kind = JunoToken.Kind.Or THEN VAR or := NEW(JunoAST.Or, f1 := ast, bp := End); BEGIN ast := or; Match(la); (* skip "OR" *) Formula(la, or.f2) END END END Formula; PROCEDURE Form1( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr; READONLY qid: JunoAST.QId := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Form1 = Form2 [ AND Form1 ]. | Form1QId = Form3QId [ AND Form1 ]. *) BEGIN IF qid = NIL THEN Form2(la, ast) ELSE Form3(la, ast, qid) END; IF la.t.kind = JunoToken.Kind.And THEN VAR and := NEW(JunoAST.And, f1 := ast, bp := End); BEGIN ast := and; Match(la); (* skip "AND" *) Form1(la, and.f2) END END END Form1; PROCEDURE Form2(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Form2 = Form3 | NOT Form2. *) BEGIN IF la.t.kind = JunoToken.Kind.Not THEN VAR not := NEW(JunoAST.Not, bp := End); BEGIN ast := not; Match(la); (* skip "NOT" *) Form2(la, not.f) END ELSE Form3(la, ast) END END Form2; PROCEDURE Form3( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr; READONLY qid: JunoAST.QId := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* This procedure actually binds "ast" to an object that is a proper subtype of JunoAST.Relation. | Form3 = Expr [ RelationOp Expr ]. | Form3QId = ExprQId [ RelationOp Expr ]. | RelationOp = "=" | "#" | "<" | ">" | "<=" | ">=" | CONG | PARA | HOR | VER. *) VAR rel: JunoAST.Relation; BEGIN Expr(la, ast, qid); CASE la.t.kind OF | JunoToken.Kind.Equals, JunoToken.Kind.Near => rel := NEW(JunoAST.Equals, bp := End, e1 := ast, near := (la.t.kind = JunoToken.Kind.Near)); ast := rel; Match(la); (* skip "=" or "~" *) Expr(la, rel.e2) | JunoToken.Kind.Differs => rel := NEW(JunoAST.Differs, e1 := ast, bp := End); ast := rel; Match(la); (* skip "#" *) Expr(la, rel.e2) | JunoToken.Kind.Less => rel := NEW(JunoAST.Less, e1 := ast, bp := End); ast := rel; Match(la); (* skip "<" *) Expr(la, rel.e2) | JunoToken.Kind.Greater => rel := NEW(JunoAST.Greater, e1 := ast, bp := End); ast := rel; Match(la); (* skip ">" *) Expr(la, rel.e2) | JunoToken.Kind.AtMost => rel := NEW(JunoAST.AtMost, e1 := ast, bp := End); ast := rel; Match(la); (* skip "<=" *) Expr(la, rel.e2) | JunoToken.Kind.AtLeast => rel := NEW(JunoAST.AtLeast, e1 := ast, bp := End); ast := rel; Match(la); (* skip ">=" *) Expr(la, rel.e2) | JunoToken.Kind.Cong => rel := NEW(JunoAST.Cong, e1 := ast, bp := End); ast := rel; Match(la); (* skip "CONG" *) Expr(la, rel.e2) | JunoToken.Kind.Para => rel := NEW(JunoAST.Para, e1 := ast, bp := End); ast := rel; Match(la); (* skip "PARA" *) Expr(la, rel.e2) | JunoToken.Kind.Hor => rel := NEW(JunoAST.Hor, e1 := ast, bp := End); ast := rel; Match(la); (* skip "HOR" *) Expr(la, rel.e2) | JunoToken.Kind.Ver => rel := NEW(JunoAST.Ver, e1 := ast, bp := End); ast := rel; Match(la); (* skip "VER" *) Expr(la, rel.e2) ELSE (* SKIP *) END END Form3; (* ============================== EXPRESSIONS ============================== *) PROCEDURE Expr( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr; READONLY qid: JunoAST.QId := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Expr = Expr1 [ REL Expr1 ]. | ExprQId = Expr1QId [ REL Expr1 ]. *) BEGIN Expr1(la, ast, qid); IF la.t.kind = JunoToken.Kind.Rel THEN VAR rel := NEW(JunoAST.Rel, e1 := ast, bp := End); BEGIN ast := rel; Match(la); (* skip "REL" *) Expr1(la, rel.e2); END END END Expr; PROCEDURE Expr1( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr; READONLY qid: JunoAST.QId := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Expr1 = Expr2 [ Expr1Tail ]. | Expr1QId = Expr2QId [ Expr1Tail ]. *) BEGIN Expr2(la, ast, qid); Expr1Tail(la, ast) END Expr1; PROCEDURE Expr1Tail( VAR (*IO*) la: LookAhead; VAR (*INOUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* On entry, "ast" is the expression parsed so far. If the next token is NOT an "AddOp", then this procedure consumes no tokens and leaves "ast" unchanged. | Expr1Tail = AddOp Expr2 [ Expr1Tail ]. | AddOp = "+" | "-" | "&". *) VAR add: JunoAST.BuiltInAddFunc; BEGIN CASE la.t.kind OF | JunoToken.Kind.Plus => add := NEW(JunoAST.Plus, e1 := ast, bp :=End); | JunoToken.Kind.Minus => add := NEW(JunoAST.Minus, e1 := ast, bp :=End); | JunoToken.Kind.Concat => add := NEW(JunoAST.Concat, e1 := ast, bp :=End); ELSE RETURN END; ast := add; Match(la); (* skip "+", "-", or "&" *) Expr2(la, add.e2); Expr1Tail(la, ast) END Expr1Tail; PROCEDURE Expr2( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr; READONLY qid: JunoAST.QId := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Expr2 = Expr3 [ Expr2Tail ]. | Expr2QId = Expr4QId [ Expr2Tail ]. *) BEGIN IF qid = NIL THEN Expr3(la, ast) ELSE Expr4(la, ast, qid) END; Expr2Tail(la, ast) END Expr2; PROCEDURE Expr2Tail( VAR (*IO*) la: LookAhead; VAR (*INOUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* On entry, "ast" is the expression parsed so far. If the next token is NOT a "MulOp", then this procedure consumes no tokens and leaves "ast" unchanged. | Expr2Tail = MulOp Expr3 [ Expr2Tail ]. | MulOp = "*" | "/" | DIV | MOD. *) VAR mul: JunoAST.BuiltInMulFunc; BEGIN CASE la.t.kind OF | JunoToken.Kind.Times => mul := NEW(JunoAST.Times, e1 := ast, bp :=End); | JunoToken.Kind.Divide => mul := NEW(JunoAST.Divide, e1 := ast, bp :=End); | JunoToken.Kind.Div => mul := NEW(JunoAST.Div, e1 := ast, bp :=End); | JunoToken.Kind.Mod => mul := NEW(JunoAST.Mod, e1 := ast, bp :=End); ELSE RETURN END; ast := mul; Match(la); (* skip "*", "/", "DIV", or "MOD" *) Expr3(la, mul.e2); Expr2Tail(la, ast) END Expr2Tail; PROCEDURE Expr3(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Expr3 = Expr4 | "-" Expr3. *) BEGIN IF la.t.kind = JunoToken.Kind.Minus THEN VAR minus := NEW(JunoAST.UMinus, bp := End); BEGIN ast := minus; Match(la); (* skip "-" *) Expr3(la, minus.e) END ELSE Expr4(la, ast) END END Expr3; PROCEDURE Expr4( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr; READONLY qid: JunoAST.QId := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | Expr4 = NIL | TRUE | FALSE | Literal | BuiltIn | | QID [ QIDExprTail ] | | "(" LPExprTail | | "[" ExprList "]". | Expr4QId = [ QIDExprTail ]. | QIDExprTail = [ ":" QID ] LPET7. | LPET7 = "(" [ ExprList ] ")". *) CONST FirstQIDExprTail = SET OF JunoToken.Kind{ JunoToken.Kind.Colon, JunoToken.Kind.LPren}; BEGIN IF qid = NIL THEN CASE la.t.kind OF | JunoToken.Kind.Nil => ast := JunoAST.NilVal; Match(la) | JunoToken.Kind.True => ast := JunoAST.TrueVal; Match(la) | JunoToken.Kind.False => ast := JunoAST.FalseVal; Match(la) | JunoToken.Kind.LitReal => VAR num := NEW(JunoAST.Number, bp := End); BEGIN ast := num; MatchReal(la, JunoToken.Kind.LitReal, num.val) (* skip real *) END | JunoToken.Kind.LitText => VAR txt := NEW(JunoAST.Text, bp := End); t: REFANY; BEGIN ast := txt; TRY MatchKind(la, JunoToken.Kind.LitText, t) FINALLY txt.val := NARROW(t, TEXT) (* skip text *) END END | FIRST(JunoToken.ResvdId)..LAST(JunoToken.ResvdId) => BuiltIn(la, ast) | JunoToken.Kind.Id => (* Id of QID *) VAR qid := NEW(JunoAST.QId, bp := End); id: REFANY; BEGIN TRY TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip ID *) qid.id0 := NARROW(id, JunoAST.Id) END; QID(la, qid); (* parse QID *) FINALLY ast := qid END; IF la.t.kind IN FirstQIDExprTail THEN QIDExprTail(la, qid, ast) END END | JunoToken.Kind.LPren => VAR raised := TRUE; BEGIN TRY Match(la); raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.GroupedExpr, bp := End) END END END; LPExprTail(la, ast) (* skip tail *) | JunoToken.Kind.LBracket => VAR lst := NEW(JunoAST.List, bp := End); BEGIN ast := lst; Match(la); (* skip "[" *) ExprList(la, lst.elts); (* skip list *) MatchKind(la, JunoToken.Kind.RBracket, NilRef); (* skip "]" *) END ELSE ast := NIL; RaiseError(la) END ELSE IF la.t.kind IN FirstQIDExprTail THEN QIDExprTail(la, qid, ast) ELSE ast := qid END END END Expr4; PROCEDURE BuiltIn( VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind" is in "JunoToken.ResvdId". *) VAR up: JunoAST.BIUPred; uf: JunoAST.BIUFunc; bf: JunoAST.BIBFunc; BEGIN CASE la.t.kind OF <* NOWARN *> JunoToken.Kind.Real => up := NEW(JunoAST.IsReal, bp := End); | JunoToken.Kind.Text => up := NEW(JunoAST.IsText, bp := End); | JunoToken.Kind.Pair => up := NEW(JunoAST.IsPair, bp := End); | JunoToken.Kind.Int => up := NEW(JunoAST.IsInt, bp := End); | JunoToken.Kind.Floor => uf := NEW(JunoAST.Floor, bp := End); | JunoToken.Kind.Ceiling => uf := NEW(JunoAST.Ceiling, bp := End); | JunoToken.Kind.Round => uf := NEW(JunoAST.Round, bp := End); | JunoToken.Kind.Abs => uf := NEW(JunoAST.Abs, bp := End); | JunoToken.Kind.Sin => uf := NEW(JunoAST.Sin, bp := End); | JunoToken.Kind.Cos => uf := NEW(JunoAST.Cos, bp := End); | JunoToken.Kind.Ln => uf := NEW(JunoAST.Ln, bp := End); | JunoToken.Kind.Exp => uf := NEW(JunoAST.Exp, bp := End); | JunoToken.Kind.Car => uf := NEW(JunoAST.Car, bp := End); | JunoToken.Kind.Cdr => uf := NEW(JunoAST.Cdr, bp := End); | JunoToken.Kind.Max => bf := NEW(JunoAST.Max, bp := End); | JunoToken.Kind.Min => bf := NEW(JunoAST.Min, bp := End); | JunoToken.Kind.Atan => bf := NEW(JunoAST.Atan, bp := End); END; CASE la.t.kind OF <* NOWARN *> JunoToken.Kind.Real..JunoToken.Kind.Int => ast := up; Match(la); (* skip predicate name *) MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) Expr(la, up.e); (* skip argument *) MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) | JunoToken.Kind.Floor..JunoToken.Kind.Cdr => ast := uf; Match(la); (* skip function name *) MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) Expr(la, uf.e); (* skip argument *) MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) | JunoToken.Kind.Max..JunoToken.Kind.Atan => ast := bf; Match(la); (* skip function name *) MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) Expr(la, bf.e1); (* skip 1st argument *) MatchKind(la, JunoToken.Kind.Comma, NilRef); (* skip "," *) Expr(la, bf.e2); (* skip 2nd argument *) MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) END END BuiltIn; PROCEDURE QIDExprTail( VAR (*IO*) la: LookAhead; READONLY qid: JunoAST.QId; VAR (*OUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "la.t.kind = JunoToken.Kind.Colon or "la.t.kind = JunoToken.Kind.LPren". "Qid" is the most recently parsed qualified identifier. Its meaning depends on the next token. If the next token is ":", then "qid" is the name of an INOUT parameter to a procedure call. Otherwise, it is the name of the procedure, predicate, or function being called. In either case, "ast" is actually bound to an object of type JunoAST.Call. | QIDExprTail = [ ":" QID ] LPET7. | LPET7 = "(" [ ExprList ] ")". *) VAR call := NEW(JunoAST.Call, bp := End); BEGIN ast := call; IF la.t.kind = JunoToken.Kind.Colon THEN call.inouts := JunoASTUtils.NewQIdList(qid, bp := End); Match(la); (* skip ":" *) QID(la, call.name) (* read QID call name *) ELSE call.inouts := JunoAST.EmptyExprList; (* make INOUT params empty *) call.name := qid; END; MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IF la.t.kind # JunoToken.Kind.RPren THEN ExprList(la, call.ins) (* skip expr list *) ELSE call.ins := JunoAST.EmptyExprList (* make IN params empty *) END; MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) END QIDExprTail; PROCEDURE LPExprTail(VAR (*IO*) la: LookAhead; VAR (*OUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | LPExprTail = LPET2 | LPET3. | LPET2 = E NearVarList "::" Constraint ")". (* existential quant *) | LPET3 = Formula LPET4. (* paren formula, pair, inouts *) *) BEGIN IF la.t.kind = JunoToken.Kind.Exists THEN VAR ex := NEW(JunoAST.Exists, bp := End); BEGIN ast := ex; Match(la); (* skip "E" *) NearVarList(la, ex.vars); (* skip var list *) MatchKind(la, JunoToken.Kind.SuchThat, NilRef); (* skip "::" *) Formula(la, ex.f); (* skip constraint *) MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip final ")" *) END ELSE VAR first: JunoAST.Expr; raised := TRUE; BEGIN TRY Formula(la, first); raised := FALSE FINALLY (* skip first expr *) IF raised THEN ast := NEW(JunoAST.GroupedExpr, expr := first, bp := End) END END; LPET4(la, first, ast) END END END LPExprTail; PROCEDURE LPET4( VAR (*IO*) la: LookAhead; READONLY f1: JunoAST.Expr; VAR (*OUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* "F1" is the most recently parsed formula. Its meaning depends on which LPET4 alternative is taken. If the first alternative is taken, then "ast" will be a grouped expression containing "f1" or a procedure call expression with "f1" as the single INOUT parameter to the call. If the second alternative is taken, then "f1" is considered the first expression in an expression list forming a pair of the INOUT parameters to a procedure call. | LPET4 = ")" [ LPET6 ] | | "," Expr LPET5. | LPET6 = ":" QID LPET7. *) BEGIN CASE la.t.kind OF | JunoToken.Kind.RPren => VAR raised := TRUE; BEGIN TRY Match(la); raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.GroupedExpr, expr := f1, bp := End) END END END; IF la.t.kind = JunoToken.Kind.Colon THEN VAR call := NEW(JunoAST.Call, inout_parens := TRUE, inouts := JunoASTUtils.NewExprList(f1, bp := End), bp := End); BEGIN ast := call; LPET6(la, call) END ELSE ast := NEW(JunoAST.GroupedExpr, expr := f1, bp := End) END | JunoToken.Kind.Comma => VAR f2: JunoAST.Expr := NIL; raised := TRUE; BEGIN TRY Match(la); (* skip "," *) Expr(la, f2); (* skip second expr *) raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.Pair, e1 := f1, e2 := f2, bp := End) END END; LPET5(la, f1, f2, ast) END ELSE ast := NEW(JunoAST.GroupedExpr, expr := f1, bp := End); RaiseError(la) END END LPET4; PROCEDURE LPET5( VAR (*IO*) la: LookAhead; READONLY f1, f2: JunoAST.Expr; VAR (*OUT*) ast: JunoAST.Expr) RAISES {Error, JunoLex.Error, Rd.Failure} = (* "F1" and "f2" are the two most recently parsed expressions of an expression list. Their meaning depends on which LPET5 alternative is taken. If the first alternative is taken, then they are either the two elements of a pair or the two INOUT parameters to a procedure. If the second alternative is taken, then they are the first two of many INOUT parameters to a procedure. | LPET5 = ")" [ LPET6 ] | | "," QIDList ")" LPET6. | LPET6 = ":" QID LPET7. *) BEGIN CASE la.t.kind OF | JunoToken.Kind.RPren => VAR raised := TRUE; BEGIN TRY Match(la); raised := FALSE FINALLY IF raised THEN ast := NEW(JunoAST.Pair, e1 := f1, e2 := f2, bp := End) END END END; IF la.t.kind = JunoToken.Kind.Colon THEN VAR call := NEW(JunoAST.Call, inout_parens := TRUE, inouts := NewExprList2(f1, f2), bp := End); BEGIN ast := call; LPET6(la, call) END ELSE ast := NEW(JunoAST.Pair, e1 := f1, e2 := f2, bp := End) END | JunoToken.Kind.Comma => VAR exprs := NewExprList2(f1, f2); call := NEW(JunoAST.Call, bp := End, inout_parens := TRUE, inouts := exprs); qid_list: JunoAST.QIdList; BEGIN ast := call; VAR raised := TRUE; BEGIN TRY Match(la); raised := FALSE FINALLY (* skip "," *) IF raised THEN (* fix "call" so it will seem to contain > 2 inouts *) INC(call.inouts.size); exprs.head.next.next := exprs.head END END END; TRY QIdList(la, NIL, qid_list) FINALLY (* skip QID's *) INC(exprs.size, qid_list.size); exprs.head.next.next := qid_list.head END; MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) LPET6(la, call) (* skip rest of proc *) END ELSE ast := NEW(JunoAST.Pair, e1 := f1, e2 := f2, bp := End); RaiseError(la) END; END LPET5; PROCEDURE LPET6( VAR (*IO*) la: LookAhead; VAR (*IO*) ast: JunoAST.Call) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Assumes "ast" is initially a JunoAST.Call whose "inouts" field has been filled in. Expects JunoToken.Kind.Colon as next token. | LPET6 = ":" QID LPET7. | LPET7 = "(" [ ExprList ] ")". *) BEGIN MatchKind(la, JunoToken.Kind.Colon, NilRef); (* skip ":" *) QID(la, ast.name); (* skip proc name *) MatchKind(la, JunoToken.Kind.LPren, NilRef); (* skip "(" *) IF la.t.kind # JunoToken.Kind.RPren THEN ExprList(la, ast.ins) (* skip expr list *) ELSE ast.ins := JunoAST.EmptyExprList (* make IN params empty *) END; MatchKind(la, JunoToken.Kind.RPren, NilRef); (* skip ")" *) END LPET6; (* ============================= MISCELLANEOUS ============================= *) PROCEDURE ExprList( VAR (*IO*) la: LookAhead; VAR (*IO*) elist: JunoAST.ExprList) RAISES {Error, JunoLex.Error, Rd.Failure} = (* If "elist = NIL", then this procedure parses the next expression as the first element of a list of expressions, and initializes "elist" to a new JunoAST.ExprList whose first element is that expression. Otherwise, it assumes "elist" is already initialized to a JunoAST.ExprList with a single element. In either case, this procedure parses the rest of the expression list. | ExprList = Expr { "," Expr }. *) VAR curr: JunoAST.ExprLink; BEGIN IF elist = NIL THEN curr := NEW(JunoAST.ExprLink); elist := NEW(JunoAST.ExprList, size := 1, head := curr, bp := End); Expr(la, curr.expr) ELSE curr := elist.head END; WHILE la.t.kind = JunoToken.Kind.Comma DO INC(elist.size); curr.next := NEW(JunoAST.ExprLink); curr := curr.next; Match(la); (* skip "," *) Expr(la, curr.expr) (* skip Expr *) END END ExprList; PROCEDURE NewExprList2(READONLY e1, e2: JunoAST.Expr): JunoAST.ExprList = (* Return an expression list of length 2 containing "e1" and "e2". *) VAR curr := NEW(JunoAST.ExprLink, expr := e1); result := NEW(JunoAST.ExprList, size := 2, head := curr, bp := End); BEGIN curr.next := NEW(JunoAST.ExprLink, expr := e2); RETURN result END NewExprList2; PROCEDURE QID(VAR (*IO*) la: LookAhead; VAR (*IO*) qid: JunoAST.QId) RAISES {Error, JunoLex.Error, Rd.Failure} = (* If "qid = NIL", "qid" is first bound to a new JunoAST.QId. In this case, "la.t.kind" must be "JunoToken.Kind.Id", or else the Error exception is raised with "qid # NIL" and "qid.id0 = NIL". In the error-free case when the current token is an identifier, the procedure stores the current token in "qid.id0". If "qid # NIL", it is assumed that "qid" contains the first part of a (potentially) qualified identifier in "qid.id0". If the identifier is not qualified (i.e., no JunoToken.Kind.Dot appears in the token stream after the first identifier), then the single identifier is stored in "qid.id1", and "qid.id0" is set to the special value "JunoAST.NilId". | QID = Id [ "." Id ]. *) VAR id: REFANY; BEGIN IF qid = NIL THEN qid := NEW(JunoAST.QId, bp := End); TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY qid.id0 := NARROW(id, JunoAST.Id) END END; IF la.t.kind = JunoToken.Kind.Dot THEN Match(la); (* skip "." *) TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY qid.id1 := NARROW(id, JunoAST.Id) END ELSE qid.id1 := qid.id0; qid.id0 := JunoAST.NilId; END END QID; PROCEDURE QIdList( VAR (*IO*) la: LookAhead; VALUE qid: JunoAST.QId; VAR (*OUT*) qids: JunoAST.QIdList) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Read a QIDList from "la", storing the result in "qids". If "qid # NIL", it is expected to contain the first QID of the list. Otherwise, the first element of the list is read from "la"; in this case, if the current token is not "JunoToken.Kind.Id", this procedure raises Error. | QIDList = QID { "," QID }. *) VAR curr: JunoAST.ExprLink; BEGIN TRY IF qid = NIL THEN QID(la, qid) END FINALLY qids := JunoASTUtils.NewQIdList(qid, bp := End) END; curr := qids.head; WHILE la.t.kind = JunoToken.Kind.Comma DO INC(qids.size); curr.next := NEW(JunoAST.ExprLink); curr := curr.next; Match(la); (* skip "," *) TRY qid := NIL; QID(la, qid) FINALLY (* skip QId *) curr.expr := qid END END END QIdList; PROCEDURE NearVar( VAR (*IO*) la: LookAhead; VAR (*IO*) nv: JunoAST.NearVarLink) RAISES {Error, JunoLex.Error, Rd.Failure} = (* If "nv" = NIL, then a new "NearVarLink" is allocated for "nv"; otherwise, the information in "nv" is overwritten. | NearVar = Id [ ("~" | "=") Expr ]. *) BEGIN IF nv = NIL THEN nv := NEW(JunoAST.NearVarLink) END; VAR id: REFANY; BEGIN TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY nv.id := NARROW(id, JunoAST.Id) END END; IF la.t.kind = JunoToken.Kind.Near THEN Match(la); (* skip "~" *) Expr(la, nv.hint) (* skip expression *) ELSIF la.t.kind = JunoToken.Kind.Equals THEN nv.frozen := TRUE; Match(la); (* skip "=" *) Expr(la, nv.hint) (* skip expression *) ELSE nv.hint := JunoAST.NilExpr END END NearVar; PROCEDURE NearVarList( VAR (*IO*) la: LookAhead; VAR (*OUT*) nv: JunoAST.NearVarList) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | NearVarList = NearVar { "," NearVar }. *) VAR last: JunoAST.NearVarLink; BEGIN nv := NEW(JunoAST.NearVarList, bp := End); NearVar(la, nv.head); last := nv.head; INC(nv.size); WHILE la.t.kind = JunoToken.Kind.Comma DO INC(nv.size); last.next := NEW(JunoAST.NearVarLink); last := last.next; Match(la); (* skip "," *) NearVar(la, last) (* skip NearVar *) END END NearVarList; PROCEDURE IdList0( VAR (*IO*) la: LookAhead; VAR (*OUT*) ids: JunoAST.IdList; READONLY first: JunoAST.Id := NIL) RAISES {Error, JunoLex.Error, Rd.Failure} = (* | IDList = Id { "," Id }. *) VAR id: REFANY; curr := NEW(JunoAST.IdLink); BEGIN ids := NEW(JunoAST.IdList, size := 1, head := curr, bp := End); IF first = NIL THEN TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY curr.id := NARROW(id, JunoAST.Id) END ELSE curr.id := first END; WHILE la.t.kind = JunoToken.Kind.Comma DO INC(ids.size); curr.next := NEW(JunoAST.IdLink); curr := curr.next; Match(la); (* skip "," *) TRY MatchKind(la, JunoToken.Kind.Id, id) FINALLY (* skip Id *) curr.id := NARROW(id, JunoAST.Id) END END END IdList0; PROCEDURE NewIdList(READONLY id: JunoAST.Id := NIL): JunoAST.IdList = (* Create and return a new JunoAST.IdList. If "id # NIL", then the returned list has size 1 and the single value "id". Otherwise, the list has size 0. *) VAR result := NEW(JunoAST.IdList, bp := End); BEGIN IF id # NIL THEN result.size := 1; result.head := NEW(JunoAST.IdLink, id := id) END; RETURN result END NewIdList; PROCEDURE MatchKind( VAR (*IO*) la: LookAhead; READONLY kind: JunoToken.Kind; VAR (*OUT*) val: REFANY ) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Like Match() below, only raises "Error" with the appropriate "ErrorRec" if "la.t.kind # kind". "Val" is set to the value corresponding to the current token; this only has meaning if "kind" is one of: "JunoToken.Kind.LitReal", "JunoToken.Kind.LitText", "JunoToken.Kind.Id", or "JunoToken.Kind.Comment". *) BEGIN IF la.t.kind # kind THEN val := NIL; RaiseError(la, kind) END; val := la.t.val; (* do assignment in all cases *) Match(la) END MatchKind; PROCEDURE MatchReal( VAR (*IO*) la: LookAhead; READONLY kind: JunoToken.Kind; VAR (*OUT*) num: JunoValue.Real ) RAISES {Error, JunoLex.Error, Rd.Failure} = (* Like Match() below, only raises "Error" with the appropriate "ErrorRec" if "la.t.kind # kind". "Val" is set to the real value corresponding to the current token, namely "la.t.num". This operation is meaningful only if "kind" is "JunoToken.Kind.LitReal". *) BEGIN IF la.t.kind # kind THEN RaiseError(la, kind) END; num := la.t.num; Match(la) END MatchReal; PROCEDURE Match(VAR (*IO*) la: LookAhead) RAISES {JunoLex.Error, Rd.Failure} = (* Reads the next token into "la.t". If there is a lex error reading the next token, it is converted into the appropriate "JunoLex.ErrorRec" and "JunoLex.Error" is raised. *) BEGIN la.t := la.s.next(); INC(la.cnt) END Match; PROCEDURE RaiseError( VAR (*IO*) la: LookAhead; READONLY kind := JunoToken.Kind.Unknown) RAISES {Error} = (* Raises "Error" with an argument "ErrorRec" corresponding to the current token, and the expected kind of token "kind". This procedure also has the side-effect of decrementing "la.cnt", since we don't want to count the token that caused the parse error as having been parsed. *) BEGIN DEC(la.cnt); RAISE Error(NEW(ErrorRec, found := JunoToken.Copy(la.t), additional := JunoLex.Close(la.s), expected := kind)) END RaiseError; BEGIN End := JunoAST.End END JunoParse.