Implement `(LOCAL)` as a host primitive that defers its effect to the
outer-interpreter compile state via two new `PendingAction` variants:
- `DeclareLocal(name)` — a non-sentinel `(LOCAL)` call with `u > 0`
appends the name to `compiling_locals` as an int local.
- `DeclareLocalEnd` — the `0 0 (LOCAL)` sentinel emits reverse-order
`ForthLocalSet` IR for the batch declared since the last sentinel,
reusing the same IR shape as the `{: ... :}` locals flow.
`local_batch_base` tracks where the current batch started; it is
saved/restored across nested compile frames and cleared on
`finish_colon_def`. Int-only, per spec — float locals remain `{F: :}`.
Also fix `\` per §6.2.2535: parse-and-discard must stop at the next
`\n`, not at `#TIB`. Under line-wrapped `evaluate` calls (common in
test files) the old behaviour consumed the trailing `;` of a multi-line
`:` definition, silently leaving state in compile mode.
Tighten `compliance.rs`: `load_file` now returns a line-failure count,
every prerequisite is asserted against `expected_load_failures(path)`,
and a new `load_file_whole` handles multi-line definitions (`DOES>`
split across lines in `errorreport.fth`) that the per-line loader
cannot stitch. Baselines document known gaps for `core.fr` (nested
`:`, SOURCE/>IN via EVALUATE), `coreexttest.fth` (SAVE-INPUT, `.(`
inside `[...]`), `exceptiontest.fth` (one garbled parse after
CATCH/THROW source stacking), and `toolstest.fth` (37 `\?`-guarded
lines where `SOURCE >IN ! DROP` fails to skip under per-line
`evaluate`). Each entry is a tech-debt ledger item, not an allowlist.
Regression tests: LT32 (the localstest case that silently skipped
before `(LOCAL)` existed), the `0 0 (LOCAL)` sentinel-only no-op, a
multi-line `:` followed by `VARIABLE` after a `\` comment, and a
direct `\` stops-at-newline case.
Incidental: clear two `implicit_clone` clippy lints in the RANDOM
determinism test (`.to_vec()` → `.clone()`).
Decouple ForthVM from wasmtime via a Runtime trait so the same outer
interpreter, compiler, and 200+ word definitions work on both native
(wasmtime) and browser (js-sys WebAssembly API) backends.
Runtime trait (runtime.rs):
- HostAccess trait for memory/global ops inside host function closures
- HostFn type: Box<dyn Fn(&mut dyn HostAccess) -> Result<()>>
- Runtime trait: memory, globals, table, instantiate, call, register
NativeRuntime (runtime_native.rs):
- Wraps wasmtime Engine/Store/Memory/Table/Global/Func
- CallerHostAccess bridges HostAccess to wasmtime Caller API
- Feature-gated behind "native" (default)
outer.rs refactor:
- ForthVM<R: Runtime> — generic over execution backend
- All 87 host functions converted from Func::new closures to HostFn
- All memory access via rt.mem_read/write_*, global access via rt.get/set_*
- Zero logic changes — pure API conversion
wafer-core feature gates:
- default = ["native"] includes wasmtime + all native modules
- Without "native": pure Rust only (outer, codegen, optimizer, dictionary)
Browser REPL (crates/web):
- WebRuntime: js-sys WebAssembly.Memory/Table/Global/Module/Instance
- WaferRepl: wasm-bindgen entry point (evaluate, data_stack, reset)
- WebAssembly.Function with Safari fallback (wrapper module)
- Frontend: dark terminal UI, word panel, init code editor, history
- Build: wasm-pack build --target web
All 452 tests pass (431 unit + 1 benchmark + 9 comparison + 11 compliance).
Three compile-time words for unstructured control flow:
- AHEAD: unconditional forward branch (code to THEN skipped)
- CS-PICK: duplicate control-flow stack entries (enables multi-exit loops)
- CS-ROLL: rotate control-flow stack entries (reorder IF/THEN resolution)
Also adds POSTPONE support for compile-time keywords (IF, UNTIL, etc.)
via a __CTRL__ host function and unified pending_actions queue.
Key design:
- LoopRestartIfFalse IR op desugars into nested If nodes for CS-PICK'd
BEGIN+UNTIL patterns (multiple backward branches in one loop)
- Flat Block/BranchIfFalse/EndBlock IR ops for CS-ROLL'd IF/THEN
patterns where structured If nesting would consume wrong flags
- First-iteration flag local for AHEAD-into-BEGIN patterns (PT8)
Enables 12th compliance test (compliance_tools): all 11+1 now pass.
Replace placeholder compliance tests with real harness that boots WAFER,
loads Gerry Jackson's test suite, and asserts 0 errors per word set.
Passing word sets (11/13):
Core, Core Plus, Core Ext, Exception, Double-Number, String,
Search-Order, Memory-Allocation, Programming-Tools, Facility, Locals
Not yet: File-Access (needs WASI), Floating-Point, Extended-Character
272 total tests (261 unit + 11 compliance)
Optimizing Forth 2012 compiler targeting WebAssembly with IR-based
compilation pipeline, multi-typed stack inference, subroutine threading,
and JIT/consolidation modes. Rust kernel with ~35 primitives and Forth
standard library for core/core-ext word sets.