Skip to content

Evaluator Engine

chant has two ways to turn a source file’s exported resources into structured data for the lexicon serializer: reduce their AST to a value directly (the default), or run them for real (the per-file fallback, and what --no-fold forces). This page covers both — see TypeScript as Data for the full comparison.

chant build folds by default since #1134 (mechanism from #1022/#1026/#1023, epic #1019): it skips execution for files that stay inside the fold subset. It walks top-level statements in the source file directly — looking for an exported new Type({...}) resource, or a composite factory call — and recursively reduces the property object into a value tree from the AST, with zero module execution. This is the literal “read the AST as data” mechanism; it’s implemented in packages/core/src/fold/{fold,subset}.ts.

A file outside the subset falls back to importing and running it instead, transparently, logged as [fold:run] <reason> under chant build --verbose (a count otherwise). A folded file and a run file produce byte-identical serialized output (#1025) — see Folded vs Run for what folds today and what still falls back.

A run-fallback file (and every file under --no-fold) is dynamically imported — real module execution, the same as any other TypeScript entry point. new ResourceType({...}) really constructs a Declarable; a composite factory call really runs its factory function. Nothing here is a bespoke evaluator: it’s the TypeScript runtime.

What keeps that safe is the evaluability lint rules (EVL), covered in TypeScript as Data: they reject anything a resource file could do besides read literals, constants, and cross-resource references, so running the supported subset has no side effects and always yields the same result. EVL runs in chant lint and your editor, not inside chant build itself — see Evaluability Rules for how that diagnostic layer relates to the enforcement mechanism above.

  • Literals: strings, numbers, booleans, null, arrays, object literals
  • Identifiers: references to const bindings (traced to their initializer)
  • Property access: obj.key on known objects, including the cross-resource { __attrRef } case
  • Spread: {...defaults, name: "x"} from const sources
  • Ternary: cond ? a : b
  • Binary expressions: arithmetic, string concatenation, comparisons
  • Nullish coalescing and logical operators: left ?? right, &&, ||
  • Tagged templates: registered lexicon intrinsic tags

This is the same subset the evaluability rules (EVL001/EVL003) flag violations of — one shared definition (fold/subset.ts), imported by both, with a short list of environment-dependent exceptions where the two can’t fully agree; see Evaluability Rules for the list. Expressions outside this subset make fold() reject that one file; they don’t stop the default run path at all, since running plain JavaScript imposes no such restriction on its own.

Each extracted resource — folded or run — produces an evaluated resource with:

  • LogicalName — derived from the export binding name
  • ResourceType — provider type string from the resource registry
  • Props — ordered map of evaluated property values

A Composite({...}) definition is a factory function; running it (default path) or resolving and invoking it through the file’s imports with folded arguments (--fold path, #1023) both expand its resource map the same way, resolving siblings references between member resources.