• No results found

Restrictions imposed by the analysis

5.4 Overview of the analysis

5.4.1 Restrictions imposed by the analysis

The analysis has two separate but related phases:

1. We ensure certain exports relationships exist for each declaration. This is suffi- cient to ensure that all synthesized equations are present (and that no duplicate equations exist) using only local checks.

2. We examine flow types for the host (ftH

nt) and extension (ft E

nt), and make sure

certain relationships exist between them. This is sufficient to ensure that we only need local checks for all necessary inherited attribute equations.

The first phase is similar to a requirement in Haskell for instances of typeclasses, called the orphaned instances rule, and so we call these orphaned declarations. The purpose of this sort of rule is to ensure no duplicates could possibly exist. Suppose we have three separate modules: N which declares a nonterminal, A which declares an attribute, and O which declares an occurrence of this attribute on the nonterminal. To write this declaration, O must import both N and A. We wish to ensure there could never be duplicate occurrences, so we require that exports({N, A}, O). (Recall that while exports is a relation on modules, we take the notational liberty of applying it to declarations, meaning the modules containing those declarations.) In other words, one of N or A (or both) must export O. In order to write another occurrence besides the one in O, we must import both modules, and so as long as one of them exports the existing O, a local check is sufficient to globally forbid duplicate declarations.

We repeat this rule for equations. Suppose we have the modules O, P which declares a production, and E which declares an equation for this attribute on this production. We likewise require exports({P, O}, E). Thus we can forbid duplicate

5.4. Overview of the analysis A O P N E imports exports or or or production is forwarding

Figure 5.5: A summary of the import/export relationships required between the modules that contain related attribute grammar declarations (nonterminal, attributes, productions, occurs, and equations.)

equations.

Finally, a slight variation of this rule for productions. A production constructs a particular nonterminal, and so must import N (which we assume is the LHS nonter- minal for the production in P .) Either it must be the case that exports(N, P ), or the production must be forwarding. As a result, we have a fixed set of non-forwarding productions for each nonterminal, determined by N . Any extension production must (as a result) be a forwarding one.

These restrictions are summarized visually in the graph in figure 5.5. Remem- ber that actual structure can always be simpler than we depict here, because these modules can always be the same module. Since a module always exports itself, and our restrictions are always about lack of an exports relationship, a single module importing no other module can never run afoul of our restrictions. (In fact, this will remain true even as we introduce additional restrictions later.) As a result, a clas- sically well-defined host language consisting of only one module is always modularly well-defined.

5.4. Overview of the analysis

There is another important property these restrictions accomplish. The O module must import N , which must export all P containing non-forwarding productions. Because all equations E must be exported by P or O, we are able to see (from O) all synthesized attribute equations that must be present for that attribute on that nonterminal. As a result, checking for the presence of equations locally from O is sufficient to ensure completeness of synthesized equations. Further, this also allows us to implement pattern matching exhaustiveness checking normally, as the modified pattern matching semantics means we need only cover non-forwarding productions.

The second phase of our analysis involves computing flow types. One way of thinking about this is to perform flow type computations for each module. For each nonterminal nt being imported, we compute ftH

nt on the set of imported modules,

and ftE

nt again now with the extension module now included. (We impose no flow

restrictions on those nonterminals declared in the extension module, only those that are imported.) We have several requirements on the values we compute for these.

First, for every synthesized attribute s that occurs on nt in H, it must be the case that ftH

nt(s) = ft E

nt(s). It’s not possible for extensions to remove things from the

flow type, and it must also be the case that extensions do not add to the flow type of an attribute occurrence from the host language. As a result of this restriction, we always know what inherited attributes every synthesized attribute access may need.

When we return to look at the var extension of figure 5.3, and the flow type computed for it in figure 5.4, we can see a violation of this restriction. Originally, we would have computed ftExpr(fwd) = ∅, but the var extension has introduced a

new dependency on env (and for the other two synthesized attributes as well.) Other extensions cannot necessary know of this new depedency, and as a result may not have an equation for it. Thus, this extension is not modularly well-defined (despite

5.4. Overview of the analysis

appearing well-defined in isolation,) and this latent problem is exposed by the iff extension again shown in figure 5.3.

This restriction is almost enough to ensure that all necessary inherited equations are present, but there’s a little more we need. In addition to host language synthesized attributes, we repeat this requirement for the forward dependencies we are tracking. That is, for every nt in H, ftH

nt(fwd) = ft E

nt(fwd). This is necessary to ensure that

extensions do not use new inherited attributes in their forward equation, and as a result cause those to pollute the dependencies of synthesized attributes through forwarding’s implicit equations.

Note that we are only talking about nonterminal and occurrence declarations from the imported module H. New nonterminals and synthesized occurrences introduced by the extension are not subject to the above restrictions (except when writing ex- tensions to extensions, of course.) However, there is one restriction that does apply to new synthesized occurrences.

It must be the case that for each new occurrence of a synthesized attribute s in the extension on a nonterminal nt from H, that ftH

nt(fwd) ⊆ ft E

nt(s). This one is

slightly subtle. Consider two independent extensions, both introducing forwarding productions with equations that make full use of their allowed dependencies. Suppose one introduced a synthesized attribute that violated the above rule. For example,

ftnt(s) =∅ while ftnt(fwd) ={env}. We would now suffer a missing inherited equation

if we attempt to access this attribute (which claims to require no inherited attributes) from a tree rooted in the forwarding production from the independent extension. We need that inherited attribute to compute the forward tree, which would then have the equations that give a value to this attribute. Thus, this minimum requirement for the dependencies of extension attributes on host nonterminals.

5.4. Overview of the analysis NA nonterminal A; NB nonterminal B; AS syn S :: T; AI inh I :: T; OS attr S occurs on B; OI attr I occurs on B; PA production pa l::A ::= r::B { EI r.I = ...; cloud ... r.s ... } PB production pb l::B ::= { ES l.s = l.i; } AI OI PA EI NB or or NA A::=B fwds AS OS PB ES B::= r.i= l.s=l.i or or ?=...r.s... import edge necessary due to 'l.i' reference

Figure 5.6: A worst-case module imports/exports diagram, showing the relationship be- tween an access of a synthesized attribute and the required location of an inherited equation it may need to exist. On the left: Ag code that, when exploded into multiple modules, would give rise to this graph.