100 likes | 203 Views
SDTs used to implement SDDs. A non-cyclic SDD (having definitions of attributes) can always be implemented by a SDT (having actions that assign values to attributes) by arranging that the parser first builds a tree, ignoring the actions of the SDT
E N D
SDTs used to implement SDDs • A non-cyclic SDD (having definitions of attributes) can always be implemented by a SDT (having actions that assign values to attributes) by arranging that • the parser first builds a tree, ignoring the actions of the SDT • the tree is then walked, executing the actions of the SDT • Two interesting cases where SDDs may be implemented by SDTs whose actions can be executed during parsing, in 1 pass, without a parse tree being constructed • S-attributed SDD and parsing is LR (deterministic bottom-up) • this is the simplest case • all actions can be grouped at the end of a production • result is a “postfix SDT” • L-attributed SDD and parsing can be LL (deterministic top-down) • if a grammar can be parsed LL, it can also be parsed LR http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
Postfix SDTs • If grammar can be parsed deterministically bottom-up, & all attributes “synthetic” • Then all actions of a production can be executed along with the parser’s reduce step • They can all be grouped at the end of the production, forming a postfix to it e.g. Postfix SDT for very-simple desk calculator L ::= E \n E ::= E1 + T E ::= T T ::= T1 * F T ::= F F ::= ( E ) F ::= digit { print(E.val) } { E.val=E1.val+T.val } { E.val=T.val } { T.val=T1.val*F.val } { T.val = F.val } { F.val=E.val } { F.val=digit.lexval } all attributes are synthetic, all actions occur at end of production and from the parser’s point of view they are part of the production, the grammar is LR(1). http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
Implemention issues with postfix SDTs • Attributes can be stored in records/structs/dictionaries on the symbol stack, alongside LR parser states on the state stack. • Furthermore the grammar symbol itself need not be kept on the symbol stack • If some attribute values have unbounded size – e.g. for strings or code lists – it is best to store fixed-size pointers in the stack structures, and store the actual values elsewhere • When a parser reduce occur – say for T ::= T1 * F – the stack records containing needed values (T1’s for T1.val, F’s for F.val) will be at known offsets (-1, 0) relative to the top of stack. The result can be left on top of the stack after reduce step. • With single productions, e.g. T ::= F, no change to the topmost stack entry is necessary since the item at the top must stay at the top. (but top state will usually change) http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
Parser stack manipulations made explicit L ::= E \n E ::= E1 + T E ::= T T ::= T1 * F T ::= F F ::= ( E ) F ::= digit { print (stack[top-1].val; top=top-1; } { stack[top-2].val=stack[top-2].val+stack[top].val; top=top-2; } { } stack[top-2].val=stack[top-2].val*stack[top].val; top=top-2; } { } { stack[top-2].val=stack[top-1].val; top=top-2; } { } // if we may assume that val and lexval attributes // occupy the same locations in a struct effect is to place 9 into the existing E element’s .val and make top point to it T: T.val=3 * E: E.val=6 top … … … … … … … http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
SDTs with actions inside productions - 1 • Where SDD is not S-attributed (it may be L-attributed or worse) then no postfix SDT can be constructed: actions inside productions will be necessary productions have the form X ::= a {a} b where b is not e • If such a SDT cannot be handled during parsing – because it is worse than L-attributed while still being acyclic – then • parse, ignoring actions, and producing an explicit parse tree • then, knowing what productions have been used to parse, add extra child nodes for the particular actions within those productions • perform preorder traversal of tree, executing the actions X X a a b b {a} http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
SDTs with actions inside productions - 2 • If such a SDT can be handled during parsing, then • if parsing bottom-up, treat the action like a non-terminal: • invent a unique marker non-terminal for each action • such a non-terminal has one empty production • perform the action when that non-terminal is on the top of the stack • if parsing top-down • perform the action just before processing the next grammar symbol • checking the next terminal, if b begins with a terminal • expanding the next non-terminal, if b begins with a non-terminal X ::= a M39b M39 ::= e http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
Eliminating left recursion from SDTs • Grammars, and hence SDTs, cannot be used for top-down parsing if left recursive • Treat actions like terminals, since order of terminals is preserved by left recursion elimination • (although if any production begins with an action, the grammar with SDT will not be possible to parse deterministically either top-down or even bottom-up, and it will then be necessary to walk a tree) • General idea of left recursion elimination (without actions) is to replace left recursion by right recursion involving a new non-terminal capable of generating e A ::= A a | b A ::= b R R ::= a R | e • With actions {a} and {b} in SDD, similar transformation requires care A ::= A a {a} | b {b} A ::= b {?1} R {?2} R ::= a {?3} R {?4} | e {?5} http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
… the right-recursive version should achieve same result Example left-recursive original SDT with only one S-attribute ‘q’ A ::= A1 b { A.q=f(A1.q, b.q);} | c { A.q=g(c);} A For sample input ‘cbb’, flow of information is always upwards A.q=f(f(g(c),b.q),b.q) A b A.q=f(g(c),b.q) A b After eliminating left recursion, the shape of the parse tree will be radically different, but the end result calculated for topmost A.q should be the same A.q=g(c) c http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
… needing inherited attributes A A A.q=R.s R.i=g(c) A.q=f(f(g(c),b.q),b.q) A b A.q=f(g(c),b.q) c R R.s=R1.s R1.i=f(g(c),b.q) A b A.q=g(c) b R R.s=R1.s R1.i=f(f(g(c),b.q),b.q) with left recursive grammar c b R R.s=R.i Only at this point can it be known that the correct result is f(f(g(c),b.q),b.q) e http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction
… and appropriate evaluation order • Getting the actions done in the right order and with the right information available requires use of inherited attributes • values of inherited attributes must be calculated immediately before the right-recursive use of the non-terminal R • Getting the result(s) back to the top node involves a new synthesized attribute(s) • values of synthesized attributes may be calculated right at the ends of productions, just as with postfix SDTs A ::= c {R.i=g(c)} R {A.q=R.s} R ::= b {R1.i=f(R.i, b.q)} R {R.s=R1.s} R ::= e {R.s=R.i} http://csiweb.ucd.ie/staff/acater/comp30330.html Compiler Construction