Files
rose-ash/plans/erlang-on-sx.md
giles 0f67021aa3 plans: briefings + roadmaps for lua, prolog, forth, erlang, haskell
Five new guest-language plans mirroring the js-on-sx / hs-loop pattern, each
with a phased roadmap (Progress log + Blockers), a self-contained agent
briefing for respawning a long-lived loop, and a shared restore-all.sh that
snapshots state across all seven language loops.

Briefings bake in the lessons from today's stall debugging: never call
sx_build (600s watchdog), only touch lib/<lang>/** + own plan file, commit
every feature, update Progress log on each commit, route shared-file
issues to Blockers rather than fixing them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:16:45 +00:00

5.4 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 ( ) { } [ ] , ; . : :: ->
  • Parser: module declarations, -module/-export/-import attributes, function clauses with head patterns + guards + body
  • Expressions: literals, vars, calls, tuples {...}, lists [...|...], binaries <<...>>, if, case, receive, fun, try/catch, operators
  • 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.

  • (not started)

Blockers

  • (none yet)