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>
174 lines
10 KiB
Markdown
174 lines
10 KiB
Markdown
# 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/**` and `plans/elixir-on-sx.md`. Do **not** edit
|
|
`spec/`, `hosts/`, `shared/`, or other `lib/<lang>/`. Reuse `lib/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-tree` MCP 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 log` updated 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` → SX `define-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 exit
|
|
- `for x <- list, filter, do: expr` → list comprehension (SX `map`/`filter`)
|
|
- `quote do expr end` → returns AST as SX list (homoiconic — Elixir AST IS SX-like)
|
|
- `unquote(expr)` → evaluate expr and splice into surrounding `quote`
|
|
- `defmacro` → macro in module; expanded at compile time by calling the SX macro
|
|
- Protocol → dict of implementations keyed by type name; `defprotocol` defines interface,
|
|
`defimpl` registers 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, hex `0xFF`, octal `0o77`, binary `0b11`), booleans (`true`/`false`/`nil`),
|
|
operators (`|>`, `<>`, `++`, `--`, `:::`, `&&`, `||`, `!`, `..`, `<-`, `=~`),
|
|
sigils (`~r/regex/`, `~s"string"`, `~w(word list)`), do/end blocks, keywords as args
|
|
`f(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-clause `def f(a) do ...; def f(b) do ...` → clause list
|
|
- `defp` (private), `defmacro`, `defmacrop`
|
|
- `@doc`, `@moduledoc`, `@spec`, `@type`, `@behaviour` module 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: expr` comprehension
|
|
- `fn pat -> body end` anonymous 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-ast` entry
|
|
- [ ] Arithmetic, string `<>`, list `++`/`--`, comparison, boolean (`and`/`or`/`not`)
|
|
- [ ] Pattern matching in `=`, function heads, `case` — reuse Erlang pattern engine
|
|
- [ ] `def`/`defp` → SX `define` with clause dispatch (like Erlang function clauses)
|
|
- [ ] Module as a dict of named functions; `ModuleName.function(args)` dispatch
|
|
- [ ] `|>` pipe: desugar `a |> f(b, c)` → `f(a, b, c)` at transpile time
|
|
- [ ] `with` expression: chain of `<-` bindings, short-circuit on mismatch to `else`
|
|
- [ ] `for` comprehension: `for x <- list, filter do body end` → `map`/`filter`
|
|
- [ ] `fn` anonymous functions, `&` capture forms
|
|
- [ ] `if`/`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 surrounding `quote`
|
|
- [ ] `unquote_splicing(list)` → splice list into surrounding `quote`
|
|
- [ ] `defmacro` → 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` → calls `Module.__using__/1` macro, injects code into caller
|
|
- [ ] `import Module` → bring functions into scope without prefix
|
|
- [ ] `alias Module, as: M` → short name for module
|
|
- [ ] Tests: `defmacro unless`, `defmacro my_if`, `use` injection, `__MODULE__`, `__DIR__`
|
|
|
|
### Phase 4 — protocols
|
|
- [ ] `defprotocol P do @spec f(t) :: result end` → defines protocol dict + dispatch fn
|
|
- [ ] `defimpl 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`
|
|
- [ ] `Enumerable` implementation for lists, maps, ranges — enables `Enum.*` on custom types
|
|
- [ ] `derive` — 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__: ModuleName` key + 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
|
|
Reuse `lib/erlang/` process + message queue infrastructure
|
|
- [ ] `send(pid, msg)` / `receive do patterns end` — already in Erlang runtime
|
|
- [ ] `GenServer` behaviour: `start_link`, `call`, `cast`, `handle_call`, `handle_cast`,
|
|
`handle_info`, `init` — implement as SX macros expanding to process + message loop
|
|
- [ ] `Agent` — simple state wrapper over GenServer; `Agent.start_link`, `get`, `update`
|
|
- [ ] `Task` — async computation; `Task.async`, `Task.await`
|
|
- [ ] `Supervisor` — 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`, `product`
|
|
- [ ] `Stream.*` — lazy versions of Enum; `Stream.map`, `Stream.filter`, `Stream.take`,
|
|
`Stream.cycle`, `Stream.iterate`, `Stream.unfold`, `Stream.resource`
|
|
Uses SX promises (Phase 9) for laziness
|
|
- [ ] `String.*` — `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`, `drop`
|
|
- [ ] `List.*` — `first`, `last`, `flatten`, `zip`, `unzip`, `keystore`, `keyfind`,
|
|
`wrap`, `duplicate`, `improper?`, `delete`, `insert_at`, `replace_at`
|
|
- [ ] `Tuple.*` — `to_list`, `from_list`, `append`, `insert_at`, `delete_at`
|
|
- [ ] `Integer.*` / `Float.*` — `parse`, `to_string`, `digits`, `pow`, `is_odd?`, `is_even?`
|
|
- [ ] `IO.*` — `puts`, `gets`, `inspect`, `write`, `read` → SX IO perform
|
|
- [ ] `Kernel.*` — built-in functions: `is_integer?`, `is_binary?`, `length`, `hd`, `tl`,
|
|
`elem`, `put_elem`, `apply`, `raise`, `exit`, `inspect`
|
|
- [ ] `inspect/1` / `IO.inspect/2` — debug printing using `Inspect` protocol
|
|
|
|
### 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)_
|