Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup
Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests
WASM: regenerate sx_browser.bc.js from updated hs sources
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
10 KiB
10 KiB
Elixir-on-SX: Elixir on the CEK/VM
Compile Elixir source to SX AST; the existing CEK evaluator runs it. The natural companion
to lib/erlang/ — Elixir compiles to the BEAM and most of its runtime semantics are
Erlang's. The interesting parts are Elixir-specific: the macro system (quote/unquote),
the pipe operator |>, with expressions, defmodule/def/defp, protocol dispatch,
and the Stream lazy evaluation library.
End-state goal: core Elixir programs running, including modules, pattern matching, the
pipe operator, macros (quote/unquote/defmacro), protocols, and actor-style processes
reusing the Erlang runtime foundation.
Ground rules
- Scope: only touch
lib/elixir/**andplans/elixir-on-sx.md. Do not editspec/,hosts/,shared/, or otherlib/<lang>/. Reuselib/erlang/runtime functions where possible — import them, don't duplicate. - Shared-file issues go under "Blockers" below with a minimal repro; do not fix here.
- SX files: use
sx-treeMCP tools only. - Architecture: Elixir source → Elixir AST → SX AST. Reuse Erlang runtime for process/
message/pattern primitives; add Elixir-specific surface in
lib/elixir/. - Commits: one feature per commit. Keep
## Progress logupdated and tick boxes.
Architecture sketch
Elixir source text
│
▼
lib/elixir/tokenizer.sx — atoms (:atom), strings (""), charlists (''), sigils (~r, ~s etc.),
│ operators (|>, <>, ++, :::, etc.), do/end blocks
▼
lib/elixir/parser.sx — Elixir AST: defmodule, def/defp/defmacro, @attribute,
│ pattern matching, |> pipe, with, for comprehension, quote/unquote,
│ case/cond/if/unless, fn, receive, try/rescue/catch/after
▼
lib/elixir/transpile.sx — Elixir AST → SX AST
│
├── lib/erlang/runtime.sx (reused: processes, message passing, pattern match)
└── lib/elixir/runtime.sx — Elixir-specific: Kernel, String, Enum, Stream, Map,
List, Tuple, IO, protocol dispatch, macro expansion
Key semantic mappings (differences from Erlang):
defmodule M do ... end→ SXdefine-library+ module dict{:module "M" :fns {...}}def f(args) do body end→ named function in module dict, with pattern-match dispatch|>pipe → left-to-right function composition;a |> f(b)=f(a, b)with x <- expr, y <- expr2 do body else patterns end→ chained pattern match with early exitfor x <- list, filter, do: expr→ list comprehension (SXmap/filter)quote do expr end→ returns AST as SX list (homoiconic — Elixir AST IS SX-like)unquote(expr)→ evaluate expr and splice into surroundingquotedefmacro→ macro in module; expanded at compile time by calling the SX macro- Protocol → dict of implementations keyed by type name;
defprotocoldefines interface,defimplregisters an implementation Stream→ lazy sequences using SX promises/coroutines (Phase 9/4 of primitives)Agent/GenServer→ SX coroutine + message queue (similar to Erlang process model)
Roadmap
Phase 1 — tokenizer + parser
- Tokenizer: atoms (
:atom,:"atom with spaces"), strings (""), charlists (''), numbers (int, float, hex0xFF, octal0o77, binary0b11), booleans (true/false/nil), operators (|>,<>,++,--,:::,&&,||,!,..,<-,=~), sigils (~r/regex/,~s"string",~w(word list)), do/end blocks, keywords as argsf(key: val),@module_attribute - Parser:
- Module:
defmodule Name do ... end→ module AST with body - Functions:def f(pat) do body end,def f(pat) when guard do body end, multi-clausedef f(a) do ...; def f(b) do ...→ clause list -defp(private),defmacro,defmacrop-@doc,@moduledoc,@spec,@type,@behaviourmodule attributes -case expr do patterns end,cond do clauses end,if/unless-with x <- e, y <- e2, do: body, else: [pattern -> body]-for x <- list, filter, into: acc, do: exprcomprehension -fn pat -> body endanonymous function; capture&Module.fun/arity,&(&1 + 1)-receive do patterns after timeout -> body end-try do body rescue e -> ... catch type, val -> ... after ... end-quote do ... end,unquote(expr),unquote_splicing(list)-|>pipe chain:a |> f |> g(b)→g(f(a), b) - Tests in
lib/elixir/tests/parse.sx
Phase 2 — transpile: basic Elixir (no macros, no processes)
ex-eval-astentry- Arithmetic, string
<>, list++/--, comparison, boolean (and/or/not) - Pattern matching in
=, function heads,case— reuse Erlang pattern engine def/defp→ SXdefinewith clause dispatch (like Erlang function clauses)- Module as a dict of named functions;
ModuleName.function(args)dispatch |>pipe: desugara |> f(b, c)→f(a, b, c)at transpile timewithexpression: chain of<-bindings, short-circuit on mismatch toelseforcomprehension:for x <- list, filter do body end→map/filterfnanonymous functions,&capture formsif/unless/cond/case- String interpolation:
"Hello #{name}"→ string concat - Keyword lists
[key: val]→ SX list of{:key val}dicts; maps%{key: val}→ SX dict - Tuples
{a, b, c}→ SX list (or vector);elem/2,put_elem/3 - 40+ eval tests in
lib/elixir/tests/eval.sx
Phase 3 — macro system
quote do expr end→ returns Elixir AST as SX list structure (Elixir AST is 3-tuples{name, meta, args}— map to SX(list name meta args))unquote(expr)→ evaluate and splice into surroundingquoteunquote_splicing(list)→ splice list into surroundingquotedefmacro→ define a macro in the module; macro receives AST args, returns AST- Macro expansion: expand macros before transpiling (two-pass: collect defs, then expand)
use Module→ callsModule.__using__/1macro, injects code into callerimport Module→ bring functions into scope without prefixalias Module, as: M→ short name for module- Tests:
defmacro unless,defmacro my_if,useinjection,__MODULE__,__DIR__
Phase 4 — protocols
defprotocol P do @spec f(t) :: result end→ defines protocol dict + dispatch fndefimpl P, for: Type do def f(t) do ... end end→ register implementation- Protocol dispatch:
P.f(value)→ look up type of value, find implementation, call it - Built-in protocols:
Enumerable,Collectable,String.Chars,Inspect Enumerableimplementation for lists, maps, ranges — enablesEnum.*on custom typesderive— automatic protocol implementation for simple structs- Tests: custom type implementing
Enumerable,String.Chars, protocol fallback
Phase 5 — structs + behaviours
defstruct [:field1, field2: default]→ defines%ModuleName{}struct type Structs are maps with__struct__: ModuleNamekey + defined fields- Struct pattern matching:
%User{name: n} = user @behaviour Module→ declares behaviour callbacks; compile-time check@impl true/@impl BehaviourName→ marks function as behaviour implementation- Built-in behaviours:
GenServer,Supervisor,Agent,Task - Tests: struct creation, update syntax
%{struct | field: val}, behaviour callbacks
Phase 6 — processes + OTP patterns (reuses Erlang runtime)
spawn(fn -> ... end)/spawn(M, f, args)→ SX coroutine on scheduler Reuselib/erlang/process + message queue infrastructuresend(pid, msg)/receive do patterns end— already in Erlang runtimeGenServerbehaviour:start_link,call,cast,handle_call,handle_cast,handle_info,init— implement as SX macros expanding to process + message loopAgent— simple state wrapper over GenServer;Agent.start_link,get,updateTask— async computation;Task.async,Task.awaitSupervisor— child spec, restart strategy (one_for_one,one_for_all)- Tests: counter GenServer, bank account Agent, parallel Task, supervised worker
Phase 7 — standard library
Enum.*—map,filter,reduce,each,into,flat_map,zip,sort,sort_by,min_by,max_by,group_by,frequencies,count,any?,all?,find,take,drop,take_while,drop_while,chunk_every,chunk_by,flat_map_reduce,scan,uniq,uniq_by,member?,empty?,sum,productStream.*— lazy versions of Enum;Stream.map,Stream.filter,Stream.take,Stream.cycle,Stream.iterate,Stream.unfold,Stream.resourceUses SX promises (Phase 9) for lazinessString.*—length,upcase,downcase,trim,split,replace,contains?,starts_with?,ends_with?,slice,at,graphemes,codepoints,to_integer,to_float,pad_leading,pad_trailing,duplicate,match?Map.*—new,get,put,delete,update,merge,keys,values,to_list,from_struct,has_key?,filter,map,reject,take,dropList.*—first,last,flatten,zip,unzip,keystore,keyfind,wrap,duplicate,improper?,delete,insert_at,replace_atTuple.*—to_list,from_list,append,insert_at,delete_atInteger.*/Float.*—parse,to_string,digits,pow,is_odd?,is_even?IO.*—puts,gets,inspect,write,read→ SX IO performKernel.*— built-in functions:is_integer?,is_binary?,length,hd,tl,elem,put_elem,apply,raise,exit,inspectinspect/1/IO.inspect/2— debug printing usingInspectprotocol
Phase 8 — conformance target
- Vendor or hand-build 100+ Elixir program tests in
lib/elixir/tests/programs/ - Drive scoreboard
Blockers
(none yet)
Progress log
Newest first.
(awaiting phase 1)