This directory contains the code generator plugin, which is run when you
do zap compile -o haskell. This document provides a high-level
overview of its internals.
Like all zap code generator plugins, it accepts a
zap-serialized CodeGenratorRequest (defined in schema.zap)
on standard input, and emits code accordingly. For an overview on what
exactly is emitted, see Zap.Tutorial.
The data read from standard input transitions through several
intermediate forms before finally being written out to disk as Haskell
source code. The IR/ subdirectory defines the intermediate forms
themselves, while each transition is defined in a module under Trans/.
Main ties ties the whole thing together.
The Pipeline
CGR ---> Stage1 ---> Flat ---> NEW ---> Haskell (IR) --> Haskell (source code)
The flow is as follows:
- First, we read in the data from standard in, and parse it as a
CodeGeneratorRequest(henceforthCGR). We use the high-level API for this. - Next, we translate to the IR defined in
IR.Stage1. This represents essentially the same information as the CGR, but:- Information we won't use has been discarded.
- It is more type safe, in that the structure of the IR cannot represent certain illegal structures that are representable in the CGR.
- Whereas the CGR contains a list of nodes by id, and entries in the CGR reference each other by ID, in the Stage1 IR we have tied the knot, and the referenced nodes are available directly as fields of their referers.
- Then, we translate to the
FlatIR. This is still a declarative representation of the information in the schema, but it is in a form that maps more nicely to Haskell. The goal of this stage is to massage the information into a form such that later stages don't have to do any work to find out basic facts about the schema. For example:- We have flattened the namespace, all definitions are at top level;
Haskell doesn't support nested namespaces, so this solves an
impedence mismatch. The hierarchical structure is recoverable, as
the names themselves encode the structure; see
IR.Name. - Unions are now represented more cleanly as sums, rather than being mixed in with other fields.
- Interfaces now contain a full list of their ancestors, not just immediate superclasses. This is useful when genrating instance declarations later on.
- We have flattened the namespace, all definitions are at top level;
Haskell doesn't support nested namespaces, so this solves an
impedence mismatch. The hierarchical structure is recoverable, as
the names themselves encode the structure; see
- Next, we translate to the
AbstractOpIR. This form encodes the Haskell structures to be generated, rather than what is declared in the schema -- but the it is still abstract in the sense that it only specifies what constructs should be generated. Thus, Abstract and Operational. Examples:- Type declaration for this zap type
- An instance for 'Parse' for this type
- etc
- Then, we translate this form into a Haskell AST, defined in
IR.Haskell. We use our own AST instead of an off-the-shelf library for a couple reasons:- We want to eventually insert comments from the schema into the code as Haddock comments, and the libraries I(zenhack) was able to find do not support comments.
- The full Haskell syntax is quite complicated, and we don't need most of it; our AST has just the parts we need, and so is simpler to work with.
- It is useful when generating code to be able treat certain
constructs as primitive syntax which really aren't. For example:
- Our AST does not reflect the fact that function application is curried;
- Our expression type treats "functorial" application, e.g.
f <$> x <*> y <*> z, as primitive, which is convienent for our purposes.
- Finally, we translate the Haskell AST into text, and write it out to disk.
Misc notes
-
There are a couple modules that are shared between more than one phase and/or IR:
-
IR.NameandIR.Commonare used by multiple of the IRs. -
Trans.ToHaskellCommonused to be used by multiple modules. It should be folded intoTrans.AbstractOpToHaskell. -
The names of some of the IRs are not very good.
IR.Stage1latter is not terribly descriptive.IR.Flatreflects an out of date notion of what the flat IR's purpose is (it does more than just flatten the namespace).