Skip to content

Preamble System

The preamble script (preamble.luau) is required for every rule. It runs once per (rule, outputFile) pair — that is, once per unique output file a rule contributes to. The engine deduplicates its result by content per output file at assembly time. It is the right place for:

  • #include directives common to all of the rule’s outputs to this file
  • License or auto-generated banners specific to this rule (the engine already emits its own banner separately)
  • Namespace opening brackets
  • Any text that must appear at the top of the file regardless of which entities land there
return function(input)
-- input: JSON-encoded context object, decode if you need it
return "text to prepend to the file"
end

The input context is:

{
"language": "cpp",
"outputFile": "path/to/output.cpp",
"ruleName": "<RuleName>",
"params": { /* rule.params from RuleName.config.yaml */ },
"entities": [
{
"registryId": 42,
"qualifiedName": "myns::MyStruct",
"structName": "MyStruct",
"namespaces": ["myns"],
"inputFile": "include/myns/my_struct.hpp",
"includes": ["<vector>"]
}
]
}

outputFile is the post-grouping output path for this (rule, file) pair. The entities array contains every entity bound to this output file, with each entity’s handler-emitted includes exposed for conditional preamble logic. This lets you, for example, include <vector> in the preamble only if any entity in the output file needed a vector.

The return value is a raw string — not JSON. Whatever you return is buffered on the rule.

MyRule.preamble.luau
return function(input)
return "#include <string_view>\n#include <vector>\n\n"
end
preamble.luau
return function(input)
return "# API Reference\n\n"
.. "> This file is auto-generated. Do not edit manually.\n\n"
.. "---\n\n"
end

The engine collects all preamble results from the rules contributing entities to a single output file, deduplicates by content (string equality), and emits each unique result once. Two rules that produce identical preamble strings collapse to a single emission; two rules that produce different strings each contribute one block.

This is what makes fan-in clean: dozens of entities from one rule routed into api-reference.md produce exactly one # API Reference header.

It also means: preambles don’t have visibility into which entities will land in their output file. They run before grouping. If you find yourself wanting to conditionalise preamble content on entity data (“only include this header if the entity has template parameters”), that’s a signal to emit the include from the handler’s includes field instead — the engine will deduplicate those across the file too.

Belongs in the preambleBelongs in the handler
Banner / file-level commentPer-entity content
Always-on #include directives, or conditional on the entities list#include directives specific to a single entity (use includes in the return)
Opening namespace bracketThe actual code/declaration text (source, inline)

A preamble that calls error() (or hits a runtime error) aborts the whole run with E007 (exit 1). Unlike handler errors (which skip a single entity), there’s no recovery — if a preamble fails for one output file, the entire codegen run is unsafe.

Key Takeaways
  • The preamble is required (E003 if missing); use return function() return "" end if there’s nothing to emit.
  • Runs once per (rule, outputFile) pair; results are deduplicated by content per output file at assembly time.
  • The input context is {language, outputFile, ruleName, params, entities}; entities lists all matches bound to this output file.
  • Use entities to conditionally emit includes (e.g., include <vector> only if any entity has a vector member).
  • Use includes from the handler for entity-conditional includes; the engine deduplicates those too.
  • Preamble errors abort the whole run.