• No results found

Related work

4.6 Related work

In JastAdd [40] and Kiama[9], trees are represented as objects and attribute eval- uation mutates the tree effectfully (either directly as in JastAdd or indirectly via memoization as in Kiama.) As a result, both of these languages lack a type distinc- tion between the two kinds of trees. Instead, the user must remember to invoke a special copy method, analogous to our new expression, wherever a new undecorated tree is needed. These copy methods do not change the type of the tree, as our new operation does, resulting in a lack of the type safety that we have here. We avoid having to write new often by leveraging the type distinction to disambiguate, making our safety gains essentially “free.”

UUAG[41] does not appear to support reference attributes, and so the type dis- tinction is not exposed. In functional embeddings these type distinctions do occur naturally but typically having a different (internal, generated) name for the decorated view of the tree. AspectAG [10] is a sophisticated embedding into Haskell that nat- urally maintains the type safety we seek but at some loss of notational convenience. It also requires a fair amount of “type-level” programming that is less direct than the Silver specifications, and the error messages generated can be opaque.

Kiama and UUAG, by virtue of their embedding in functional languages, do sup- port parameterized nonterminals and attributes. UUAG side-steps the attribute ac- cess problem of section 4.3.2 by simply not having reference attributes. As a result, attribute accesses expressions have only names on their left-hand side, not arbitrary expressions. As a result, all necessary information can be obtained by looking up the name, obtaining the explicit type from the declaration.

4.6. Related work

could be easily extended to. Both UUAG and Kiama also support pattern matching on nonterminals. In UUAG, this is only supported for undecorated trees, and its behavior is identical to ordinary pattern matching in Haskell. In Kiama, pattern matching can extract decorated children from a production. But in both cases, use of pattern matching may compromise the extensibility of the specification, as new match rules cannot be added modularly.

Chapter 5

Modular well-definedness analysis

This chapter will present an analysis that ensures the well-definedness of an attribute grammar in a modular way. Traditional well-definedness (previously introduced in section 3.1.6) ensures that the attribute grammar is complete (no equations are miss- ing) and non-circular (results can be computed for each attribute). However, the traditional approach is whole-program, and thus non-modular, meaning the well- definedness of a module may change in response to the introduction of more modules to the whole program. Thus, the well-definedness of even the host language may be at risk from the introduction of an extension (never mind how extensions may affect each other).

Resolving this problem is one of the two critical contributions of this thesis needed to ensure that the result of automatically composing language extensions will be sensible. This analysis ensures that there will be no missing or duplicate equations for synthesized or inherited attributes in a compiler composed of any set of extensions that satisfy the analysis. To accomplish this, we place modest restrictions on what extensions are allowed to do, and use these properties to show classical attribute grammar well-definedness.

This chapter is based on our previously published paper “Modular well-definedness analysis for attribute grammars”[77]. We have expanded this chapter significantly to be more precise about how the analysis works on the language Ag of the previous

5.1. Modules

chapter.

We begin by introducing modules to Ag in section 5.1. Following that, we will discuss the concept of a modular analysis in section 5.2, specifically in contrast to the whole-program notion of the well-definedness analysis of attribute grammars. Finally, we will introduce (section 5.3) the notions of effective completeness and flow types, both essential to describing our goals and the analysis that will follow in this chapter. These sections constitute the background for the remainder of the chapter.

In section 5.4, we give an overview of how the analysis will work, describing the restrictions we will impose (in high-level terms) and how the accomplish our goals. Following that, in section 5.5, we describe in detail an algorithm for inferring flow types. (If the reader wishes to view flow types as declared rather than inferred, they can read subsections 5.5.1 and 5.5.3 describing flow information before skipping to the next section.) Finally, in section 5.6, we describe how to use the computed flow types to check for violations of the restrictions we introduced in the overview.

Concluding the chapter, we perform a small self-evaluation of the restrictions by applying the analysis to the Silver compiler itself in section 5.7. Following that, we discuss the approach and some of the concerns with extending this analysis to include non-circularity in section 5.8. And we conclude with some discussion of closely related work in section 5.9.

5.1 Modules

In chapter 4, we introduced the language Ag (figure 4.1), a subset of the Silver programming language based on attribute grammars. This language covered attribute grammar declarations (D), equations, expressions, and types. We will now augment

5.1. Modules

module (or “grammar”) G ::= grammar ng; M D

module statements

M ::= imports ng; | exports ng;

Figure 5.1: The module language for Ag

it with a simple module language in figure 5.1.

A module (G) (in Silver referred to as a “grammar”) consists of a declaration of its name (ng), followed by some module statements, followed by the Ag declarations that

make up its body. The module statements indicate the dependencies of this module. The direct imports of a module are exactly those listed within the module using imports. Likewise, the direct exports of a module are those listed using exports.

An import indicates that this module requires another, and that other module’s declarations will be visible to this modules implementation (D). An export further indicates that any module which imports this one will also need to see the declarations from another module. As a result, if module A imports module B and module B exports C, then A will also see C’s declarations.

The set of modules exported by a module is the transitive closure over the export relationships of that initial module. That is, if we import module A, then we start with the set {A} and iteratively expand this set based on the direct exports of any member of that set, until there are no more additions. This final set of modules is the full set of modules that are actually imported as a result. Thus, the set of dependencies that make up a module’s normal environment follows that module’s direct imports (i.e. non-transitively) and then transitively closes of all those module’s exports. (Where the transitive dependencies of a module are those we obtain by closing over both imports and exports transitively.)

5.1. Modules E2 HB E1 HA L imports exports extension host language

Figure 5.2: An example of a complicated module breakdown for a hypothetical host language and two extensions.

A host language might consist of multiple modules, and an extension may as well. In figure 5.2, we depict the host language as consisting of two modules (HA

and HB). One of the extensions (E2) consists of just one module. The other (E1)

extends the host language in addition to making use of another separate language (L). The extensions must depend on the host language somehow, but the host must not depend upon the extensions.

In this chapter, we will make a more fine-grained use of the module dependency relationships than just “host” and “extension.” We will actually make restrictions at the module level. We will consider every module to be an “extension,” to all the modules it imports, unless the imported module exports it back. In the figure, HA is

an extension to nothing (HB exports it back), but HB will be treated as an extension

to HA for our purposes. Likewise, the E1 module extends both HA and L.

We have chosen to include an exports relationship in order to make more precise some of the restrictions that will be introduced in this chapter. Strictly speaking we could do without introducing an “exports” relationship between modules, however some of our restrictions will require certain exports relationships to exist between two modules with related declarations. These restrictions could be instead be written