Start Lecture #8

**Remark**: Final comments on FIRST/FOLLOW.

**Homework**: Do the following extension

A → B C B → b B | ε C → c C | ε

Skipped.

We consider **very** briefly two alternatives to SLR,
canonical-LR or LR, and lookahead-LR or LALR.

SLR used the LR(0) items, that is the items used were productions with an embedded dot, but contained no other (lookahead) information. The LR(1) items contain the same productions with embedded dots, but add a second component, which is a terminal (or $). This second component becomes important only when the dot is at the extreme right (indicating that a reduction can be made if the input symbol is in the appropriate FOLLOW set). For LR(1) we do that reduction only if the input symbol is exactly the second component of the item. This finer control of when to perform reductions, enables the parsing of a larger class of languages.

Skipped.

Skipped.

For LALR we merge various LR(1) item sets together, obtaining nearly the LR(0) item sets we used in SLR. LR(1) items have two components, the first, called the core, is a production with a dot; the second a terminal. For LALR we merge all the item sets that have the same cores by combining the 2nd components (thus permitting reductions when any of these terminals is the next input symbol). Thus we obtain the same number of states (item sets) as in SLR since only the cores distinguish item sets.

Unlike SLR, we limit reductions to occurring only for certain specified input symbols. LR(1) gives finer control; it is possible for the LALR merger to have reduce-reduce conflicts when the LR(1) items on which it is based is conflict free.

Although these conflicts are possible, they are rare and the size reduction from LR(1) to LALR is quite large. LALR is the current method of choice for bottom-up, shift-reduce parsing.

Skipped.

Skipped.

Skipped.

Skipped.

Dangling-ElseAmbiguity

Skipped.

Skipped.

The tool corresponding to Lex for parsing is yacc, which (at least originally) stood for yet another compiler compiler. This name is cute but somewhat misleading since yacc (like the previous compiler compilers) does not produce a compiler, just a parser.

The structure of the user input is similar to that for lex, but instead of regular definitions, one includes productions with semantic actions.

There are ways to specify associativity and precedence of operators. It is not done with multiple grammar symbols as in a pure parser, but more like declarations.

Use of Yacc requires a serious session with its manual.

Skipped.

Skipped

Skipped

**Homework:** Read Chapter 5.

Again we are redoing, more formally and completely, things we briefly discussed when breezing over chapter 2.

Recall that a syntax-directed definition (SDD) adds semantic rules
to the productions of a grammar.
For example to the production T → T_{1} / F we might add the rule

T.code = T_{1}.code || F.code || '/'

if we were doing an infix to postfix translator.

Rather than constantly copying ever larger strings to finally
output at the root of the tree after a depth first traversal, we can
perform the output incrementally by embedding semantic actions
within the productions themselves.
The above example becomes

T → T_{1} / F { print '/' }

Since we are generating postfix, the action comes at the end (after
we have generated the subtrees for T_{1} and F, and hence
performed their actions).
In general the actions occur within the production, not necessarily
after the last symbol.

For SDD's we conceptually need to have the entire tree available
after the parse so that we can run the postorder traversal.
(It is postorder since we have a simple S-attributed

SDD; we
will see other orders and times when no order is possible).
Semantic actions can be performed during the parse, without saving
the tree.

Formally, attributes are values (of any type) that are associated with grammar symbols. Write X.a for the attribute a of symbol X. You can think of attributes as fields in a record/struct/object.

Semantic rules (rules for short) are associated with productions.

Terminals can have synthesized attributes; these are given to it by the lexer (not the parser). There are no rules in an SDD giving values to attributes for terminals. Terminals do not have inherited attributes.

A nonterminal A can have both inherited and synthesized attributes. The difference is how they are computed. In either case the computation is specified by a rule associated with the production at a node N of the parse tree.

**Definition**:
A *synthesized* attribute of a nonterminal A, is defined at a
node N (where A is the LHS of the production associated with N).
The attribute can depend only on (synthesized or inherited)
attribute values at the children of N (the RHS of the production)
and on inherited attribute values at N itself.

The arithmetic division example above was synthesized.

Production | Semantic Rules |
---|---|

L → E $ | L.val = E.val |

E → E_{1} + T | E.val = E_{1}.val + T.val |

E → E_{1} - T | E.val = E_{1}.val - T.val |

E → T | E.val = T.val |

T → T_{1} * F | T.val = T_{1}.val * F.val |

T → T_{1} / F | T.val = T_{1}.val / F.val |

T → F | T.val = F.val |

F → ( E ) | F.val = E.val |

F → num | F.val = num.lexval |

