#moonast
The chain

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.

01 · source #demo.describe pub struct Point { x : Int y : Int } 02 · read @gen.read Struct( name="Point", fields=[x, y], attrs=[…]) 03 · bind @attr.find + Spec name="demo.describe" args=[] no parameters, so nothing to check 04 · fill @quote.items(…) fn $T::describe( self : $T) { $text } three holes, three fills 05 · write @cli.run // Generated by // describe from // models.mbt. describe.g.mbt ONE CALL TO generate() SEES EVERY DECLARATION OF EVERY INPUT
The same five stages run whether one file is annotated or a hundred.
01 · source models.mbt

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
}
Why the order matters

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.
StageSeesGivesRefuses when
gen.reada file name and its textArray[Decl]the text is not MoonBit
attr.bindan annotation and a specArgsa key is missing, unknown, mistyped or repeated
quote.rendera template and its fillsCodea hole is empty or unfilled, or the result is not MoonBit
gen.rendera generator and its inputsthe file's textany of the above
cli.runa command linea file on diskthe line is wrong, or --check finds the file stale