Today:
Some more about executing the syntax tree. Type checking.
ASU 6.1-6.2.
int main() { int i; *i = 2; // invalid type argument of `unary *' break; // break statement not within loop or switch e: e: // duplicate label `e' return 0; }
Type tree:
-> / \ x pointer / \ \ char char integerType DAG:
-> / \ x pointer || \ char integer
cdecl> declare a as pointer to int
int *a
cdecl> declare a as array 10 of pointer to function returning double
double (*a[10])()
cdecl> explain int *(*foo)[8]
declare foo as pointer to array 8 of pointer to int
class B { public: int x; virtual void f() { } }; class D1 : public B { public: int y; }; void f(B* b) { D1* d1 = dynamic_cast<D1*>(b); if (d1 != 0) d1->y = 4711; }
Strongly typed language = the compiler can guarantee that the program will execute without type errors.
Grammar (slighly modified from the one on ASU page 349):
Program --> Declarations ; ExprA literal is a character constant, for example 'a'.
Declarations --> Declarations ; Declarations | id : Type
Type --> char | integer | array [ num ] of Type | *Type
Expr --> literal | num | id | Expr + Expr | Expr [ Expr ] | *Expr
Example program (with a type error?):
Idea: Add an attribute type to each node in the parse tree!n : integer; i : integer; a : array [256] of char; n + a[i] + 7
A translation scheme, with semantic actions, can be used for type checking. This part of the translation scheme saves the type of a variable in the symbol table (ASU Fig 6.4):
Type --> char { Type.type := char; }
Type --> integer { Type.type := integer; }
Type --> *Type1 { Type.type := pointer(Type1.type); }
Type --> array [ num ] of Type1 { Type.type := array(1..num.val, Type1.type); }
Declarations --> id : Type { addtype(id.entry, T.type); }
Program --> Declarations ; Expr
Declarations --> Declarations ; Declarations
Type --> num { Expr.type := integer; }Variables have the type they were declared as:
Expr --> literal { Expr.type := char; }
Expr --> id { Expr.type := lookup(id.entry); }Adding two integers gives an integer, anything else is a type error:
Indexing into an array:Expr --> Expr1 + Expr2 { if (Expr1.type == integer and Expr2.type == integer) Expr.type = integer; else Expr.type = type_error; }
Expr --> Expr1 [ Expr2 ] { if (Expr2.type == integer and Expr1.type == array(s, t)) Expr.type = t; else Expr.type = type_error; }
The while statement:
The expression statement from C:Stmt --> while ( Expr ) Stmt1 { if (Expr.type == integer and Stmt1.type == void) Stmt.type == void; else Stmt.type == type_error; }
Stmt --> Expr ; { if (Expr.type != type_error) Stmt.type == void; else Stmt.type == type_error; }
Expr --> Expr1 ( Expr2 )Add function declarations to the declaration part, for example with the type syntax int -> int:
f : int -> int;Grammar for the function type:
f(7) + 3;
Type --> Type1 "->" Type2Translation scheme for the function type:
Translation scheme for type checking a function call:Type --> Type1 "->" Type2 { Type.type = function(Type1.type, Type2.type); }
Expr --> Expr1 ( Expr2 ) { if (Expr1.type != function(s, t) and Expr2.type == 2) Stmt.type == t; else Stmt.type == type_error; }
More structural equivalence in C -- m1 and m2 have the same type:typedef int T1; typedef int T2; T1 t1; T2 t2; t1 = t2; // ok
Checking type equivalence:double m1[14][17]; double m2[14][17];
C uses structural equivalence for all types except records ("structs"):
This is to aviod cycles in the type representation:struct A { int foo; double fum; }; struct B { int blaj; double urko; }; struct A a; struct B b; a = b; // "incompatible types in assignment"
But note that TA1 and TA2 are the same type (since both are A):struct L { int foo; struct L* next; };
typedef struct A TA1; typedef struct A TA2; TA1 ta1; TA2 ta2; ta1 = ta2; // ok