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 = realThe relation may be calculated by a fixed-point iteration:
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.tIn 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.tThe 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.
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.nameWiith 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
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.