We will be using a symbol table to keep track of declared symbols and help typechecking. As scopes get nested, we have to use a stack of scopes, and use static scoping rules to access non-local variables. You may choose to implement this as a stack of symbol tables.
Recursive procedures are allowed. Mutually-recursive procedures are not. To facilitate this, first store all declarations in the scope, and then process function bodies.
For extra credit (5 bonus marks), implement a check on correct returns from functions and procedures:
program : scope scope : 'begin' decls stmts 'end' /* First start a new scope for decls and stmts. */ /* Upon exit, optionally print symbol table for current */ /* scope and remove current scope off the stack */ decl : type ':' id /* If id is not already declared in current scope, */ /* insert (id,type) into table. */ type ':' id '[' exp ']' /* Same as above but also check that t(exp)=integer. */ /* Insert exp as a parameter to (id,type). */ fundecl procdecl fundecl : type 'function' id scope /* make new entry in symbol table for function, with */ /* return of type type, name id and no params */ /* start new scope */ /* give errors if duplicate name */ type 'function' id '(' parms ')' scope /* make new entry in symbol table for function, with */ /* return of type type, name id and parameters params. */ /* Make sure there are no duplicate names of params */ /* give errors if duplicate name */ procdecl : 'procedure' id scope 'procedure' id '(' parms ')' scope /* same as fundecl, but return is VOID */ morestmts : stmt /* t(morestmts) = t(stmt) */ stmt morestmts1 /* t(morestmts) = t(morestmts1) */ /* make sure that type of all statements is VOID */ stmt : var '<-' exp /* check that var has an lvalue and t(var) = t(exp) */ ifstmt wstmt lpstmt 'exit' rtstmt 'put' outputs 'get' outputs scope prcstmt prcstmt : id /* make sure id is declared as procedure */ id '(' args ')' /* same as above and that args match in number */ /* and type */ ifstmt : 'if' exp 'then' stmts1 'else' stmts2 'end' /* check t(exp)=bool */ 'if' exp 'then' stmts 'end' /* check that t(exp)=bool */ rtstmt : 'return' '(' exp ')' /* make sure that t(exp) = */ /* t(return(current scope)) */ 'return' wstmt : 'while' exp 'do' stmts 'end' /* check t(exp) = boolean */ exp : '-' exp1 /* if t(exp1) = int then t(exp) = int */ 'not' exp1 /* if t(exp) = bool then t(exp) = bool */ exp1 '+' exp2 exp1 '-' exp2 exp1 '*' exp2 exp1 '/' exp2 exp1 '^' exp2 /* if t(exp1) = int and t(exp2) = int then t(exp) = int */ exp1 'and' exp2 exp1 'or' exp2 /* if t(exp1) = bool and t(exp2) = bool then t(exp) = bool */ exp1 '>=' exp2 exp1 '>' exp2 exp1 '<' exp2 exp1 '<=' exp2 /* if t(exp1) = int and t(exp2) = int then t(exp) = bool */ exp1 '=' exp2 exp1 'not=' exp2 /* if t(exp1) = t(exp2) then t(exp) = bool */ '(' exp ')' '{' decls stmts 'yields' exp '}' /* start new scope for unnamed function returning t(exp), */ /* with decls and stmts. */ var id '(' args ')' /* lookup function id and check type of args */ constant var : id /* check that id has been defined */ id '[' exp ']' /* check that id has been defined and t(exp)=int */ parms : parm parms ',' parm /* make sure parameters have unique names */