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 */