One declaration, five stages, one file
Everything moonast does happens in this order. Pick a stage to see the call it stands for and what it hands to the next one.
The declaration is annotated
The annotation is the whole interface. It sits above a declaration you already wrote, names the generator that should run, and changes nothing else about the code it annotates.
#demo.describe
pub struct Point {
x : Int
y : Int
}
Each stage only knows what the one before it gave
A stage is a package, and a package is usable alone. That is what keeps a generator
small: gen never opens a file, quote never reads a
declaration, attr never sees a template. The only package that touches the
disk is cli.
It also decides where a mistake is caught. A template that is not MoonBit fails in
quote, before anything is written. An annotation argument of the wrong
kind fails in attr, with the annotation's line and column. An input that
is not MoonBit fails in gen, naming the file.
A generator that refuses leaves the old file where it was. Half a file is never written.
| Stage | Sees | Gives | Refuses when |
|---|---|---|---|
gen.read | a file name and its text | Array[Decl] | the text is not MoonBit |
attr.bind | an annotation and a spec | Args | a key is missing, unknown, mistyped or repeated |
quote.render | a template and its fills | Code | a hole is empty or unfilled, or the result is not MoonBit |
gen.render | a generator and its inputs | the file's text | any of the above |
cli.run | a command line | a file on disk | the line is wrong, or --check finds the file stale |