Semantic Analysis Leonidas Fegaras CSE 5317/4305 L6: Semantic Analysis 1 Type Checking source file get next character scanner get token parser AST type checking AST token symbol table type errors • Checking whether the use of names is consistent with their declaration in the program int x; x := x+1; x.A := 1; x[0] := 0; correct use of x type errors • Statically typed languages: done at compile time, not at run time • Need to remember declarations – Symbol Table CSE 5317/4305 L6: Semantic Analysis 2 Symbol Table • A compile-time data structure used to map names into declarations • It stores: – for each type name, its type definition • eg. for the C type declaration typedef int* mytype, it maps the name mytype to a data structure that represents the type int* – for each variable name, its type • if the variable is an array, it also stores dimension information • it may also store storage class, offset in activation record, etc – for each constant name, its type and value – for each function and procedure, its formal parameter list and its output type • each formal parameter must have – name – type – type of passing (by-reference, by-value, etc) CSE 5317/4305 L6: Semantic Analysis 3 Symbol Table (cont.) • Need to capture nested scopes, if necessary { int a; { int a; a = 1; }; a = 2; }; • Interface: insert ( key: String, binding: Declaration ) lookup ( key: String ): Option[Declaration] begin_scope () end_scope () CSE 5317/4305 L6: Semantic Analysis 4 The Gen Symbol Table class SymbolTable { var symbol_table: List[List[(String,Declaration)]] = Nil def lookup ( key: String ): Option[Declaration] = { val ds = for ( s <- symbol_table; (n,d) <- s if n.equals(key) ) yield d ds match { case c::cs => Some(c) case _ => None } } def insert ( key: String, declaration: Declaration ) { symbol_table match { case c::cs => symbol_table = ((key,declaration)::c)::cs case _ => throw new Error("Empty scope") } } CSE 5317/4305 L6: Semantic Analysis 5 The Gen Symbol Table (cont.) def begin_scope () { symbol_table = List()::symbol_table } def end_scope () { symbol_table match { case c::cs => symbol_table = cs case _ => throw new Error("Empty scope") } } CSE 5317/4305 L6: Semantic Analysis 6 Example { int a; { int a; a = 1; }; a = 2; }; CSE 5317/4305 L6: Semantic Analysis 7 Type ASTs • A typechecker is a function that maps an AST that represents an expression into its type • Need to define the data structures for types: sealed abstract class Type case class IntegerType () extends Type case class BooleanType () extends Type case class NamedType ( typename: String ) extends Type case class ArrayType ( elements: Type ) extends Type case class RecordType ( components: List[(String,Type)] ) extends Type CSE 5317/4305 L6: Semantic Analysis 8 Declarations • The symbol table must contain type declarations (ie. typedefs),variable declarations, constant declarations, and function signatures: var symbol_table: List[List[(String,Declaration)]] = Nil sealed abstract class Declaration case class TypeDeclaration ( declaration: Type ) extends Declaration case class VarDeclaration ( declaration: Type ) extends Declaration case class ConstantDeclaration ( declaration: Type, value: Exp ) extends Declaration case class FunctionDeclaration ( result: Type, parameters: List[Type] ) extends Declaration CSE 5317/4305 L6: Semantic Analysis 9 Typechecking • A tree traversals that checks each node of the AST tree recursively: def typecheck ( e: Expr ): Type = e match { case IntegerExp => IntegerType case TrueExp => BooleanType case FalseExp => BooleanType case VariableExp(name) => st.lookup(name) match { case Some(VarDeclaration(type) => type case Some(_) => throw new Error(name+" is not a variable") case None => throw new Error("Undefined variable: "+name) } CSE 5317/4305 L6: Semantic Analysis 10 Typechecking: BinaryExp case BinOpExp(op,left,right) => { val left_type = typecheck(left) val right_type = typecheck(right) op match { case "+": if (left_type == right_type && left_type == IntegerType()) left_type else throw new Error("expected integers in addition") … } CSE 5317/4305 L6: Semantic Analysis 11 Typechecking: CallExp case CallExp(f,args) => st.lookup(f) match { case Some(FunctionDeclaration(otp,params)) => { if (params.length != args.length) throw new Error("Number of parameters doesn't match number of arguments") else (args.map(typecheck(_)) zip params).map({ case (atp,(_,ptp)) => if (!equal_types(atp,ptp)) throw new Error("The type of call argument ("+atp +") does not match the type of the formal parameter: "+ptp) }) otp } case _ => throw new Error("Undefined function: "+f) } • equal_types(x,y) checks the types x and y for equality • Two types of type equality: type equality based on type name equivalence, or based on structural equivalence CSE 5317/4305 L6: Semantic Analysis 12 The Calculator Interpreter • Evaluate an expression e using a symbol table st: def eval ( e: Expr ): Double = { e match { case RealConst(v) => v case IntConst(v) => v case StringConst(v) => throw new Error("Strings are not permitted: "+e) case Var(v) => st.lookup(v) match { case Some(VarDec(v)) => v case Some(_) => throw new Error(v+" is not a variable") case None => throw new Error("Undefined variable: "+v) } case IfExp(e1,e2,e3) => if (eval(e1) > 0.0) eval(e2) else eval(e3) CSE 5317/4305 L6: Semantic Analysis 13 The Calculator Interpreter (cont.) case CallExp(fnc,args) => st.lookup(fnc) match { case Some(FunDec(body,params)) => if (params.length != args.length) throw new Error("Number of parameters does not much number of arguments") else { st.begin_scope() (args.map(eval(_)) zip params).map({ case (a,p) => st.insert(p,new VarDec(a)) }) val res = eval(body) st.end_scope() res } case Some(_) => throw new Error(fnc+" has not been defined as a function"); case None => throw new Error("Undefined function: "+fnc) } CSE 5317/4305 L6: Semantic Analysis 14 The Calculator Interpreter (cont.) case BinOpExp(op,e1,e2) => { val left = eval(e1) val right = eval(e2) op match { case "plus" => left + right case "minus" => left - right case "times" => left * right case "div" => left / right case "and" => if ((left > 0.0) && (right > 0.)) 1.0 else 0.0 case "or" => if ((left > 0.0) || (right > 0.0)) 1.0 else 0.0 case "eq" => if (left == right) 1.0 else 0.0 case "ne" => if (left != right) 1.0 else 0.0 case "gt" => if (left > right) 1.0 else 0.0 case "lt" => if (left < right) 1.0 else 0.0 case "ge" => if (left >= right) 1.0 else 0.0 case "le" => if (left <= right) 1.0 else 0.0 case _ => throw new Error("Unrecognized binary operation: "+e) } } case _ => throw new Error("Unrecognized expression: "+e) CSE 5317/4305 L6: Semantic Analysis 15