**Example**:
The SDD at the right gives a left-recursive grammar for
expressions with an extra nonterminal L added as the start symbol.
The terminal num is given a value by the lexer,
which corresponds to the value stored in the numbers table for lab 2.

Draw the parse tree for 7+6/3 on the board and verify that L.val is 9, the value of the expression.

This example uses only synthesized attributes.

**Definition**: An SDD with only
synthesized attributes is called *S-attributed* and has the
property that the rules give the attribute of the LHS in terms of
attributes of the RHS.

For an S-attributed SDD, all the rules can be evaluated by a single bottom-up (i.e., postorder) traversal of the annotated parse tree.

Inherited attributes are more complicated since the node N of the parse tree with which the attribute is associated (and which is also the natural node to store the value) does not contain the production with the corresponding semantic rule.

**Definition**:
An *inherited* attribute of a
nonterminal B at node N (where B is the LHS) is defined by a
semantic rule of the production at the **parent** of N (where B
occurs in the RHS).
The value depends only on attributes at N, N's siblings, and N's
parent.

Note that when viewed from the parent node P (the site of the semantic rule), the inherited attribute depends on values at P and at P's children (the same as for synthesized attributes). However, and this is crucial, the nonterminal B is the LHS of a child of P and hence the attribute is naturally associated with that child. It is possibly stored there and is shown there in the diagrams below.

We will see examples with inherited attributes soon.

**Definition**: Often the
attributes are just evaluations without side effects.
In such cases we call the SDD an
*attribute grammar*.

If we are given an SDD and a parse tree for a given sentence, we would like to evaluate the annotations at every node. Since, for synthesized annotations parents can depend on children, and for inherited annotations children can depend on parents, there is no guarantee that one can in fact find an order of evaluation. The simplest counterexample is the single production A→B with synthesized attribute A.syn, inherited attribute B.inh, and rules A.syn=B.inh and B.inh=A.syn+1. This means to evaluate A.syn at the parent node we need B.inh at the child and vice versa. Even worse it is very hard to tell, in general, if every sentence has a successful evaluation order.

All this not withstanding we will not have great difficulty because we will not be considering the general case.

