The specification · Normative text

Grammar of the fold subset

Normative draft. Identifiers are S-*. Every production here is decidable from syntax alone. Where the folder is stricter than the shape rule, because it also resolves names or consults a registry, the production says so and names the F-* rule (evaluation.md, verdict.md) that adds the condition. That is the one direction F-Direction permits; a production must never be stricter than the folder.

Derived from findSubsetViolation (subset.ts), fold() (fold.ts), scanExports (fold-import.ts), findFunctionSubsetViolation and findFactorySubsetViolation, at e4074c17, with the ?. forms from chant-v0.63.0 (11572c7a, #2328). Node kinds are TypeScript’s.

In the notation below, ⟨X⟩ is a nonterminal; | alternation; * zero or more; + one or more. Terminals are TypeScript tokens or node kinds. Each production cites its inventory row.


1. The module and statement gate

S-Module. A module is a sequence of top-level statements. The gate examines only statements carrying export. Every other statement, a non-exported let, a bare call, an if, a non-exported class, is neither admitted nor a disqualifier: it is invisible to the gate, and its effects are never performed. (fold-import.ts scanExports, the continue for non-exported statements; L1.1.)

S-TopConst. const ⟨Identifier⟩ = ⟨Expr⟩, exported or not, is a binding (L5.1). A destructured or uninitialized top-level const is not a binding: the name is invisible.

S-LocalFunction. A top-level function ⟨Identifier⟩ ( ⟨Params⟩ ) ⟨Block⟩, exported or not, and a top-level const ⟨Identifier⟩ = ⟨Arrow⟩ or = function …, bind the name to a project-local function (F-Bind). The body is judged by S-FnBody at the call, never here; the name used as a value is F-Eval-Ident’s rejection.

A module is admitted when every exported statement matches one of S-ExportResource … S-ExportTypeOnly. One S-Disqualify match rejects the whole module (L1.2–L1.5).

S-ExportResource    ::= export const ⟨Identifier⟩ = new ⟨Expr⟩ ( ⟨Args⟩ )
S-ExportSingle      ::= export const ⟨Identifier⟩ = ⟨Expr⟩
S-ExportDestructure ::= export const { ⟨PlainElement⟩+ } = ⟨Expr⟩
S-ExportNamed       ::= export { ( ⟨Identifier⟩ ( as ⟨Identifier⟩ )? )+ }
S-ReExport          ::= export { ( ⟨Identifier⟩ ( as ⟨Identifier⟩ )? )+ } from ⟨StringLiteral⟩
S-ExportFunction    ::= export function ⟨Identifier⟩ ( ⟨Params⟩ ) ⟨Block⟩
S-ExportDefault     ::= export default ⟨Expr⟩                                -- data-host; see below
S-ExportTypeOnly    ::= export type { … }  |  a type-only element of S-ExportNamed / S-ReExport

⟨PlainElement⟩      ::= ⟨Identifier⟩ | ⟨PropertyName⟩ : ⟨Identifier⟩     -- no rest, no default, no nesting

Notes. Under S-ExportFunction an overload signature (no body) is skipped, not a disqualifier, and the bodied declaration that follows is the export. S-ReExport takes named elements only. Under S-ExportNamed a local name must be an identifier, and the TS 4.5 string module-export-name form disqualifies.

S-ExportDefault is the declarator named default, admitted in the data-host profile; in full it stays a disqualifier until chant admits it, which the profile table records as permitted and not required. export default function and export = disqualify in both.

S-Disqualify ::= export default …                              -- full only; data-host admits S-ExportDefault
               | export default function …
               | export * from …
               | export class …
               | export let …  |  export var …
               | export const ⟨Identifier⟩                    -- no initializer
               | export const [ … ] = …                       -- array pattern
               | export const { … ⟨rest | default | nested⟩ … } = …
               | export { "…" as ⟨Identifier⟩ }

2. The expression classifier

⟨Expr⟩ is any production below. The shape rule is what findSubsetViolation decides; an F note is a condition only the folder can check.

S-Unwrap      ::= ( ⟨Expr⟩ ) | ⟨Expr⟩ as ⟨Type⟩ | ⟨Expr⟩ satisfies ⟨Type⟩ | ⟨Expr⟩ !
S-Literal     ::= ⟨StringLiteral⟩ | ⟨NoSubstitutionTemplateLiteral⟩ | ⟨NumericLiteral⟩ | true | false | null
S-Undefined   ::= undefined
S-Ident       ::= ⟨Identifier⟩
S-Template    ::= ` ⟨text⟩ ( ${ ⟨Expr⟩ } ⟨text⟩ )+ `
S-Tagged      ::= ⟨Identifier⟩ ` … `
S-Object      ::= { ⟨Member⟩* }
  ⟨Member⟩    ::= S-Prop | S-Shorthand | S-SpreadProp
  S-Prop      ::= ⟨LiteralKey⟩ : ⟨Expr⟩
  S-Shorthand ::= ⟨Identifier⟩
  S-SpreadProp::= ... ⟨Expr⟩
  ⟨LiteralKey⟩::= ⟨Identifier⟩ | ⟨StringLiteral⟩ | ⟨NumericLiteral⟩
S-Array       ::= [ ( ⟨Expr⟩ | ... ⟨Expr⟩ )* ]
S-Member      ::= ⟨Expr⟩ ( . | ?. ) ⟨Identifier⟩
S-Index       ::= ⟨Expr⟩ ( [ | ?.[ ) ⟨StringLiteral⟩ | ⟨NumericLiteral⟩ ]
S-Unary       ::= ! ⟨Expr⟩ | - ⟨Expr⟩
S-Binary      ::= ⟨Expr⟩ ⟨BinOp⟩ ⟨Expr⟩
  ⟨BinOp⟩     ::= && | || | ?? | + | - | * | / | === | !== | > | < | >= | <=
S-Conditional ::= ⟨Expr⟩ ? ⟨Expr⟩ : ⟨Expr⟩
S-New         ::= new ⟨Expr⟩ ( ⟨Expr⟩* )
S-Call        ::= S-CallHelper | S-CallIntrinsic | S-CallEager | S-CallMethod | S-CompositeStep
  S-CallHelper    ::= ⟨HelperName⟩ ( ⟨Expr⟩* )
  S-CallIntrinsic ::= ⟨Identifier⟩ ( ⟨Expr⟩* )        -- registry-gated
  S-CallEager     ::= ⟨Identifier⟩ ( ⟨Expr⟩* )        -- registry-gated
  S-CallMethod    ::= ⟨Expr⟩ ( . | ?. ) ⟨Identifier⟩ ( ⟨Expr⟩* )
  S-CompositeStep ::= ⟨UnclaimedCall⟩ . step
  ⟨UnclaimedCall⟩ ::= ⟨Identifier⟩ ( ⟨Expr⟩* )     -- see S-Unclaimed
S-Reject      ::= anything else

The per-production conditions and divergences follow.

ProductionRowShape ruleF: what the folder adds
S-UnwrapL2.1recurse into the operand;
S-LiteralL2.2admittednumeric → Number(text)
S-Undefined;an identifier, shape-valid as S-Identfolds to undefined
S-IdentL2.3always shape-validmust resolve in consts then externals; a bare process is a pointed rejection; a name bound to a same-file new resolves only via externals, where F-Prebuild placed the single instance
S-TemplateL2.5every span ∈ ⟨Expr⟩spans coerce by ToString
S-TaggedL2.4interior opaque; not recursedtag must be a registered, tag-foldable intrinsic; interior folds with unresolved dotted chains kept symbolic
S-PropL2.6key must be ⟨LiteralKey⟩ (EVL001); value ∈ ⟨Expr⟩;
S-ShorthandL2.6always shape-validthe name resolves as S-Ident
S-SpreadPropL2.6operand ∈ ⟨Expr⟩operand must fold to a non-null object
S-Array;each element or spread operand ∈ ⟨Expr⟩spread operand must be an array
S-MemberL2.15object ∈ ⟨Expr⟩, . or ?.; S-CompositeStep takes precedence when the member is step and the object is a callon a resource-bound identifier → {__attrRef}; . on null/undefinedrefused (L3.10); ?. on null/undefined → short-circuits the rest of the chain to undefined (L3.21)
S-IndexL2.7key must be a string or numeric literal (EVL003); object ∈ ⟨Expr⟩, [ or ?.[same as S-Member, including the ./?. distinction
S-UnaryL2.8operator ∈ {!, -}ECMAScript coercion
S-BinaryL2.8, L2.9operator ∈ ⟨BinOp⟩; both sides ∈ ⟨Expr⟩ (flow-insensitive)&&/||/?? evaluate lazily; the untaken side is never folded
S-ConditionalL2.9all three ∈ ⟨Expr⟩only the taken branch is folded
S-NewL2.10every argument ∈ ⟨Expr⟩, positionally; callee shape unconstrainedcallee must be a plain identifier (L3.15); refused inside a folded function body
S-CallHelperL2.11name ∈ FOLDABLE_AUTHORING_HELPERS; args ∈ ⟨Expr⟩name must be bound by an import from chant; not shadowed by a local const; refused inside a folded function body
S-CallIntrinsicL2.12with a registry: name registered with foldsAsCall; without a registry: S-Rejectname must resolve through the file’s imports; refused inside a folded function body
S-CallEagerL2.13with a registry: name registered with foldsEagerly; without: S-Rejectcallee must resolve to a function; evaluated at fold time
S-CallLocalL2.17, L5.4callee is an identifier this file binds by S-LocalFunction or by an import from a project specifier (./, ../); args ∈ ⟨Expr⟩the body must satisfy S-FnBody at the call (F-Eval-CallLocal); a cross-file callee needs the module graph, so an expression-level folder refuses it
S-CallMethodL2.14receiver ∈ ⟨Expr⟩, . or ?.; args ∈ ⟨Expr⟩; method name unconstrainedreceiver must fold to a real value, not a symbolic envelope; the named property must be a function. Receiver null/undefined: . refuses, ?. short-circuits (L3.22)
S-CompositeStepL2.15any call, any arguments, member exactly stepcallee must be an unclaimed bare identifier (L3.20); refused inside a folded function body
S-RejectL2.16EVL001;

S-Unclaimed. A call is unclaimed when its callee is a bare identifier that no other S-Call form claims. The callee must not be:

Only the bare-identifier part is decidable from syntax. The four conditions are resolution so S-CompositeStep admits any call at shape level and F-Eval-Member step 2 applies the full test.

Explicitly outside the subset (S-Reject at shape level, and rejected by the folder): an arrow or function expression as a value (L3.1); class expressions; await, yield; assignment and compound assignment; the comma operator; typeof, void, delete, ++, --; ==, !=, %, **, the bitwise operators, in, instanceof; a computed property name; a non-literal element-access key; a call not matching any S-Call form. Optional chaining is specified rather than merely admitted: since chant-v0.63.0 a ?. on a nullish object produces a chain-short-circuit value that propagates through the remaining ./[]/!/?.() of the same chain and becomes undefined at its end which is ECMAScript’s semantics.


3. Body sub-grammars

Two further statement-level grammars govern what may appear inside a function the folder evaluates. Both are stricter than S-Module.

S-FnBody, a project-local function (L5.4):

S-FnParams ::= ( ⟨Identifier⟩ ( = ⟨Expr⟩ )? | { ⟨PlainElement⟩+ } )*    -- no rest, no array pattern
S-FnBody   ::= ⟨Expr⟩
             | { ( const ⟨Identifier⟩ = ⟨Expr⟩ | const { ⟨PlainElement⟩+ } = ⟨Expr⟩ )*  ( return ⟨Expr⟩? )? }

No generator, no async, no early return, no let/var, no uninitialized const, no other statement kind. A block with no return evaluates to undefined (L5.7). Inside S-FnBody the expression grammar loses five productions: S-New and S-Tagged, S-CallHelper and S-CallIntrinsic, and S-CompositeStep (L3.16).

S-FactoryBody, an interpretable composite factory (L7.3–L7.5):

S-FactoryParams ::= ( ⟨Identifier⟩ | { ⟨PlainElement⟩+ } )?     -- at most one; no default, no rest
S-FactoryBody   ::= ⟨Expr⟩
                  | { ( const … = ⟨Expr⟩ )*  return ⟨Expr⟩ }      -- must end in return; empty body rejected

Inside S-FactoryBody the expression grammar gains new in any value position and a call through a bare identifier (a nested composite, a registered helper, an opted-in intrinsic), and a method call stays out (per the contract above resolveInterpretableFactory; checkFactoryExpression’s exact set is not transcribed here).

The asymmetry between the two. S-FnBody tolerates a missing return and S-FactoryBody requires one which the specification decides rather than inherits.


What this grammar does not decide

Resolution, registration, trust, the fold/run verdict and its taint (verdict.md, taint.md), and every semantic rule. A string that parses under this grammar is shape-admissible; whether it folds is the judgments’ question.