M050: Plain Rule Calls
Unmemoized rules without captures do not need the full memo lookup, capture-frame switch and generic rule dispatch on every call.
Mechanism and Invariants
rule.plain excludes left-recursion leaders and requires no capture slots and
no ordinary memoization. invokePlain leaves the caller's frame current while
preserving depth accounting, cuts, environment rollback, recovered errors and
result finalization. Call sites known to be plain bypass the memo decision. The
iterative VM combines the plain call with its body frame; generated Go and
TypeScript emit rule-specific wrappers. Document parsing still memoizes
transient rules and must take the appropriate Document route.
Scope and State
Default eligible plain-call routes exist in closure, recursive VM, iterative VM, generated Go Node/recognition, typed Go and TypeScript. Typed direct bodies are a broader generator specialization described separately.
Reference Control
There is no universal same-source plain-call reference switch. A memo-disable option changes memo policy, not only wrapper dispatch. Typed body controls also remove larger groups of code.
Evidence and Implementation
Records 019/024/034 measure simpler runtime calls; 038/042/043/046 measure direct call-site or wrapper refinements. For example, 034 reports iterative JSON recognition from 18.4 to 14.5 ms, and 046 reports 0.3–6% generated improvements. These are successive historical comparisons, not independently additive current effects.
Evidence records: 019, 021, 024, 034, 038, 042, 043, 046, 060.
Source owners: Rule.Plain classification,
parser.invokePlain,
plainCallFrame, plain call emission,
tsgen.Generator.plainCall.
The comparison policy distinguishes same-source controls, historical changes and full checkpoints.