<<

Semantic Analysis Lecture 10

February 20, 2018 Scoping Review

▶ Programs have many symbols declared throughout ▶ Class names ▶ Attribute names ▶ Method names ▶ bound identifiers (e.g., let in Cool) ▶ Language determines scoping rules ▶ Static vs. Dynamic scope ▶ Almost always static... ▶ Globals? ▶ Most-closely-nested?

Compiler Construction 2/1 Symbol Table and Maps ▶ You love tables and maps ▶ We use maps for tracking class attributes and class methods ▶ These are both global inside each class! ▶ We use a map of stacks to implement most-closely-nested rule! ▶ When we enter a new scope (e.g., through let), we push new copies of bound symbols on the stack mapped by each symbol!

Compiler Construction 3/1 Class and Implementation Maps

▶ Classes contains lists of features ▶ Attributes (data) ▶ Methods (operations on the data)

▶ Attributes are global within a class

Compiler Construction 4/1 Scoping Reminder

1 class Main inherits IO{ 2 a : Int <- 1; 3 b : Int <- 2; 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ 7 out_int(a) -- 1 8 let a : Int <- 5 in 9 out_int(a) ; -- 5 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ 7 out_int(a) -- 1 8 let a : Int <- 5 in 9 out_int(a) ; -- 5 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 a Int 8 let a : Int <- 5 in 9 out_int(a) ; -- 5 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 8 let a : Int <- 5 in 9 out_int(a) ; -- 5 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 a Int 8 let a : Int <- 5 in 9 out_int(a) ; -- 5 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 8 let a : Int <- 5 in 9 out_int(a) ; -- 5 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 a Int 8 let a : Int <- 5 in 9 out_int(a) ; -- 5 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 a Int 8 let a : Int <- 5 in b Int 9 out_int(a) ; -- 5 c Int 10 let a : Int <- 10 in 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 a Int; String 8 let a : Int <- 5 in b Int 9 out_int(a) ; -- 5 c Int 10 let a : Int <- 10 in d Int 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Scoping Reminder Class Map Main 1 class Main inherits IO{ a Int 2 a : Int <- 1; b Int 3 b : Int <- 2; c Int 4 c : Int <- 3; 5 a() : Int { a }; 6 main () : Object {{ Symbol Table 7 out_int(a) -- 1 a Int; String; Int 8 let a : Int <- 5 in b Int 9 out_int(a) ; -- 5 c Int; Int 10 let a : Int <- 10 in d Int 11 out_int(b) ; -- 2 12 let a : Int <- 5 in 13 let b : Int <- 6, c : Int <- 7 in 14 let a : String <- "8", d : Int <- 8 in 15 let c : Int <- 7, a : Int <- 9 in 16 out_int(a+b+c+d+self.a()); 17 }}; 18 };

Compiler Construction 5/1 Formal Parameters Class Map a Int We must track formal parameters Symbol Table (main) 1 class Main inherits IO{ 2 a : Int <- 5; 3 main () : Object {{ 4 stuff (9); 5 out_int(a) --outputs 5 6 }}; 7 stuff ( a : Int ) : Int {{ 8 out_int(a); --outputs 9 9 a; 10 }}; 11 };

Compiler Construction 6/1 Formal Parameters Class Map a Int Symbol Table (stuff) We must track formal parameters a Int 1 class Main inherits IO{ 2 a : Int <- 5; 3 main () : Object {{ 4 stuff (9); 5 out_int(a) --outputs 5 6 }}; 7 stuff ( a : Int ) : Int {{ 8 out_int(a); --outputs 9 9 a; 10 }}; 11 };

Compiler Construction 6/1 Class Map ▶ Class attributes are private in Cool

1 class Main inherits IO{ Class Map 2 a : A <- new A; Main 3 main () : Object {{ a A 4 out_int(a.field); 5 -- not allowed A 6 out_int(field); field Int 7 -- nothing in 8 -- symbol table or class map! 9 out_int(a); 10 -- totally Cool 11 }}; 12 }; 13 class A{ 14 field : Int <- 5; Compiler15 Construction}; 7/1 Implementation Map

▶ Class methods are public in Cool

1 class Main inherits IO{ 2 method () : Int { 5 }; 3 method2 () : Int { 5 }; 4 main () : Object {{ 5 out_int(method()); 6 out_int ( 7 ( new A).method2() 8 ); 9 }}; 10 }; 11 class A inherits Main { 12 method2() : Int { 10 }; 13 method3() : Int { 5 }; 14 };

Compiler Construction 8/1 Implementation Map ▶ Class methods are public in Cool Implementation Map

1 class Main inherits IO{ Main 2 method () : Int { 5 }; 1 Object.abort 3 method2 () : Int { 5 }; 2 Object.type_name 4 main () : Object {{ ...... 5 out_int(method()); A 6 out_int ( field loc(2) 7 ( new A).method2() 8 ); 9 }}; 10 }; 11 class A inherits Main { 12 method2() : Int { 10 }; 13 method3() : Int { 5 }; 14 };

Compiler Construction 8/1 Implementation Map ▶ Class methods are public in Cool Implementation Map

1 class Main inherits IO{ Main 2 method () : Int { 5 }; 1 Object.abort 3 method2 () : Int { 5 }; 2 Object.type_name 4 main () : Object {{ ...... 5 out_int(method()); 8 Main.method 6 out_int ( 9 Main.method2 7 ( new A).method2() 10 Main.main 8 ); A 9 }}; field loc(2) 10 }; 11 class A inherits Main { 12 method2() : Int { 10 }; 13 method3() : Int { 5 }; 14 };

Compiler Construction 8/1 Implementation Map ▶ Class methods are public in Cool Implementation Map

1 class Main inherits IO{ Main 2 method () : Int { 5 }; 1 Object.abort 3 method2 () : Int { 5 }; 2 Object.type_name 4 main () : Object {{ ...... 5 out_int(method()); 8 Main.method 6 out_int ( 9 Main.method2 7 ( new A).method2() 10 Main.main 8 ); A 9 }}; 1 Object.abort 10 }; 2 Object.type_name 11 class A inherits Main { ...... 12 method2() : Int { 10 }; 8 Main.method 13 method3() : Int { 5 }; 9 Main.method2 14 }; 10 Main.main

Compiler Construction 8/1 Implementation Map ▶ Class methods are public in Cool Implementation Map

1 class Main inherits IO{ Main 2 method () : Int { 5 }; 1 Object.abort 3 method2 () : Int { 5 }; 2 Object.type_name 4 main () : Object {{ ...... 5 out_int(method()); 8 Main.method 6 out_int ( 9 Main.method2 7 ( new A).method2() 10 Main.main 8 ); A 9 }}; 1 Object.abort 10 }; 2 Object.type_name 11 class A inherits Main { ...... 12 method2() : Int { 10 }; 8 Main.method 13 method3() : Int { 5 }; 9 A.method2 14 }; 10 Main.main 11 A.method3

Compiler Construction 8/1 Validating Identifiers in Expressions ▶ Your program is a list of classes ▶ Each class is a list of features ▶ Each feature could be a method (with an AST!) ▶ Use the AST to determine valid uses of identifiers ▶ We’re not checking the types yet; just whether variables are in scope! ▶ Introduce new bindings to symbol table when encountering let expressions (what else?) ▶ Restore old bindings after leaving a node (why?)

Compiler Construction 9/1 Validating Identifiers in Expressions (2) For each method: 1. Start with a symbol table containing the class map! ▶ Class map tells us starting symbols globally available 2. Add in entries for parameters 3. Recurse through child expression (there is only one per method!) ▶ On let expressions (or case expressions), introduce new bindings ▶ On identifier expressions, check if it exists in the symbol table! ▶ If not, complain to user

Compiler Construction 10/1 Rules of Inference You have survived formal languages in specifying parts of the compiler ▶ Regular expressions (lexer) ▶ Context-free grammars (parser)

We use logical rules of inference as a formalism for type in- ference

Compiler Construction 11/1 Rules of Inference?

Inference rules have the form

If Hypothesis is true, then Conclusion is true

Type checking computes via reasoning:

If E1 and E2 have certain types, then E3 has a certain type

Rules of inference are a compact notation for “If-then” statements

Compiler Construction 12/1 From English to Inference Rules

Start with a simplified and gradually add features...

Building blocks ▶ Symbol “∧” is “and” ▶ Symbol “⇒” is “if-then” ▶ x:T is “x has type T”

Compiler Construction 13/1 English to Inference Rules (2)

If e1 has type Int and e2 has type Int, + then e1 e2 has type Int ∧ ⇒ (e1 has type Int e2 has type Int) + e1 e2 has type Int ( ∧ ) ⇒ + e1 : Int e2 : Int e1 e2 : Int

This is an inference rule! ▶ ∧ e1 : Int e2 : Int are two hypotheses, + e1 e2 : Int is a conclusion!

Compiler Construction 14/1 Notation for Inference Rules By tradition, inference rules are written

⊢ ⊢ Hypothesis1... Hypothesisn ⊢ Conclusion

Cool type rules have hypotheses and conclusions of the form:

⊢ e : T

⊢ means “we can prove that...”

Compiler Construction 15/1 Examples

Int ⊢ i : Int (i is an Integer) ⊢ e1 : Int ⊢ e2 : Int ⊢ + Add e1 e2 : Int (the result of adding to Ints is an Int)

Compiler Construction 16/1 Examples (2)

⊢ e1 : Int ⊢ e2 : Int ⊢ + Add e1 e2 : Int ▶ These rules give templates describing how to type integers and + expressions... ▶ We can fill in the templates, producing a complete typing for any expression!

⊢ false : Int ⊢ true : Int ⊢ true + false : Int

Compiler Construction 17/1 Example: 1+2

⊢ 1 : Int ⊢ 2 : Int ⊢ 1 + 2 : Int

Compiler Construction 18/1