Attribute Grammars

An attribute grammar is a stylized presentation of rules for calculating properties by tree walks. Syntactic categories are decorated with attributes; and grammar productions are decorated with rules. A rule describes relations that hold among attributes of parse-tree nodes involved in a reduction according to the rule.

Example 1

This attribute grammar performs type checking and type inference for a language that supports declaration, assignment and the addition operator.

A .type attribute encodes data type. This grammar allows for both declaration and type inference.

   dcl
      int dlist          dlist.t = int
      real dlist         dlist.t = real
   dlist
      ident              ident.t := dlist.t
      dlist , ident      dlist1.t := dlist0.t 
                         ident.t := dlist.t
   assign
      var := expr        var.t = expr.t
   var
      ident              var.t = ident.t
   expr
      const              expr.t = const.t          
      sum                expr.t = sum.t
   sum
      var                sum.t = var.t
      sum + var          sum0.t = sum1.t = var.t
   const
      num                const.t = int
      num . num          const.t = real
The relation may be calculated by a fixed-point iteration:

Guiding the calculation

The language specification may require the relation to be calculated in a particular order. For example, it is customary to infer the type of a variable from the type of an expression assigned to it, but not vice versa. This may be written.
   dcl
      int dlist          dlist.t := int
      real dlist         dlist.t := real
   dlist
      ident              ident.t := dlist.t
      dlist , ident      dlist1.t := dlist0.t 
                         ident.t := dlist.t
   assign
      var := expr        var.t := expr.t
In a language that requires full declaration, types must originate from declarations. Then at assignment we would merely check the relation among types, but not propagate types:
   assign
      var := expr        var.t == expr.t
The check might be made only when both sides are defined. Undefined values left when the iteration stops would be errors.

Often the direction of propagation is fully specified: every rule involves := or ==. It is usually required that the dependency graph implied by := rules be free of loops: each value gets set at most once during the fixed-point iteration.

Example 2

This attribute grammar expresses syntax-directed translation to convert infix operations to postfix.
   sum
      term            sum.code := term.code
      sum - term      sum0.code := sum1.code ++ term.code ++ "-"
   term
      var             term.code := var.code
      term * var      term0.code := term1.code ++ var.code ++ "*"
   var 
      ident           var.code := ident.name
Wiith a bit of cleverness we can even get a left-associative translation out of a right-associative parse that was employed to avoid left recursion. Code for a running total up to but not including the tail of an expression like a-b-c-d is accumulated in an inherited attribute .cum:
   sum
      term            sum.code := term.code
      term - tail     tail.cum := term.code;
   tail
       term           tail.code := tail.cum ++ term.code ++ "-"
       term - tail    tail1.cum := tail0.cum ++ term.code ++ "-"
The completed code for the sum resides in tail.code at the end of a chain of tail nodes. To propagate it back to sum we add the rules shown in boldface:
   sum
      term            sum.code := term.code
      term - tail     tail.cum := term.code
                      sum.code := tail.code;
   tail
       term           tail.code := tail.cum ++ term.code ++ "-"
       term - tail    tail1.cum := tail0.cum ++ term.code ++ "-";
                      tail0.code := tail1.code

Synthesized and inherited attributes

Attributes that are calculated at parent nodes from children are said to be synthesized. Synthesized attributes can be calculated by a postorder tree walk.

Attributes calculated at a child from its parent and siblings are said to be inherited. Inherited attributes with no dependency on siblings may be computed by a preorder tree walk.

Inheritance from a sibling can be reduced to inheritance from the parent by copying the sibling attribute to a dummy synthesized attribute in the parent.

In Example 2, .code is synthesized; .cum is inherited. As .code depends on .cum (by the last boldface rule), .cum must be computed first. However, the calculation may all be done in one mixed tree walk, with .cum computed on the way down and .code on the way back. In fact, the calculation can be combined with parsing.

Scheduling the calculations to respect dependencies can be tricky. The issue can be finessed, though, by entrusting the scheduling to lazy evaluation.