• Static checking and symbol table • Chapter 6, Chapter 7.6 and Chapter 8.2 • Static checking: check whether the program follows both the syntactic and semantic conventions at compile time (versus dynamic checking -- check at run time). • Examples of static checking – Type checks: – Flow of control checks int a, b[10], c; … a = b + c; main { int I …. I++; break; } – Examples of static checks – uniqueness check: – defined before use: – name related check: main() { int i, j; double i, j; …. } main() { int i; i1 = 0; …. } LOOPA: LOOP EXIT WHEN I=N I=I+1; END LOOP LOOPB; – Some checks can only be done at runtime: • Array-bound checking in java: a[i] = 0; – To perform static checks, semantic information must be recorded in some place -- symbol table. • Grammar specifies the syntax, additional (semantic) information, sometimes called attributes, must be recorded in symbol table for all identifiers. • Typically attributes in a symbol table entry include type and offset (where in the memory can I find this variable?). – Struct {int id; int type; int offset;} stentry; • Organization of a symbol table: – basic requirement: must be able to find the information associated with a symbol (identifier) quickly. – Example: array, link list, hash table. – Provides two functions: enter(table, name, type, offset) and lookup(name); – Dealing with nested scope: Program sort(input, output) var a: array [0..10] of integers; x: integer; procedure readarray var x : real; begin …. x …. End procedure quicksort(i, j) begin … x … end begin … x … end main() { int a, b; a = 0; { int a; a = 1; } printf(“a = %d\n”, a); } – How to organize the symbol table? – How to do lookup and enter? • One symbol table for each scope (procedure, blocks)? • Maintain a stack of symbol tables for lookup/enter • Symbol tables for sort: nil header a ... x ... readarray quicksort header x …. Symbol table for readarray Symbol table for sort header Symbol table for quicksort • How does the compiler created the symbol table? – First let us consider the simple case: no nested scope, every thing entered into one symbol table: table by using • enter (table, id, type, offset) – grammar: P ->D D ->D; D D ->id : T T -> integer T ->real T ->array [num] of T T ->^T I : array [10] of integer; j : real; k : integer I array(10, integer) j real k integer 0 40 48 P -> {offset = 0;} D D ->D; D D ->id : T {enter(table, id.name, T.type, offset); offset:= offset + T.width} T -> integer {T.type = integer; T.width = 4} T ->real {T.type = real; T.width = 8;} T ->array [num] of T1 {T.type = array(num.val, T1.type); T.width = num.val * T1.width} T ->^T1 {T.type = pointer(T1.type); T.width = 4;} – Now consider the case when you have nested procedures (blocks can be considered as special procedures) • must maintain a stack of symbol tables, create new ones when entering new procedure • must reset offset when entering new procedures (a stack of offsets) • Let us also compute the total size of a table – Grammar: P->D D ->D; D D->id : T D->proc id; D; S T ->integer | real | array[num] of T | ^T • mktable(previous): make a new table, properly set all links and related information. • Enter(table, name, type, offset). • Addwidth(table, width): compute all memory needed by the symbol table. • Enterproc(table, name, newtable): enter the procedure name with its symbol table into the old table. – Grammar: P->{t=mktable(nil); push(t, tblptr);push(0, offset);}D {addwidth(top(tblptr), top(offset))} D ->D; D D->id : T {enter(top(tblptr), id.name, T.type, top(offset)); top(offset) = top(offset) + T.width;} D->proc id; {t:=mktable(top(tblptr));push(t, tblptr); push(0, offset);}D; S {t:= top(tblptr);addwidth(t, top(offset)); pop(tblptr); pop(offset);enterproc(top(tblptr), id.name, t)} • Dealing with structure (record): – T ->record D end – Make a new symbol table for all the fields in the record. T->record { t=mktable(nil); push(t, tblptr); push(0, offset); } D end { T.type = record(top(tblptr)); T.width = top(offset); pop(tblptr); pop(offset); } Question: How does allowing variable declaration at anywhere in a program (like in C++, java) affect the maintenance of the symbol tables? – Type checking • Make sure operations can be performed on operands. • Make sure the types of actual arguments matches the types of formal arguments. • Need a type system to do the job. – A type system is a collection of rules for assigning type expression to the various parts of a program. – The type system for a practical language can be complicated. • Type checking of expressions: P->D;E D->D;D | id : T T->char | integer | array[num] of T | ^T E->literal | num | id | E mod E | E[E] | E^ P->D;E D->D;D D->id : T {enter(id.val, T.type);} T->char {T.type = char;} | integer {T.type = integer;} | array[num] of T1 {T.type = array(num.val, T1.type);} | ^T1 {T.type = pointer(T1.type);} E->literal {E.type = char;} | num {E.type = integer;} | id {E.type = lookup(id.val);} | E1 mod E2 {if E1.type == integer && E2.type ==integer then E.type = integer; else E.type =error;} | E1[E2] {if E1.type == array(s, t) && E2.type == integer then E.type = t; else E.type =error;} | E1^ {if E1.type == pointer(t) then E.type = t; else E.type =error;} • Type checking for statements S -> id := E S -> if E then S1 S ->while E do S1 S->S1;S2 • Type checking for statements S -> id := E {if id.type == E.type then S.type = void; else S.type = error;} S -> if E then S1 {if E.type == boolean then S.type = S1.type; else S.type = error;} S ->while E do S1 { if E.type == boolean then S.type = S1.type; else S.type = error;} S->S1;S2 {if S1.type == void and S2.type == void then S.type = void; else S.type = type_error; } • Type checking for functions: T->T1->T2 /* function declaration */ {T.type = T1.type ->T2.type} E->E1(E2) /* function call */ {if E1.type == t1.type->t2.type && E2.type == t1.type then T.type = t2.type; else T.type - error; } • Equivalence of type expressions • Name equivalence - each type with different name is different • structural equivalence - names are replaced by the type expressions they define • Example: type link = ^cell; var next : link last : link p: ^cell • Is structural equivalence good for C++? – Other things related to type. • coercion: implicit type conversion: – e.g. double x; ….x = 1; • overloading: – a function or operator can represent different operations in different contexts. • polymorphic functions: – the body of a polymorphic function can be executed with arguments of different types.