Compilers 5. Attributed Grammars Laszlo Böszörmenyi Compilers Attributed Grammars - 1
Adding Attributes We connect the grammar rules with attributes E.g. to implement type-checking or code generation A step from pure parsing towards translation input tokens parse tree dependency graph evaluation Parse-tree with attributes: annotated parse-tree Creating the tree: annotation or decoration The attributes are evaluated via semantic rules The order is specified by the dependency graph Parse tree and depend. graph may be implicit E.g. in a recursive descent parser the structure of the code tells the evaluation order (parameters, return values) Laszlo Böszörmenyi Compilers Attributed Grammars - 2
Syntax-Directed Definitions (SDD) An SDD is a cfg + attributes + semantic rules Attributes associated to symbols Rules associated to productions X.c denotes attribute c of node X in the parse tree Set of rules b := f(c 1, c 2,... c n ) associated to A α c 1, c 2, c 3,... c n are the attributes belonging to the symbols of the production b may an attribute of A (A.b: synthesized attribute) or an attribute of a symbol from α (α i.b: inherited attribute) f is a side-effect free function If side-effect needed (e.g. print result): dummy attribute Laszlo Böszörmenyi Compilers Attributed Grammars - 3
Synthesized and Inherited Attributes Synthesized attribute A.c of A α depends on The children of node A and itself Can be evaluated in any bottom-up order (e.g. depth-first) Also terminals may have synthesized attributes Lexical values (e.g. number), computed by the lexical analyzer Inherited attribute B.c of A α 1 Bα 2 depends on The father and siblings of node B and itself Terminals may not have inherited attributes (per definition) Combined attributes Evaluation order not trivial May even contain cycles! Production Sem. Rules A.s A B A.s:= B.i B.i B.i:= A.s+1 Laszlo Böszörmenyi Compilers Attributed Grammars - 4
Dependency Graph If b:= f(c), i.e. b depends on c c must be computed before b In the dependency graph: c b Example, A XY A.a = f(x.x, Y.y) (synthesized) X.x = g(a.a, Y.y) (inherited) Dep. Gr. defines a partial order A Y X and Y A X are both corrects orders A topological sort creates a total order m 1, m 2,... m k If m i m k then evaluate m i before m k For the other cases the top. sort creates arbitrary order No topological sort exists, if the graph contains cycles Laszlo Böszörmenyi Compilers Attributed Grammars - 5 X.x A.a Y.y X.x A.a Y.y
S-attributed Definitions Contain only synthesized attributes Evaluated depth-first (post order) L.du = 19 3 * 5 + 4 dfvisit (node n) { E.v = 19 nl for each { (child m of n from left) dfvisit(m); } evaluate attributes of n; } E 1.v = 15 + T.v = 4 Production Semantic Rule 1. L E nl L.du:= E.v ~print(e.v) 2. E E 1 + T E.v:= E 1.v + T.v 3. E T E.v:= T.v 4. T T 1 * F T.v:= T 1.v * F.v 5. T F T.v:= F.v 6. F (E) F.v:= E.v 7. F digit F.v:= digit.v T.v = 15 F.v = 4 T 1.v = 3 * F.v = 5 d.v = 4 F.v = 3 d.v = 3 d.v = 5 Post-order (Polish) notation 3*5+4 35*4+ 19 3*(5+4) 354+* 27 Laszlo Böszörmenyi Compilers Attributed Grammars - 6
Bottom-up Evaluation for S-attr. Def.s With a recursive descent (LL(1)) parser Evaluation at ascending from the recursion With an LR (shift-reduce) parser Evaluation at reduction We push the input on the state stack (shift) If the top of stack is the right side of a production, we replace it by the left-hand non-terminal (reduction) See more at bottom-up parsing Evaluation of an expression grammar We push the operands on an expression stack We pop them for evaluation and push the result back A stack-machine may be implemented in hw. or JVM Laszlo Böszörmenyi Compilers Attributed Grammars - 7
Value Stack Production Evaluation stack 1. L E nl print( top() ) 2. E E 1 + T push( pop() + pop() ) 3. E T 4. T T 1 * F push( pop() * pop() ) 5. T F 6. F digit push (digit) L.du = 19 E.v = 19 3 * 5 + 4 nl E 1.v = 15 + T.v = 4 T.v = 15 F.v = 4 T 1.v = 3 * F.v = 5 d.v = 4 F.v = 3 d.v = 3 d.v = 5 Input State Value Production 3*5+4 nl - - *5+4 nl 3 *5+4 nl F 3 F digit *5+4 nl T 3 T F 5+4 nl T* 3 +4 nl T*5 3 +4 nl T*F 3 5 F digit +4 nl T 15 T T * F +4 nl E 15 E T 4 nl E+ 15 nl E+4 15 nl E+F 15 4 F digit nl E+T T F nl E 19 E E + T E nl 19 L 19 print Laszlo Böszörmenyi Compilers Attributed Grammars - 8
Inherited Attributes - Example Express well context sensitivity Nodes inherit from father + siblings Example type declaration T.type is synthesized L.in is inherited T.type=real real id 1, id 2, id 3 D L.in=real Production Semantic Rule 1. D T L L.in := T.type 2. T int T.type := integer real L.in=real L.in=real,, id id id 3.type=real 3. T real T.type := real 4. L L 1, id L 1. in := L.in addtype (id.entry, L.in) 5. L id addtype(id.entry, L.in) id id 1.type=real id 2.type=real Laszlo Böszörmenyi Compilers Attributed Grammars - 9
L-attributed Definitions Edges in the dep. graph only from left to right ( ) Attributes must be either 1. Synthesized, or 2. Inherited, depending only from father and left siblings In any A X 1, X 2,... X n the attribute X i.a may use only a) Inherited attributes associated with A b) Inherited and synthesized attributes associated with X 1, X 2,... X i-1 dfvisit (node n) { for each (child m of n, from the left) { Compute the inherited attributes of m; dfvisit (m); } Compute the synthesized attributes of n n m 1 m 2 m 3 m 21 m 22 m 23 Laszlo Böszörmenyi Compilers Attributed Grammars - 10
L-attributed - example Inherited attributes can also help if syntax and parse tree do not match Example, non-left-recursive grammar for expressions like 3 * 5 T.v = 15 Production Semantic Rule 1. T FT T.inh:= F.val T.val := T.syn 2. T *FT 1 T 1.inh:= T.inh * F.val T.syn:= T 1.syn 3. T ε T.syn:= T 1.inh 4. F digit F.val:= digit.lexval F.v = 3 d.v = 3 T.inh = 3 T.syn = 15 * F.v = 5 T 1.inh = 15 T.syn = 15 d.v = 5 ε Laszlo Böszörmenyi Compilers Attributed Grammars - 11
Syntax Trees Syntax trees are compact parse trees Intermediate nodes represent programming constructs (e.g. operators for an expression grammar) Chains are merged L.v = 19 E.v = 19 nl 3 * 5 + 4 + E 1.v = 15 + T.v = 4 T.v = 15 F.v = 4 T 1.v = 3 * F.v = 5 d.v = 4 * 3 5 4 F.v = 3 d.v = 5 d.v = 3 Laszlo Böszörmenyi Compilers Attributed Grammars - 12
Constructing a syntax tree - example Help functions 1. mknode (op, left, right) creates node for an operator 2. mkleaf (id, entry) creates node for an identifier 3. mkleaf (num, val) creates node for a number Tree is built bottom-up Production Semantic Rule E E 1 + T E.nptr := mknode( +, E 1.nptr, T.nptr) a 4 + c + E E 1 T E.nptr := mknode( -, E 1.nptr, T.nptr) E T E.nptr := T.nptr - id T (E) T id T.nptr := E.nptr T.nptr := mkleaf(id, id.entry) id n c T num T.nptr := mkleaf(num, num.val) a 4 Laszlo Böszörmenyi Compilers Attributed Grammars - 13
Laszlo Böszörmenyi Compilers Attributed Grammars - 14 Syntax DAGs Common expressions computed only once Expressions must be free of side effect Example: a + a * (b c) + (b c) * d + + a * * a - b c - b c d + + * * a - b c d
Translation Schemes A translation scheme is a cfg + semantic actions The order is explicitly specified Actions are put in { } at the place of execution Example infix postfix conversion 9-5+2 95-2+ E T R R addop T { print(addop.lexeme) } R 1 R ε T num { print (num.val)} T E 9 pr(9) - 5 R T pr(-) pr(5) + 2 R T pr(+) pr(2) R ε Laszlo Böszörmenyi Compilers Attributed Grammars - 15
Proper Placing of Semantic Actions For synthesized attributes always the right end E.g. T T 1 * F {T.val:= T 1.val * F.val} If also inherited attributes available 1. Inherited attributes of symbol X on the right side of a production must be evaluated before the actions of X 2. Synthesized attributes of a symbol to the right must not be used 3. Synthesized attributes of a left-hand non-terminal must be computed at last action quite right Example S A 1 {A 1.inh:= 1; A 2.inh:= 2; } A 2 A a {print(a.inh)} S {A 1.inh:= 1} A 1 {A 2.inh:= 2; } A 2 WRONG CORRECT a S A 1 A 2 pr(?) a Laszlo Böszörmenyi Compilers Attributed Grammars - 16
Left-recursion and Semantic Actions (1) Let be X.x, Y.y synthesized attributes A A 1 Y { A.a := g(a 1.a, Y.y) } A X { A.a := f(x.x) } Elimination of the left-recursion (as known) Let be R the new non-terminal for right-recursion A XR R YR ε Including semantic actions A X { R.i:= f(x.x) } R { A.a:= R.s } R Y{ R 1.i:= g(r.i, Y.y) } R 1 { R.s:= R 1.s } R ε { R.s := R.i } Laszlo Böszörmenyi Compilers Attributed Grammars - 17
Left-recursion and Semantic Actions (2) Let be A X Y Z a sentential form A.a, X.x, Y.y and Z.z are synthesized and computed bottom-up R.i is inherited and computed at descending R.s is synthesized and used only to pass the value upwards A.a:= R.s A.a:= h(g(f(x.x)), Y.y), Z.z) X R.i:= f(x.x) A.a:= g(f(x.x), Y.y) Z Y R.i:= g(f(x.x), Y.y) A.a:= f(x.x) Y X A X {A.a:= f(x.x)} A A Y {A.a:= g(f(x.x), Y.y)} A A Z {A.a:= h(g(f(x.x), Y.y), Z.z)} Z R.i:= h(g(f(x.x), Y.y), Z.z) ε Laszlo Böszörmenyi Compilers Attributed Grammars - 18
Left-recursion and Actions - Example E E 1 + T {E.v:= E 1.v + T.v} E E 1 - T {E.v:= E 1.v - T.v} E T {E.v:= T.v} T (E) {T.v:= E.v} T num {T.v:= num.v} Without left-recursion E T {R.i:= T.v} R {E.v:= R.s} R +T {R 1.i:= R.i +T.v} R 1 {R.s:= R 1.s} R -T {R 1.i:= R.i - T.v} R 1 {R.s:= R 1.s} R ε {R.s:= R.i} T (E) {T.v:= E.v} T num {T.v:= num.v} E.v:= 6 T.v:=9 R.i:= T.v = 9 9-6 T.v:= 5 5 + 9 5 + 2 R 1.i:= R.i-T.v = 4 4 6 T.v:= 2 R 1.i:= R.i+T.v 2 R.s:= R.i 6 ε The inherited attributes are computed at descending The computed values are copied into a synthesized attribute at ascending 6 Laszlo Böszörmenyi Compilers Attributed Grammars - 19
Creating a syntax tree from a tr. scheme E.n a-4+c R E T {R.i:= T.nptr} R {E.nptr:= R.s} R addop T {R 1.i:= T.n - T.n R.i mknode(addop, R.i, T.nptr} R 1 {R.s:= R 1.s} + + T.n R.i R ε {R.s:= R.i} T (E) {T.nptr:= E.nptr} - id ε T id {T. nptr:= mkleaf(id, id.v} T num {T. nptr:= mkleaf(num, num.v} id n c a 4 Laszlo Böszörmenyi Compilers Attributed Grammars - 20
Predictive Parser with Attributes 1. non-terminal A, create a function, using The inherited attributes as parameters The synthesized attributes as return value 2. The code for each production does a) If X V T X.x is a synthesized attribute: Store X.x and continue to read the input b) If B V N then create a function call c:= B(b 1, b 2, b k ), where b i are the variables storing the inherited attributes of B and c is the synthesized attribute of B c) In the case of actions Replace the references to attributes by references to the corresponding variables Laszlo Böszörmenyi Compilers Attributed Grammars - 21
Predictive Parser with Attributes - Example PROCEDURE R(i : Node) : Node = CONST AddOp = SET OF Scanner.Symbol {plus, minus}; VAR addop: Scanner.Symbol; BEGIN IF sym IN AddOp THEN (* R addop TR *) addop := sym; Scanner.GetSym(); RETURN R(mknode(addop, i, T())); ELSE (* R ε *) RETURN i; END; (*IF sym*) END R; R addop T {R 1.i:= mknode(addop, R.i, T.nptr)} R 1 {R.s:= R 1.i} R ε {R.s:= R.i} PROCEDURE T(): Node = BEGIN... END T; (*No inh. attr; no parameters*) Laszlo Böszörmenyi Compilers Attributed Grammars - 22