Recall that a parse tree has leaves that are terminals and internal
nodes that are non-terminals.
When we decorate the parse tree with attributes, the result is
called an *annotated parse tree*, which is constructed as
follows.
Each internal node corresponds to a production with the symbol
labeling the node the LHS of the production.
If there are no attributes for the LHS in this production, we leave
the node as it was (I don't believe this is a common occurrence).
If there are k attributes for the LHS, we replace the LHS in the
parse tree by k equations.
The LHS of the equation is the attribute and the right hand side is
its value.
Note that the annotated parse tree contains all the information of
the original parse tree since we replaced something like E with
something like E.att=7

.

We computed the values to put in this tree for 7+6/3 and on the right is (7-6).

**Homework**: 1.

Consider the following left-recursive grammar for multiplication of numbers and the parse tree on the right for 3*5*4.

T → T * F T → F F → num

It is easy to see how the values can be propagated up the tree and the expression evaluated.

When doing top-down parsing, we need to avoid left recursion. Consider the grammar below, which is the result of removing the left recursion, and again its parse tree is shown on the right. Try not to look at the semantic rules for the moment.

Production | Semantic Rules | Type |
---|---|---|

T → F T' | T'.lval = F.val | Inherited |

T.val = T'.tval | Synthesized | |

T' → * F T_{1}'
| T'_{1}.lval = T'.lval * F.val | Inherited |

T'.tval = T'_{1}.tval | Synthesized | |

T' → ε | T'.tval = T'.lval | Synthesized |

F → num | F.val = num.lexval | Synthesized |

Now where on the tree should we do the multiplication 3*5? There is no node that has 3 and * and 5 as children. The second production is the one with the * so that is the natural candidate for the multiplication site. Make sure you see that this production (for the * in 3*5) is associated with the blue-highlighted node in the parse tree. The right operand (5) can be obtained from the F that is the middle child of this T'. F gets the value from its child, the number itself; this is an example of the simple synthesized case we have already seen, F.val=num.lexval (see the last semantic rule in the table).

But where is the left operand?
It is located at the **sibling** of T' in the parse
tree, i.e., at the F immediately to T's left.
**This** F is not mentioned in the production associated with the
T' node we are examining.
So, how does T' get F.val from its sibling?
The common parent, in this case T, can get the value from F and then our
node can inherit the value from its parent.

Bingo! ... an inherited attribute.
This can be accomplished by having the following two rules at the
node T.

T.tmp = F.val

T'.lval = T.tmp

Since we have no other use for T.tmp, we combine the above two rules into the first rule in the table.

Now lets look at the second multiplication (3*5)*4, where the parent of T' is another T'. (This is the normal case. When there are n multiplies, n-1 have T' as parent and only one has T).

The pink-highlighted T' is the site for the multiplication. However, it needs as left operand, the product 3*5 that its parent can calculate. So we have the parent (another T' node, the blue one in this case) calculate the product and store it as an attribute of its right child namely the red T'. That is the first rule for T' in the table.

We have now explained the first, third, and last semantic rules. These are enough to calculate the answer. Indeed, if we trace it through, 60 does get evaluated and stored in the bottom right T', the one associated with the ε-production. Our remaining goal is to get the value up to the root where it represents the evaluation of this term T and can be combined with other terms to get the value of a larger expression.

Going up is easy, just synthesize. I named the attribute tval, for term-value. It is generated at the ε-production from the lval attribute (which at this node is not a good name) and propagated back up. At the T node it is called simply val. At the right we see the annotated parse tree for this input.

**Homework**: Extend this SDD to handle the
left-recursive, more complete expression evaluator given earlier in
this section.
Don't forget to eliminate the left recursion first.

It clearly requires some care to write the annotations.

Another question is how does the system figure out the evaluation order, assuming one exists? That is the subject of the next section.

**Remark**:
Consider the identifier table.
The lexer creates it initially, but as the compiler
performs *semantic* analysis and discovers more information
about various identifiers, e.g., type and visibility information,
the table is updated.
One could think of this as some inherited/synthesized attribute pair
that during each phase of analysis is pushed down and back up the
tree.
However, it is not implemented this way; the table is made a global
data structure that is simply updated.
The compiler writer must ensure manually that the updates are
performed in an order respecting any dependences.

The diagram on the right illustrates a great deal. The black dotted lines comprise the parse tree for the multiplication grammar just studied when applied to a single multiplication, e.g. 3*5. Each synthesized attribute is shown in green and is written to the right of the grammar symbol at the node where it is defined. Each inherited attribute is shown in red and is written to the left of the grammar symbol where it is defined.

Each green arrow points to the synthesized attribute calculated from the attribute at the tail of the arrow. These arrows either go up the tree one level or stay at a node. That is because a synthesized attribute can depend only on the node where it is defined and that node's children. The computation of the attribute is associated with the production at the node at its arrowhead. In this example, each synthesized attribute depends on only one other, but that is not required.

Each red arrow points to the inherited attribute calculated from
the attribute at the tail.
Note that two red arrows point to the same attribute.
This indicates that the common attribute at the arrowheads, depends
on both attributes at the tails.
According to the rules for inherited attributes, these arrows either
go down the tree one level, go from a node to a sibling, or stay
within a node.
The computation of the attribute is associated with the production
**at the parent** of the node at the arrowhead.

The graph just drawn is called the *dependency graph*.
In addition to being generally useful in recording the relations
between attributes, it shows the evaluation order(s) that can be
used.
Since the attribute at the head of an arrow depends on the on the
one at the tail, we must evaluate the head attribute after
evaluating the tail attribute.

Thus what we need is to find an evaluation order respecting the arrows. This is called a topological sort. The rule is that the needed ordering can be found if and only if there are no (directed) cycles. The algorithm is simple.

- Choose a node having no incoming edges
- Delete the node and all incident edges.
- Repeat

If the algorithm succeeds in deleting all the nodes, then the deletion order is a suitable evaluation order and there were no directed cycles.

The topological sort algorithm is nondeterministic (**Choose** a
node) and hence there can be many topological sort orders.

**Homework**: 1.

Given an SDD and a parse tree, it is easy to tell (by doing a topological sort) whether a suitable evaluation exists (and to find one).

However, a very difficult problem is, given an SDD, are
there *any* parse trees with cycles in their dependency graphs,
i.e., are there suitable evaluation orders for *all* parse
trees.
Fortunately, there are classes of SDDs for which a suitable
evaluation order is guaranteed.

As mentioned above an SDD is S-attributed if every attribute is synthesized. For these SDDs all attributes are calculated from attribute values at the children since the other possibility, the tail attribute is at the same node, is impossible since the tail attribute must be inherited for such arrows. Thus no cycles are possible and the attributes can be evaluated by a postorder traversal of the parse tree.

Since postorder corresponds to the actions of an LR parser when reducing the body of a production to its head, it is often convenient to evaluate synthesized attributes during an LR parse.

Unfortunately, it is hard to live without inherited attributes. So we define a class that permits certain kinds of inherited attributes.

- Synthesized.
- Inherited
from the left

, and hence the name L-attributed.

Specifically, if the production is A → X_{1}X_{2}...X_{n}, then the inherited attributes for X_{j}can depend only on- Inherited attributes of A, the LHS.
- Any attribute of X
_{1}, ..., X_{j-1}, i.e. only on symbols to the left of X_{j}. - Attributes of X
_{j}, ***BUT*** you must guarantee (separately) that the attributes of X_{j}do not by themselves cause a cycle.

Case 2c must be handled specially whenever it occurs. The top picture to the right illustrates the other cases and suggests why there cannot be any cycles.

The picture immediately to the right corresponds to a fictitious
R-attributed definition.
One reason L-attributed definitions are favored over R, is the left
to right ordering in English.
See the example below on type declarations and also consider the
grammars that result from left recursion.

Production | Semantic Rule |
---|---|

A → B C | B.inh = A.inh C.ihn = A.inh - B.inh + B.syn A.syn = A.inh * B.inh + B.syn - C.inh / C.syn |

B → X | X.inh = something B.syn = B.inh + X.syn |

C → Y | Y.inh = something C.syn = C.inh + Y.syn |

The table on the right shows a very simple grammar with fairly general, L-attributed semantic rules attached. Compare the dependencies with the general case shown in the (red-green) picture of L-attributed SDDs above.

The picture below the table shows the parse tree for the grammar in
the table.
The triangles below B and C represent the parse tree for X and Y.
The dotted and numbered arrow in the picture illustrates the
evaluation order for the attributes; it will be discussed shortly.

The rules for calculating A.syn, B.inh, and C.inh are shown in the
table.
The attribute A.inh would have been set by
the **parent** of A in the tree; the semantic rule
generating A.inh would be given with the production at the parent.
The attributes X.syn and Y.syn are calculated at the children of B
and C respectively.
X.syn can depend of X.inh and on values in the triangle below B;
similarly for Y.syn.

The picture to the right shows

that there is an evaluation
order for L-attributed definitions (again assuming no case 2c).
We just need to follow the arrow and stop at all the numbered
points.
As in the pictures above, red signifies inherited attributes and
green synthetic.
Specifically, the evaluations at the numbered stops are

- A is invoked (viewing the traversal as a program) and is passed its inherited attributes (A.inh in our case, but of course there could be several such attributes), which have been evaluated at its parent.
- B is invoked by A and is given B.inh, which A has
calculated.
In programming terms: A executes

call B(B.inh)

where the argument has been evaluated by A. This argument can depend on A.inh since the parent of A has given A this value. - B calls its first child (in our example X is the only child) and passes to the child its inherited attributes.
- The child returns passing back to B. the synthesized attributes
of the child.
In programming terms: X executes

return X.syn

In reality there could be more synthesized attributes, there could be more children, the children could have children, etc. - B returns to A passing back B.syn, which can depend on B.inh (given to B by A in step 2) and X.syn (given to B by X in the previous step).
- A calls C giving C its inherited attributes, which can depend on A.ihn (given to A, by A's parent), B.inh (previously calculated by A in step 2), and B.syn (given to A by B in step 5).
- C calls its first child, just as B did.
- The child returns to C, just as B's child returned to B.
- C returns to A passing back C.syn, just as B did.
- A returns to its parent passing back A.syn, which can depend on A.inh (given to A by its parent in step 1), B.inh calculated by A in step 2, B.syn (given to A by B in step 5), C.inh (calculated by A in step 6), and C.syn (given to A by C in step 9).

More formally, do a depth first traversal of the tree and evaluate inherited attributes on the way down and synthetic attributes on the way up. This corresponds to a an Euler-tour traversal. It also corresponds to a call graph of a program where actions are taken at each call and each return

The first time you visit a node (on the way down), evaluate its inherited attributes (in programming terms, the parent evaluates the inherited attributes of the children and passes them as arguments to the call). The second time you visit a node (on the way back up), you evaluate the synthesized attributes (in programming terms the child returns the value to the parent).

The key point is that all attributes needed will have already been evaluated. Consider the rightmost child of the root in the diagram on the right.

- Inherited attributes (which are evaluated on the first, i.e.,
downward, pass):
An inherited attribute depends only on inherited attributes from
the parent and on (inherited or synthesized) attributes from
left siblings.
- The parent will have already been evaluated on the downward pass before the current child so the parent's inherited attributes will have already been evaluated.
- The left children have already had
**both**passes done so all their attributes will have been evaluated.

- Synthesized attributes (which are evaluated on the second,
i.e., upward pass):
A synthesized attribute depends only on (inherited or
synthesized) attributes of its children and on its own inherited
attributes.
- The children have already had both passes so all their attributes have been evaluated.
- The node itself has already had its first (downward) pass so has had its inherited attributes evaluated.

**Homework**: 3(a-c).