Files
rose-ash/plans/erlang-on-sx.md
giles 99753580b4 Recover agent-loop progress: lua/prolog/forth/erlang/haskell phases 1-2
Salvaged from worktree-agent-* branches killed during sx-tree MCP outage:
- lua: tokenizer + parser + phase-2 transpile (~157 tests)
- prolog: tokenizer + parser + unification (72 tests, plan update lost to WIP)
- forth: phase-1 reader/interpreter + phase-2 colon/VARIABLE (134 tests)
- erlang: tokenizer + parser (114 tests)
- haskell: tokenizer + parse tests (43 tests)

Cherry-picked file contents only, not branch history, to avoid pulling in
unrelated ocaml-vm merge commits that were in those branches' bases.
2026-04-24 16:03:00 +00:00

6.5 KiB

Erlang-on-SX: actors on delimited continuations

The headline showcase for the SX runtime. Erlang is built around the one primitive — lightweight processes with mailboxes and selective receive — that delimited continuations implement natively. Most Erlang implementations ship a whole VM (BEAM) for this; on SX each process is a pair of continuations and the scheduler is ~50 lines of SX.

End-state goal: spawn a million processes, run the classic ring benchmark, plus a mini gen_server OTP subset and a test corpus of ~150 programs.

Scope decisions (defaults — override by editing before we spawn)

  • Syntax: Erlang/OTP 26 subset. No preprocessor, no parse transforms.
  • Conformance: not BEAM-compat. "Looks like Erlang, runs like Erlang, not byte-compatible." We care about semantics, not BEAM bug-for-bug.
  • Test corpus: custom — ring, ping-pong, fibonacci-server, bank-account-server, echo-server, plus ~100 hand-written tests for patterns/guards/BIFs. No ISO Common Test.
  • Binaries: basic bytes-lists only; full binary pattern matching deferred.
  • Hot code reload, distribution, NIFs: out of scope entirely.

Ground rules

  • Scope: only touch lib/erlang/** and plans/erlang-on-sx.md. Don't edit spec/, hosts/, shared/, lib/js/**, lib/hyperscript/**, lib/lua/**, lib/prolog/**, lib/forth/**, lib/haskell/**, lib/stdlib.sx, or lib/ root. Erlang primitives go in lib/erlang/runtime.sx.
  • SX files: use sx-tree MCP tools only.
  • Commits: one feature per commit. Keep ## Progress log updated and tick roadmap boxes.

Architecture sketch

Erlang source
    │
    ▼
lib/erlang/tokenizer.sx  — atoms, vars, tuples, lists, binaries, operators
    │
    ▼
lib/erlang/parser.sx     — AST: modules, functions with clauses, patterns, guards
    │
    ▼
lib/erlang/transpile.sx  — AST → SX AST (entry: erlang-eval-ast)
    │
    ▼
lib/erlang/runtime.sx    — scheduler, processes, mailboxes, BIFs

Core mapping:

  • Process = pair of delimited continuations (on-receive, on-resume) + mailbox list + pid + links
  • Scheduler = round-robin list of runnable processes; cooperative yield on receive
  • spawn = push a new process record, return its pid
  • send = append to target mailbox; if target is blocked on receive, resume its continuation
  • receive = selective — scan mailbox for first matching clause; if none, perform a suspend with the receive pattern; scheduler resumes when a matching message arrives
  • Pattern matching = SX case on tagged values; vars bind on match
  • Guards = side-effect-free predicate evaluated after unification
  • Immutable data = native
  • Links / monitors / exit signals = additional process-record fields, scheduler fires exit signals on death

Roadmap

Phase 1 — tokenizer + parser

  • Tokenizer: atoms (bare + single-quoted), variables (Uppercase/_-prefixed), numbers (int, float, 16#HEX), strings "...", chars $c, punct ( ) { } [ ] , ; . : :: ->62/62 tests
  • Parser: module declarations, -module/-export/-import attributes, function clauses with head patterns + guards + body — 52/52 tests
  • Expressions: literals, vars, calls, tuples {...}, lists [...|...], if, case, receive, fun, try/catch, operators, precedence
  • Binaries <<...>> — not yet parsed (deferred to Phase 6)
  • Unit tests in lib/erlang/tests/parse.sx

Phase 2 — sequential eval + pattern matching + BIFs

  • erlang-eval-ast: evaluate sequential expressions
  • Pattern matching (atoms, numbers, vars, tuples, lists, [H|T], underscore, bound-var re-match)
  • Guards: is_integer, is_atom, is_list, is_tuple, comparisons, arithmetic
  • BIFs: length/1, hd/1, tl/1, element/2, tuple_size/1, atom_to_list/1, list_to_atom/1, lists:map/2, lists:foldl/3, lists:reverse/1, io:format/1-2
  • 30+ tests in lib/erlang/tests/eval.sx

Phase 3 — processes + mailboxes + receive (THE SHOWCASE)

  • Scheduler in runtime.sx: runnable queue, pid counter, per-process state record
  • spawn/1, spawn/3, self/0
  • ! (send), receive ... end with selective pattern matching
  • receive ... after Ms -> ... timeout clause (use SX timer primitive)
  • exit/1, basic process termination
  • Classic programs in lib/erlang/tests/programs/:
    • ring.erl — N processes in a ring, pass a token around M times
    • ping_pong.erl — two processes exchanging messages
    • bank.erl — account server (deposit/withdraw/balance)
    • echo.erl — minimal server
    • fib_server.erl — compute fib on request
  • lib/erlang/conformance.sh + runner, scoreboard.json + scoreboard.md
  • Target: 5/5 classic programs + 1M-process ring benchmark runs
  • link/1, unlink/1, monitor/2, demonitor/1
  • Exit-signal propagation; trap_exit flag
  • try/catch/of/end

Phase 5 — modules + OTP-lite

  • -module(M). loading, M:F(...) calls across modules
  • gen_server behaviour (the big OTP win)
  • supervisor (simple one-for-one)
  • Registered processes: register/2, whereis/1

Phase 6 — the rest

  • List comprehensions [X*2 || X <- L]
  • Binary pattern matching <<A:8, B:16>>
  • ETS-lite (in-memory tables via SX dicts)
  • More BIFs — target 200+ test corpus green

Progress log

Newest first.

  • parser greenlib/erlang/parser.sx + parser-core.sx + parser-expr.sx + parser-module.sx. 52/52 in tests/parse.sx. Covers literals, tuples, lists (incl. [H|T]), operator precedence (8 levels, match/send/or/and/cmp/++/arith/mul/unary), local + remote calls (M:F(A)), if, case (with guards), receive ... after ... end, begin..end blocks, anonymous fun, try..of..catch..after..end with Class:Pattern catch clauses. Module-level: -module(M)., -export([...])., multi-clause functions with guards. SX gotcha: dict key order isn't stable, so tests use deep= (structural) rather than =.
  • tokenizer greenlib/erlang/tokenizer.sx + lib/erlang/tests/tokenize.sx. Covers atoms (bare, quoted, node@host), variables, integers (incl. 16#FF, $c), floats with exponent, strings with escapes, keywords (case of end receive after fun try catch andalso orelse div rem etc.), punct (( ) { } [ ] , ; . : :: -> <- <= => << >> | ||), ops (+ - * / = == /= =:= =/= < > =< >= ++ -- ! ?), % line comments. 62/62 green.

Blockers

  • (none yet)