scrml.dev v0.7.1
Reference › Elements

<engine>

Declares a state machine — the canonical structural form for "UI as a fully-handled state machine."

Syntax

<engine for=TypeName initial=.Variant>
  <Variant rule=.OtherVariant />
  ...
</engine>

for= names the discriminating enum type. Every variant of that type must have a corresponding state-child or the compiler fires E-ENGINE-STATE-CHILD-MISSING. initial= picks the starting variant. rule= on a state-child constrains the legal transitions out of that variant. Compile-time enforcement where statically resolvable; runtime otherwise.

Worked example

A three-state traffic light. One enum type, one engine, three state-children, one button to cycle.

type Light:enum = { Red, Yellow, Green }

<engine for=Light initial=.Red>
  <Red    rule=.Green>
    Stop. Wait for green.
  </Red>
  <Green  rule=.Yellow>
    Go.
  </Green>
  <Yellow rule=.Red>
    Slow.
  </Yellow>
</engine>

<button onclick=@light.advance(.Green)>Cycle</button>
This block is canonical scrml — compile it with scrml compile to verify. For a running engine you can inspect, the showcase mounts a live state-machine app beside its source and its compiled transition diagram.

Semantics

  • Singleton per declaration site. One <engine> tag → one running machine for that scope. Cross-file mounting is via <EngineName/>; the singleton remains one instance per the outer-times-inner invariant per SPEC §51.0.K Machine Cohesion footnote.
  • Auto-declared variable. The engine type name auto-derives the handle, lowercase-first (Lightlight, per SPEC §51.0.C). Read the current variant via @light. Write via @light.advance(.Variant). Override the auto-name via var= if it would collide.
  • rule= is a contract. Compile-time when the target is statically resolvable; runtime otherwise. Transitions not listed fire E-ENGINE-INVALID-TRANSITION.
  • initial= is required at top-level engines. Omission fires W-ENGINE-INITIAL-MISSING. Engines without an initial state are usually accidents.

Errors this feature can fire

Edge cases

  • Self-writes are runtime no-ops. @light.advance(.Red) from inside .Red is silently discarded (v0.3 Option-d idempotent self-write semantics). NOT a rule= violation. The compiler emits W-ENGINE-SELF-WRITE-DETECTED to surface the no-op so it doesn't slip past code review.
  • <engine> inside a component body fires E-COMPONENT-ENGINE-SCOPE. Components are multi-instance; engines are singletons. They don't compose. Use a derived engine (derived=expr) or pass the current variant in as a prop and let the component render off the read.
  • Temporal triggers ride on the engine surface. <onTimeout after=… to=…/> as a child of a state-child is the engine temporal surface. NOT a separate timer system. Per SPEC §51.0.M.
  • Hierarchy is composite state-children. Nested <engine> declarations inside composite state-children preserve singleton semantics (outer-times-inner = one running inner instance). The history=true attribute on a composite state-child enables shallow history restoration via the .Variant.history target form. Per SPEC §51.0.N.

Related features

  • <match for=Type> — Tier 1 sibling. Pattern matching as a block; same exhaustiveness check, no active rules. bun scrml promote --match lifts to <engine>.
  • <onTransition> — effect on transition. Attributes: from=, to=, once, if=.
  • <onTimeout> — temporal trigger as a state-child child element.
  • derived=expr — derived engines. Compute the active variant from another engine's state.
  • <auth role=…> — per-role visibility. Wraps engine state-children that should only render for specific roles.

Availability

Surface Since Notes
<engine> shape S57 D2.8 (2026-05-04) Spec ratified; parser S70 (A1c Wave 1 C12-C15 in flight)
history= / internal:rule= / nested engines S67 (2026-05-08) Spec landed S68 1de05ef; parser+typer S70
<onTimeout> S70 A5-2 Spec § 51.0.M; runtime rides § 51.12
Idempotent self-write v0.3 Option-d (2026-05-12) W-ENGINE-SELF-WRITE-DETECTED surfaces the no-op at compile time

Specification

This page summarizes SPEC §51.0 (~21293-24415). The normative text lives at compiler/SPEC.md in the scrml repository. When this page disagrees with SPEC.md, SPEC.md is authoritative — surface the contradiction and we'll fix the docs.

Quick-lookup anchors: SPEC-INDEX.md entries — engine declaration, engines as singleton, auto-declared engine variable, engine mount position, engine rule= contract, idempotent self-write semantics.