Compiler tools and syntax values
Compiler tools and syntax values
Grid programs can inspect, construct, and transform expressions as data. The Rust compiler supplies parsing, source analysis, checked rename, and compilation diagnostics. Generated source passes through the ordinary compiler and LIR runtime.
In the Code workspace
Open Compiler tools in the Code toolbar. The panel makes no compiler requests until you choose an action.
- Inspect syntax opens a source-linked tree. Select a node to reveal its source, attributes, and byte range. A named node can prepare a checked rename.
- Select an expression and choose Explain expression for its binding, contextual type, known effects, direct source dependencies, and available units, bounds, and proofs. Each fact distinguishes established, partial, and unknown information. Go to definition follows a resolved source binding.
- In Edit expression, prepare a replacement or extract a named function. Select a function declaration for Change function signature: rename or reorder its parameters, remove unused ones, or supply arguments for new ones. Preview each source change alongside the resulting compiler diagnostics. Staging rechecks the transaction against the original draft and applies all its edits together; a changed draft or invalid proposal cannot be staged.
- Check source reports diagnostics and named dimension, bound, and proof facts without executing the model.
- Choose one of the six examples to open a separate scratchpad. Run outputs evaluates only the named outputs in a fresh authoring model. Generate preview evaluates the generator output, checks the generated source, and optionally evaluates its named outputs in another fresh model.
- Inspect expansion reads an
AST.EXPANDoutput and opens a viewer linking generated nodes and compiler findings to their template and inserted inputs. It checks declaration source without running the generated model. An expression requiring surrounding declarations is clearly labeled as such. - Review the proposed replacement and Stage replacement for review. This pauses automatic synchronization; save explicitly to apply it. A changed model draft invalidates the preview.
Editor completion, hover, and signature help include AST.QUOTE and dotted
AST.* names. The guide and all examples are bundled for offline reading.
Authoring requests accept up to 64 KiB of source and 32 selected output names. A fresh Rust worker handles each request, with a 15-second wall deadline, a 2 MiB report limit, and at most two workers per host. Unix workers also have a CPU limit; Linux adds an address-space limit. The worker initializes no live model, connector, or import resolver. This is a deterministic language admission boundary, not an operating-system sandbox. Closing the panel cancels its response; an already-started server worker remains bounded by its deadline.
Quotation and reflection
formula = AST.QUOTE(quantity * unit_price)
original = AST.SOURCE(formula)
dependencies = AST.FREE_NAMES(formula)
price(quantity) = quantity * 7
definition = AST.DEFINITION(price)
definition_source = AST.SOURCE(definition)Quotation captures a normalized expression without evaluating its references or
calls. AST.DEFINITION reads a local function, named assignment, or cell
definition, including a definition written later in the source. It returns the
declaration; quoting price returns only a reference. Imported definitions and
computed targets are refused. Existing BODY, ARGS, and FUNCTION keep their
existing record formats and callable metadata.
Reflected definitions depend on source. A formula edit in a model containing
AST.DEFINITION requires ordinary whole-source compilation; partial formula
splicing refuses rather than retaining an obsolete definition. Changing an input
value does not change its authored definition.
Public data contract
A syntax value has schema: "grid.syntax.v1", root, and optional source.
Every node has kind, attributes, children, and optional span. Spans are
half-open UTF-8 byte offsets into that exact source. Paths are zero-based child
indices; they identify occurrences in this tree, not persistent compiler IDs.
AST.WALK returns flat rows with paths, kinds, attributes, spans, and child
counts. AST.AT(tree, path) selects a subtree. AST.ROOT exposes the root record;
AST.CHILDREN returns detached child syntax values. Construction and substitution
clear source spans: generated syntax must not claim an authored location it lacks.
The schema represents literals, references, operators, calls, lexical lambda
and let nodes, arrays, objects, and declarations. A source module also retains
the entire original source, including comments and directives. Its
structureComplete: false flag means that metadata-owned declarations are not
all semantically projected. Unprojected regions remain source-linked declaration
fragments, so the inspector does not silently omit them. Ordinary named-assignment
modules report complete structure and can render; use the original source for
faithful reproduction, including comments.
Whole-source projection version 2 distinguishes structured inspection from
source emission. Function definitions include their structured signature,
effects, captures, dependencies, and body. Assignments retain modifiers and
typed tensor shapes; their first child is the value and an optional second
declaration child with compiler type AssignSchedule carries scheduling.
A rule's first child has compiler type RuleTriggerAst, followed by its
RuleActionAst children. These records, and their nested targets, qualifiers,
write modes, rate limits and execution/retention policies, use the generated
compilerDeclaration fields described below. For example, a When trigger's
fields.execution links to its policy record, whose fields.concurrency is a
number and fields.retention links to the retention variant. RESET and CLEAR
expose fields.expr: null; their internal placeholder is never inspected.
Solve declarations retain their goal or optimization direction.
New rule/schedule inspections replace the previous flattened ruleTrigger,
ruleAction and schedule forms. Queries should select compiler type and
follow local field links instead of old childRoles or flattened attributes.
Previously serialized forms remain readable. The compiler records retain
normalized addresses and default policies; exact authored spelling is in the
source. Action spans are absent because the compiler does not retain them. Try Code’s
Inspect rule policies example or
inspect-rule.grid.
Graph schemas and predicate schemas expose their kinds, fields, types, and
cardinality. Views expose their authored outline, named roles, and independently
parsed expression children with source-relative byte spans. Contract-dependent
phrases, action bodies, and other unclassified parts remain source-linked parts
with structureComplete: false; they are not guessed into expression trees.
Hidden assignments emitted during View lowering are not shown as additional
authored declarations. Comments and formatting remain in the separate, exact
source text; canonical rendering is not a formatting-preserving round trip.
structureComplete: true does not imply that rendering or substitution is
supported. Use AST.CAPABILITIES for that answer. A module's incompleteFamilies
also lists metadata families still awaiting projection, including clauses that
share an assignment's source span. This prevents missing FORMAT or VALIDATE
structure from being mistaken for complete coverage. Other declaration families
remain incomplete; the full language syntax model is still being extended.
The compiler's own program fields now own this inventory: a newly added field
must declare its source coverage. Authored model headers, dimensional policy,
unit-scale policy and coercion policy are also reported while their structured
syntax remains incomplete.
Graph paths, partitions, chain patterns and fixed-point declarations now expose
their retained compiler structure. Their enclosing declaration has
form: "binding" and keeps assignment modifiers. Its first child has
form: "compilerDeclaration", a compiler type such as PathBindingDecl, an
optional enum variant, and a fields object. Named fields keep their compiler
names; tuple variant fields use "0", "1", and so on. Scalars are values,
absent options are null, and lists retain order. A nested record or expression
is represented by {"child": n}, where n selects a child of that same node.
The link never contains a compiler arena ID. External syntax validation rejects
dangling, duplicate and unreferenced children.
These nodes derive from the same types used by the compiler. New supported
fields are included automatically; source locations and derived lowering state
have explicit roles. Internal GRAPH_PATH_QUERY, GRAPH_PARTITION_NODES,
GRAPH_PATTERN_MATCH and GRAPH_PROPAGATE helper calls are replaced by the
retained declaration structure. Locations remain in span, so detaching a
tree removes them consistently. Inspection does not grant rendering or
substitution support: ask AST.CAPABILITIES before transforming. Exact spelling,
comments and formatting remain in the retained source; compiler defaults are
not evidence that every option was explicitly authored. See
inspect-graph.grid.
Views, library items, graph schemas and predicate schemas use the same generated
compilerDeclaration representation directly as module children. This replaces
the older view, viewItem, graphSchema and predicateSchema inspection
forms. For example, SurfaceDeclaration.fields.items is an ordered list of
local child links; a SurfaceItem has a kind child whose variant identifies
the compiler's outline construct. Name records carry their own source locations.
Nested arrangement rows retain their list-of-lists shape, and predicate
multiplicity retains its ordered identity fields.
Span fields on the compiler records declare whether they contain embedded Grid
expressions or retained fragments. Both become sourcePart nodes with a field
role and source location. Only embedded Grid expressions are parsed into
children. Unclassified phrases, parameter fragments and foreign/action bodies
remain explicit fragments; their incompleteness propagates through generated
parents. Error variants retain the compiler diagnostic. Layout-only data stays
in the separate source text. New supported fields and variants need no View or
schema visitor update. See
inspect-view.grid.
Limits are 65,536 nodes, 128 node levels, and a 1 MiB source/attribute budget.
Whole-source parsing applies the node bound to retained authored items as well
as projected nodes, including declarations carried in opaque source fragments.
Serialized syntax has a 2 MiB input bound and the JSON decoder's additional
nesting bound. Each operation checks its combined input budget before copying
values. AST.LITERAL preserves matrix rows and columns and refuses native values
that cannot be represented by ordinary literal syntax. Unknown schemas,
malformed nodes, invalid names, and budget excess
return errors. AST.FROM_JSON(text) reads this portable data format; it grants no
execution authority.
Construction and transformation
Checked source transactions
source = "price(quantity) = quantity * 7\nanswer = price(6)"
parsed = AST.PARSE(source)
proposal = AST.EDIT(source, {
revision: parsed.sourceRevision,
operation: {
kind: "changeSignature",
function: "price",
parameters: [{ name: "items", from: "quantity" }]
}
})
updated_source = AST.APPLY(source, proposal)AST.EDIT(source, request) prepares a grid.syntax.transaction.v1 report.
request contains revision and one operation:
| Kind | Fields | Behavior |
|---|---|---|
replaceExpression |
span, replacement |
Replace one exact retained expression with parsed expression text, parenthesized to preserve its surrounding precedence. |
extractFunction |
span, name |
Move the selected expression into a function before its owning declaration, passing free value and lexical callable bindings explicitly. |
changeSignature |
function, parameters |
Edit one local function and every resolved direct caller, including nested calls. |
Each signature parameter has name and exactly one of from (an old parameter)
or argument (a new expression inserted at every caller). Existing arguments
follow their old parameter identities when reordered. Parameter reads respect
sequential LET and LAMBDA shadowing; capture and removal of a used parameter
are refused. New argument text is interpreted in each caller's lexical scope.
Untouched source stays byte-identical. Comments inside an extracted body move
with that body; retained argument comments move with their argument. Deliberately
removed expressions or parameters can remove comments within their edited ranges.
The report carries its request, original and updated source revisions,
updatedSource, sorted non-overlapping edits, an explanation, focused
preview entries, ok, resulting diagnostics, and validation. Invalid
compiled proposals remain reviewable with ok: false. AST.APPLY reconstructs
the proposal, verifies its edits, source identities, explanation and focused
preview, and compiles it again.
It returns updated source text; it does not mutate a model. External success
flags and diagnostic fields never authorize an application.
Compilation checks validity; it does not prove equivalent behavior. Extraction, argument reordering/removal, and newly supplied arguments can change evaluation order, effects, or reflection results. Every transaction explicitly reports that equivalence is not established.
Current refactorings require retained lexical scope. Imported definitions, partial/reflection/adapter uses of a changed function, and qualified, positional, or grammar expressions in an extracted or signature-edited body are refused. Expression replacement can still target an exact retained expression of those forms. The source must parse before preparation. Limits include 128 parameters, 4,096 edits/callers, 1 MiB source or replacement text, existing expression node/depth limits, and a 2 MiB report. These are explicit supported-operation limits, not claims that the entire language is refactorable.
Constructing syntax
expression = AST.BINARY("*", AST.REFERENCE("quantity"), AST.LITERAL(7))
definition = AST.FUNCTION("price", ["quantity"], expression)
answer = AST.ASSIGN("total", AST.CALL("price", [AST.LITERAL(6)]))
generated_source = AST.RENDER(AST.MODULE([definition, answer]))
check = AST.CHECK(generated_source)AST.NODE(kind, attributes, children) constructs other validated node shapes.
AST.SUBSTITUTE(tree, replacements) simultaneously replaces free names. It
renames lambda and sequential LET binders, function parameters, and generated
declaration names when necessary to prevent capture. Plain AST.ASSIGN,
AST.FUNCTION, and AST.MODULE constructions share this support. Module names
bind throughout the module, including forward and recursive references. Inserted
expressions are never substituted again; unused or shadowed replacement keys
do not rename declarations.
Declaration hygiene supports unqualified named bindings. Names containing digits
must include an underscore to stay outside the compiler's spreadsheet-address
syntax. Duplicate module names, qualified names, annotations, incomplete source
projections, and other declaration families require compiler binding analysis.
AST.CAPABILITIES reports these limits before transformation. Each replacement
must be an expression, and combined input and expanded-name sizes are bounded.
Review generated declaration names before joining the result to other source:
preventing capture can rename a module binding. Generated trees have no authored
source spans. Use AST.RENAME or checked transactions for existing source
declarations, preserving their annotations, comments, and formatting. Explicit
templates extend this hygiene with named holes, list splicing, and traced origins.
Quoted templates and expansion origins
formula = AST.QUOTE(SUM(AST.SPLICE("prices")) * quantity + AST.HOLE("fee"))
expansion = AST.EXPAND(formula, {
prices: [AST.QUOTE(3 + 4), AST.LITERAL(0)],
fee: AST.QUOTE(5)
})
generated_source = expansion.generatedSourceAST.HOLE("name") denotes one expression, and AST.SPLICE("name") denotes a
list of expressions in a call's arguments or an array row. They are captured
unevaluated inside AST.QUOTE; calling them directly constructs the same marker
syntax for use with AST.CALL, AST.FUNCTION, and other constructors. Bindings
map case-sensitive labels of 1–128 UTF-8 bytes to one syntax value or a list of
syntax values, respectively. Empty lists remove that list occurrence. Array row
boundaries are retained. Missing, unused, mismatched, and misplaced bindings are
refused. Fixed-arity operator operands, record keys, parameter names, and module
declaration lists are not expression-splice positions.
Expansion is simultaneous. It does not recursively expand marker calls supplied inside an input. Temporary hole names cannot collide with names in either the template or its inputs. The shared scope engine renames supported template binders to keep inserted free names free, including constructed module declarations, forward references, recursive calls, parameters, and sequential local bindings. The source-only and incomplete-scope restrictions above remain applicable.
The grid.syntax.expansion.v1 report contains template, bindings, detached
syntax, generatedSource, and origins. Each origin identifies a generated
node path and UTF-8 generatedSpan, its template path and optional authored span,
and, for inserted nodes, its binding label, optional zero-based list item, and
input path/span. Repeated inputs have distinct generated occurrences. Original
source stays in the retained template or binding, separate from generated syntax;
generated nodes never claim authored source spans. Constructors detach child
locations, so composed templates can expose a structural origin without authored
text. Inputs made with quotation retain their exact input text and locations.
AST.EXPAND neither compiles nor runs generated code. In Code, choose the Quoted
template example and Inspect expansion. Select a node or follow a compiler
finding to compare its template, input, and generated text. Renamed bindings are
shown explicitly. Declaration source is checked by the ordinary compiler;
standalone expressions report that context is required. Successful checking
does not establish runtime success or equivalent behavior. Inspection reconstructs
the expansion from retained inputs instead of trusting supplied generated text,
syntax, or origin claims. Generate preview also accepts an expansion report
and applies the existing deterministic admission before optional execution.
Existing syntax depth/node budgets apply. Combined inputs are bounded to twice the syntax budget; output syntax and rendered source retain their 1 MiB limit. Origin allocation is bounded during construction, and the complete serialized expansion report, including retained inputs, is limited to 2 MiB. The editor worker's 64 KiB source and 15-second limits still apply. No automatic expansion is added to ordinary model compilation. Whole-declaration splices, origins preserved through every constructor, and expansion histories remain further work.
Bulk selection and structural rewrites
formula = AST.QUOTE(unit_price * quantity + delivery_fee)
references = AST.SELECT(formula, { kinds: ["reference"] })
support = AST.CAPABILITIES(formula, [])
# Paths refer to occurrences in the original formula.
revised = AST.REWRITE(formula, [
{ path: [0, 0], replacement: AST.LITERAL(7) },
{ path: [1], replacement: AST.LITERAL(5) }
])
revised_source = AST.RENDER(revised)AST.SELECT(syntax, selector) returns flat rows in source-tree preorder, with
the same path, kind, attributes, span, and childCount fields as
AST.WALK. A selector can specify kinds, exact attributes, a within path,
and maxDepth relative to that subtree. Omitted filters match everything;
maxDepth: 0 selects only the subtree root. Returned paths remain absolute in
the original tree. Unknown selector fields are errors. A report exceeding the
1 MiB logical output budget is refused; narrow the selector to inspect a large
tree in bounded sections.
AST.REWRITE(syntax, edits) applies a batch of {path, replacement} records
simultaneously. Input order does not matter; replacements are not revisited.
Duplicate and ancestor/descendant paths are refused, as are nonexistent paths,
invalid parent shapes, and combined or resulting budget excess. The original
tree stays unchanged. The result has no retained source or authored spans.
This is structural rewriting: it can change bindings and behavior. Use
AST.SUBSTITUTE for capture-avoiding free-name substitution and compiler-checked
source edits for preserving surrounding comments and formatting.
AST.CAPABILITIES(syntax, path) reports render, freeNames, and substitute
support for that subtree. Each has supported and, on refusal, a reason
and the absolute path of the first unsupported node. select and rewrite
report structural operation support; sourceSpan is the retained authored
range, if any. Support describes node forms, not unlimited resources or the
validity of a future replacement: normal argument, scope, and output limits
still apply. Capability discovery uses the renderer and scope policies without
attempting to render or substitute every subtree.
These bulk operations validate external syntax once per request. Traversal
does not validate the whole tree again for each selected node. Rust tooling can
retain a ValidatedSyntax value for repeated immutable queries; it cannot
mutate the admitted tree through that wrapper.
AST.RENDER emits supported expression forms, ordinary parsed named assignments, and constructed assignments,
functions, and modules. It refuses source-only declarations or qualified
references whose modifiers or spelling it cannot safely reproduce. AST.SOURCE
returns retained text; it does not render an edited tree. This distinction
preserves scheduling, types, comments, and source forms outside the emitter's
supported subset. Rendering is not compilation or semantic-equivalence proof. String emission uses
Grid’s own escape rules, preserving Unicode and control characters when reparsed.
Object keys use Grid’s separate raw-key convention. Keys containing quotes or
backslashes are explicitly refused by rendering and capability discovery because
the emitter cannot reproduce them safely.
Compiler services
| Function | Result |
|---|---|
AST.EXPRESSION(text) |
Syntax parsed by Grid's Rust expression parser |
AST.PARSE(text) |
ok, normalized source syntax, and diagnostics |
AST.ANALYZE(text) |
Source symbols, completeness flags, function types, effects, contracts, and captures |
AST.CHECK(text) |
Ordinary source-to-LIR diagnostics, dimensions, bounds, validation proofs, and compilation summary |
AST.MEANING(text, selection) |
Revision-bound expression binding, contextual type, effects, direct dependencies, and available compiled facts |
AST.RENAME(text, byte_offset, new_name) |
Original source, updated source, and checked edits |
Rename preserves untouched text and refuses ambiguous or unsupported scope,
collisions, and capture. Reports expose completeness rather than claiming every
source construct is covered. Compilation checks do not execute the model.
The check report's sorted namedFacts rows associate inferred dimensions,
bounds, and validation proofs with source names. A missing fact is null;
inference has not established it. Raw namedWires identifiers are local to that
compilation and must not be stored as stable source identities.
Expression meaning and source revisions
source = "price = 7\ntotal = price * 6"
parsed = AST.PARSE(source)
expressions = AST.SELECT(parsed.syntax, { kinds: ["binary"] })
selected = INDEX(expressions, 1)
meaning = AST.MEANING(source, {
revision: parsed.sourceRevision,
span: selected.span
})Parse, analysis, and check reports include sourceRevision, a sha256: identity
of the exact UTF-8 source bytes, including comments and whitespace.
AST.MEANING requires that revision plus the exact nonempty byte range of one
retained expression. A changed source returns AST/STALE; invalid UTF-8
boundaries, fractional offsets, ambiguous ranges, and declaration selections
are refused. Revisions identify content, not compiler versions or permission
to mutate a model. Tree paths and compiler arena IDs are not revision identities.
Meaning reports have schema: "grid.syntax.meaning.v1", sourceRevision,
span, selected source, ordinary compilation diagnostics, and ok.
Their binding, type, effects, dependencies, units, bounds, and
proofs fields carry status and value. Status is known, partial,
unknown, or notApplicable; unknown facts include a reason.
Binding queries use resolved callable targets and the source index, including
function parameters and sequential lexical scopes. Contextual type queries use
the existing inferencer and finalized function schemes. They retain unresolved
variables and any as partial information. Enclosing higher-order constraints,
nominal/process obligations, and some lowered scopes are not reconstructed.
There is no inference from the current runtime value.
Effects include classified expression effects and named-callee effects. An empty partial list does not establish purity. Dependencies are resolved direct source references; this is not a transitive runtime dependency graph. Unit, bound, and validation-proof facts currently attach only to an exact, unique named-assignment value for which the ordinary compiler retained that fact. A fact about a whole formula is never attributed to one of its child expressions. Failed compilation withholds semantic type/effect/proof facts.
The Code inspector requests these facts only when Explain expression is selected. It preserves the syntax tree, labels old-draft reports, and disables navigation until current source is inspected again. All work remains in the bounded authoring worker; explaining an expression does not execute it.
These services require a host that installs the compiler-tools service. Grid's
Rust runtime host installs its existing compiler at startup. Bare embedded LIR
runtimes can use syntax data operations and must explicitly install that service
to parse or check source; otherwise those calls return AST/UNAVAILABLE.
Requests contain source, never model handles, import resolvers, files, network
credentials, or mutation grants. Source imports that require a resolver fail
through ordinary compiler diagnostics.
Grid-authored analyzers
An analyzer is an ordinary one-argument Grid function receiving model source
as text. It returns {sourceRevision, diagnostics}. Each diagnostic has code,
severity (error, warning, or info), message, a UTF-8 byte span, and
optional fixes. A fix contains title and an operation accepted by AST.EDIT.
AST.DIAGNOSTICS(source, report) validates and normalizes this data against the
exact source revision. Its schema is grid.analyzer.diagnostics.v1. It does not
claim compiler validity, establish proofs, execute the inspected model, or
apply suggested changes. Unknown fields, stale revisions, invalid UTF-8 ranges,
and unsupported fix requests are refused. Diagnostics may use an empty range;
expression-edit ranges must be nonempty.
The effects analyzer parses the source once, examines structured function definitions, and flags classified effects. Its suggested blank placeholder changes behavior and must be reviewed. An empty effect list or a clean analyzer report does not establish purity or completeness of compiler analysis.
In Code, open Compiler tools → Analyze the model with Grid, load the example or enter an analyzer, and choose Run analyzer. The analyzer program is kept separately from the model draft. Findings appear in the normal Problems list and editor markers with their analyzer origin. Review fix asks the compiler to prepare a checked transaction and opens its focused preview. Staging rechecks that transaction and the unchanged draft; saving remains explicit. Changing the model hides old findings, and canceled or old-draft responses cannot supply locations or edits for the current source. Findings do not control Save.
Runs are explicit and source-only. Each source and analyzer is limited to 64 KiB at the worker boundary; the existing deterministic staging admission, two-worker cap, and 15-second deadline apply. The driver supplies fresh bindings for source-as-data and the result, normalizes ordinary Grid records through the native builtin, and independently validates the report before attaching the analyzer's entry name and source revision. There is no filesystem, import resolver, network credential, or live-model mutation grant.
Reports allow at most 256 diagnostics, 8 fixes per diagnostic and 256 fixes in total, within the 2 MiB report budget. Codes use 1–64 ASCII letters, digits, underscores, hyphens or dots; messages have at most 4,096 bytes and fix titles 256 bytes. Suggested operations remain inert until explicit review. This version runs one selected analyzer against one source revision; it does not provide analyzer dependency scheduling or cross-module fact caching.
Working examples
The repository's examples/compiler-tools/ directory contains six executable
Grid models and one reusable analyzer:
- Effects analyzer supplies source-linked findings and a reviewable edit suggestion.
- Model auditor lists function signatures, separates pure and effectful functions, and inspects formula references without executing the inspected model.
- Checked refactoring renames a binding while preserving comments and demonstrates lexical substitution that avoids capture.
- Invoice language parses
buy 6 at 7using an authored grammar, reads its native TREE terminals, and constructs source that computesinvoice_total = 42. - Generate prices constructs a function and call yielding
total = 42. - Model family generates three calculations yielding 42, 54, and 66.
- Quoted template fills a formula's expression hole and argument splice, traces its inputs, and generates a calculation yielding 47.
The invoice example uses the grammar's existing native TREE authority. It explicitly translates terminal data into Grid syntax. An AST record cannot forge a native TREE value, grammar authority, rewrite certificate, or proof that a Grid program preserves meaning. General Grid AST-to-TREE conversion and automatic compiler rewrite hooks are not part of this interface.
Explicit staged generation
The maintained Rust example provides a concrete two-stage authoring tool:
cargo run --manifest-path rust/Cargo.toml -p grid-runtime-core \
--example compiler_tools -- generate \
examples/compiler-tools/model-family.grid generated_source total_7 total_9 total_11It executes the generator through LIR, reads its generated_source result,
checks the emitted source with the same compiler, and executes the explicitly
requested symbols in a fresh LIR model. The JSON report includes source text,
both source hashes, compilation size, and the results. Omit the trailing symbol
names to compile without executing the generated model.
The example admits a documented subset of deterministic named calculations,
local functions, collection helpers, and AST operations. It rejects external
effects, addressed/model state, unsupported calls, and more than 4,096 expression
nodes or 64 KiB of source at either stage. Its allowlist is in
rust/grid-compiler-tools/src/staging.rs; a rejection does
not mean the construct is unavailable in ordinary Grid models.
An explicit authoring command or panel action starts a worker process. The parent refuses output
after 15 seconds or a worker failure. GRID_COMPILER_TOOLS_TIMEOUT_MS can lower
that bound to between 1 and 15,000 milliseconds. Unix workers also install a 10-second CPU
limit; Linux additionally installs a 4 GiB address-space limit. macOS does not
support that address-space limit. Other platforms have the parent wall-time
bound. These limits do not constitute an operating-system security sandbox.
No output is accepted until both stages succeed, and generated code gains
no import resolver, external effects, live-model state, or mutation authority.
This is a runnable authoring example, not automatic macro expansion during
ordinary compilation. Application hosts can compose the same compiler and LIR
APIs at their own explicitly authorized staging boundary.