260 likes | 385 Views
Lecture #16, March 7, 2007. Mutual Recursion Symbol Tables Class Hierarchies Type checkers that rebuild code. Assignments. Reading Finish Chapter 4. Sections 4.4, 4.5, and 4.6 pages 188-208 Read Section 5.7 on Symbol tables page 238-248 Possible Quiz next Monday.
E N D
Lecture #16, March 7, 2007 • Mutual Recursion • Symbol Tables • Class Hierarchies • Type checkers that rebuild code
Assignments • Reading • Finish Chapter 4. Sections 4.4, 4.5, and 4.6 pages 188-208 • Read Section 5.7 on Symbol tables page 238-248 • Possible Quiz next Monday. • Project 3 is due Monday March 19
Mutual Recursion • Mutual recursion is found in almost all real languages • Mutual recursion requires different techniques • Multiple passes. • One pass to build a temporary table with just the information about the mutually recursive entities • Second pass to type the recursive entities where the current scope is extended with the temporary table. • Inside this pass, the table is often extended even more to capture information about entities declared in a single one of the mutually recursive entities. • Finally, the temporary table is exported to the scope in which the mutually recursive entities exist.
Example in ML x: int x: int f: int -> int val x = 56; fun f x = x + 5; fun even n = if n=0 then true else odd (n-1) and odd n = if n=0 then false else if n=1 then true else even (n-1); val main = even(f x) x: int f: int -> int even: int->bool odd: int->bool x: int f: int -> int even: int->bool odd: int->bool n: int x: int f: int -> int even: int->bool odd: int->bool main: bool
Concrete Example and Dec = Valdec of (string*MLtype)*Exp | Fundec of (string*MLtype*MLtype)*string*Exp | Mutdec of Dec list • Assume we represent declarations as above. • Every val and fun declaration contains explicit information about their types (as is true in Mini-Java) • Then the concrete syntax may look like: val (x:int) = 56; fun (f:int->int) (x:int) = x + 5;
Type Checking Declarations • Type checking will depend upon two attribute computations. • Checking declarations will map an environment to a delta environment. • New environment will be the old one plus the delta. TCExp:Exp ->(string * MLtype)list ->MLtype TCDec:Dec ->(string * MLtype)list ->(string * MLtype) list fun (even:int ->bool) (n:int) = if n=0 then true else odd (n-1) and (odd:int -> bool) (n:int) = if n=0 then false else if n=1 then true else even (n-1); x: int f: int -> int even: int -> bool odd: int -> bool
Non mutually recursive cases fun TCDec (Valdec((nm,t),exp)) cntxt = let val bodyt = TCExp exp cntxt in if typeeq(t,bodyt) then [(nm,t)] else unexpected exp bodyt t end | TCDec (Fundec((f,dom,rng),x,body)) cntxt = let val ft = Arrow(dom,rng) val bodyt = TCExp body((x,dom)::cntxt) (* f is not recursive unless inside a Mutdec *) in if typeeq(bodyt,rng) then [(f,ft)] else unexpected body bodyt rng end
Mutually recursive case | TCDec (Mutdec ds) cntxt = let fun pass1 [] cntxt = cntxt | pass1 (Valdec(p,b)::ds) cntxt = pass1 ds (p::cntxt) | pass1 (Fundec((f,d,r),x,b)::ds) cntxt = pass1 ds ((f,Arrow(d,r))::cntxt) val temp = pass1 ds cntxt val pass2 = map (fn d => TCDec d temp) ds in List.concat pass2 end; result temp pass2 cntxt even: int->bool odd: int->bool x: int f: int -> int odd: int->bool even: int->bool x: int f: int -> int even: int->bool odd: int->bool
Symbol Tables • Symbol Tables map names to information • Many kinds of Names • Variables • Procedure and function names • Labels • Data Structure names (Constructors in ML or named Struct in C) • Types • Many kinds of information • Type • Physical location in source (line number) • Size • Storage class • Lexical location (Inside what class) • Location in memory in the translation
Multiple symbol tables • Compilers might use one big table, or many smaller ones. • Multiple tables have advantages • Each table stores uniform items • Tables might have limited scope and can be recycled • Each table can use different implementation • Hash Table • Linked List • Balanced Tree • Each table probably has different access patterns • Create Once, then read only • Block Structured, so stack allocation is possible • Insertions only • Both insertions and deletions • Tables can have mutable or non-mutable data • Language may have multiple name spaces • Types, labels, variables, selectors, . . .
Nested Scope • Many tables record information with block structure or nested scope. • Stack allocation is possible • Easy to implement in a functional language like ML • Use inherited attribute computation • Table becomes a parameter to the function • Entering new block means a new call to function with the parameter augmented. • Exiting scope means restoring the old parameter • Natural in a functional style since the old parameter is always still around.
Operations on nested scope • Initialize Scope • Insert name value Scope • Lookup name Scope • Finalize Scope type ('name,'value)table = ('name * 'value) list; type ('name,'value)Scope = (('name,'value)table) list ; fun initialize scope = [ ] :: scope fun insert name value (table::scope) = ((name,value)::table)::scope fun Lookup name (table::scope) = case List.find (fn (x,y) => x=name) table of NONE => error | SOME (x,y) => y fun finalize (table::scope) = scope More sophisticated kinds of tables can be used here rather than a linked list of pairs. Hash Tables, Balanced trees, etc.
Data Structures for the MiniJava Type Checker • Scope in object oriented languages like mini-java is attached to the class structure. • Each class has its own symbol table • Usually this table is an ordinary nested scope table • Each class must link to the symbol table for its super-class. • The Class hierarchy is a tree of symbol tables. • The set of operations on the tables can be found by inspecting the type rules.
Judgments from the type rules Th, C, TE |- exp : type Th, R, C, TE |= stmt C |~ t1≤ t2 t1= t2 C defines x In C p has method f In C p has variable x Op <+> : (t1,t2) -> t3 t is basic
object point numeric boolean void double int colorpoint Data Structure datatype CTab = Node of Id * (* Class Name *) (Id * Type)list * (* Class Variables *) (Id * Type list * Type)list * (* Methods *) CTab ref * (* Parent Class *) CTab list (* Sub Classes *) | NullClass; val root = Node("object",[],[],ref NullClass,[]); CTab for class table is an n-way branching tree.
newClass fun newClass name vars methods parent NullClass = NullClass | newClass name vars methods parent (n as Node(nm,vs,ms,p,subs)) = if parent=nm then let val p1 = ref n val new = Node(name,vars,methods,p1,[]) val newP = Node(nm,vs,ms,p, new :: subs) val _ = p1 := newP in newP end else Node(nm,vs,ms,p, map (newClass name vars methods parent) subs) • newClass creates a new class hierarchy from the old one. The old one is unchanged.
val t1 = newClass "point" [] [] "object" root; root Node(“object”, [ ], [ ], ref NullClass,[ ]) new Node(“point”, [ ], [ ], ref _ ,[ ]) newP Node(“object”, [ ], [ ], ref NullClass, [Node(“point”, [ ], [ ], ref _ ,[ ])]) • The final step is to overwrite the pointer to root with newP, and then return newP • newClass rebuilds the part of the tree that are unchanged • The references make cyclic structures
val t1 = newClass "point" [] [] "object" root;val t2 = newClass "colorpoint" [] [] "point" t1; t2 Node(“object”, [ ], [ ], ref NullClass ,[ _ ]) Node(“point”, [ ], [ ], ref _ ,[ _ ]) Node(“colorpoint”, [ ], [ ], ref _ ,[ ])
t3 Node(“object”, [ ], [ ], ref NullClass ,[ _ , _ ]) Node(“point”, [ ], [ ], ref _ ,[ _ ]) Node(“colorpoint”, [ ], [ ], ref _ ,[ ]) Node(“person”, [ ], [ ], ref _ ,[ ]) val t1 = newClass "point" [] [] "object" root;val t2 = newClass "colorpoint" [] [] "point" t1;val t3 = newClass "person" [] [] "object" t2
C defines x fun defines name NullClass = false | defines name (Node(n,vs,ms,p,ss)) = if name=n then true else List.exists (defines name) ss;
t1= t2 fun basiceq (x,y) = case (x,y) of (Real,Real) => true | (Int,Int) => true | (Bool,Bool) => true | (_,_) => false fun typeeq (x,y) = case (x,y) of (BasicType x,BasicType y) => basiceq(x,y) | (ArrayType x,ArrayType y) => basiceq(x,y) | (ObjType x,ObjType y) => x=y | (VoidType,VoidType) => true | (_,_) => false
C |~ t1≤ t2 fun useTree NullClass (x,y) = false | useTree (Node(nm,vs,ms,p,ss)) (x,y) = if nm = y then List.exists (defines x) ss else List.exists (fn t => useTree t (x,y)) ss fun subtype classH (x,y) = case (x,y) of (x,ObjType "object") => true | (BasicType Int,ObjType "numeric") => true | (BasicType Real,ObjType "numeric") => true | (ObjType x,ObjType y) => useTree classH (x,y) | (_,_) => typeeq(x,y)
Type checkers that rebuild the syntax tree • Some type checkers use inference to add things to the syntax tree that are missing. • Coercions • 3 + 5.6 -> toFloat 3 + 5.6 • Implicit class information • X.f(3,5) -> X.ffrompoint(2,5) • Implicit type information • (fn x => x + 1) -> (fn (x:Int) => x + 1) • Such type checkers return a tuple. • The tuple includes the synthesized attributes and a new syntax tree. Compare the two types • TCExp2: Exp -> (string * MLtype) list -> MLtype * Exp • TCExp: Exp -> (string * MLtype) list -> MLtype
TCExp2 When a term has no sub terms, the old term can usually be returned fun TCExp2 x cntxt = case x of Lit c => (TCConstant c,x) | Var s => (case List.find (fn (nm,t) => nm=s) cntxt of SOME(nm,t) => (t,x) | NONE => error x "Undeclared variable") | Infix(l,x,r) => let val (ltype,l2) = TCExp2 l cntxt val (rtype,r2) = TCExp2 r cntxt val (lneed,rneed,result) = TCOp x in case (typeeq(ltype,lneed),typeeq(rtype,rneed)) of (true,true) => (result, Infix(l2,x,r2) ) | (true,false) => unexpected r rtype rneed | (false,true) => unexpected l ltype lneed | (false,false) => unexpected l ltype lneed end Sub terms are rebuilt and the returned term is constructed from these.
Why Bother • If all we ever do is return the same tree, why bother? • We use type (or other) information to add little bits of syntax. Infix(l,x,r) => let val (ltype,l2) = TCExp2 l cntxt val (rtype,r2) = TCExp2 r cntxt val (lneed,rneed,result) = TCOp x in case (typeeq(ltype,lneed),typeeq(rtype,rneed)) of (true,true) => Fix result (x,ltype,rtype) l2 r2 | (true,false) => unexpected r rtype rneed | (false,true) => unexpected l ltype lneed | (false,false) => unexpected l ltype lneed end fun Fix result info left right = case info of (Plus,Int,Real) => (result,Infix( Int2Real left ,Plus,right)) | (Plus,Real,Int) => (result,Infix(left,Plus, Int2Real right )) | (oper, _ , _ ) => (result,Infix(left,oper,right))
Class exercise • As an in class exercise, lets continue the function on the previous pages. • Start with an non rebuilding type-checker • Make a copy of it. • Rename every call to a new name (so it doesn’t conflict) • For each returned expression add a another value to the tuple. • For each recursive call, accept (or pattern match against) an additional value.