# Common-Lisp-on-SX: conditions + restarts on delimited continuations The headline showcase is the **condition system**. Restarts are *resumable* exceptions — every other Lisp implementation reinvents this on host-stack unwind tricks. On SX restarts are textbook delimited continuations: `signal` walks the handler chain; `invoke-restart` resumes the captured continuation at the restart point. Same delcc primitive that powers Erlang actors, expressed as a different surface. End-state goal: ANSI Common Lisp subset with a working condition/restart system, CLOS multimethods (with `:before`/`:after`/`:around`), the LOOP macro, packages, and ~150 hand-written + classic programs. ## Scope decisions (defaults — override by editing before we spawn) - **Syntax:** ANSI Common Lisp surface. Read tables, dispatch macros (`#'`, `#(`, `#\`, `#:`, `#x`, `#b`, `#o`, ratios `1/3`). - **Conformance:** ANSI X3.226 *as a target*, not bug-for-bug SBCL/CCL. "Reads like CL, runs like CL." - **Test corpus:** custom + a curated slice of `ansi-test`. Plus classic programs: condition-system demo, restart-driven debugger, multiple-dispatch geometry, LOOP corpus. - **Out of scope:** compilation to native, FFI, sockets, threads, MOP class redefinition, full pathname/logical-pathname machinery, structures with `:include` deep customization. - **Packages:** simple — `defpackage`/`in-package`/`export`/`use-package`/`:cl`/`:cl-user`. No nicknames, no shadowing-import edge cases. ## Ground rules - **Scope:** only touch `lib/common-lisp/**` and `plans/common-lisp-on-sx.md`. Don't edit `spec/`, `hosts/`, `shared/`, or any other `lib//**`. CL primitives go in `lib/common-lisp/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 ``` Common Lisp source │ ▼ lib/common-lisp/reader.sx — tokenizer + reader (read macros, dispatch chars) │ ▼ lib/common-lisp/parser.sx — AST: forms, declarations, lambda lists │ ▼ lib/common-lisp/transpile.sx — AST → SX AST (entry: cl-eval-ast) │ ▼ lib/common-lisp/runtime.sx — special forms, condition system, CLOS, packages, BIFs ``` Core mapping: - **Symbol** = SX symbol with package prefix; package table is a flat dict. - **Cons cell** = SX pair via `cons`/`car`/`cdr`; lists native. - **Multiple values** = thread through `values`/`multiple-value-bind`; primary-value default for one-context callers. - **Block / return-from** = captured continuation; `return-from name v` invokes the block-named `^k`. - **Tagbody / go** = each tag is a continuation; `go tag` invokes it. - **Unwind-protect** = scope frame with a cleanup thunk fired on any non-local exit. - **Conditions / restarts** = layered handler chain on top of `handler-bind` + delcc. `signal` walks handlers; `invoke-restart` resumes a captured continuation. - **CLOS** = generic functions are dispatch tables on argument-class lists; method combination computed lazily; `call-next-method` is a continuation. - **Macros** = SX macros (sentinel-body) — defmacro lowers directly. ## Roadmap ### Phase 1 — reader + parser - [ ] Tokenizer: symbols (with package qualification `pkg:sym` / `pkg::sym`), numbers (int, float, ratio `1/3`, `#xFF`, `#b1010`, `#o17`), strings `"…"` with `\` escapes, characters `#\Space` `#\Newline` `#\a`, comments `;`, block comments `#| … |#` - [ ] Reader: list, dotted pair, quote `'`, function `#'`, quasiquote `` ` ``, unquote `,`, splice `,@`, vector `#(…)`, uninterned `#:foo`, nil/t literals - [ ] Parser: lambda lists with `&optional` `&rest` `&key` `&aux` `&allow-other-keys`, defaults, supplied-p variables - [ ] Unit tests in `lib/common-lisp/tests/read.sx` ### Phase 2 — sequential eval + special forms - [ ] `cl-eval-ast`: `quote`, `if`, `progn`, `let`, `let*`, `flet`, `labels`, `setq`, `setf` (subset), `function`, `lambda`, `the`, `locally`, `eval-when` - [ ] `block` + `return-from` via captured continuation - [ ] `tagbody` + `go` via per-tag continuations - [ ] `unwind-protect` cleanup frame - [ ] `multiple-value-bind`, `multiple-value-call`, `multiple-value-prog1`, `values`, `nth-value` - [ ] `defun`, `defparameter`, `defvar`, `defconstant`, `declaim`, `proclaim` (no-op) - [ ] Dynamic variables — `defvar`/`defparameter` produce specials; `let` rebinds via parameterize-style scope - [ ] 60+ tests in `lib/common-lisp/tests/eval.sx` ### Phase 3 — conditions + restarts (THE SHOWCASE) - [ ] `define-condition` — class hierarchy rooted at `condition`/`error`/`warning`/`simple-error`/`simple-warning`/`type-error`/`arithmetic-error`/`division-by-zero` - [ ] `signal`, `error`, `cerror`, `warn` — all walk the handler chain - [ ] `handler-bind` — non-unwinding handlers, may decline by returning normally - [ ] `handler-case` — unwinding handlers (delcc abort) - [ ] `restart-case`, `with-simple-restart`, `restart-bind` - [ ] `find-restart`, `invoke-restart`, `invoke-restart-interactively`, `compute-restarts` - [ ] `with-condition-restarts` — associate restarts with a specific condition - [ ] `*break-on-signals*`, `*debugger-hook*` (basic) - [ ] Classic programs in `lib/common-lisp/tests/programs/`: - [ ] `restart-demo.lisp` — division with `:use-zero` and `:retry` restarts - [ ] `parse-recover.lisp` — parser with skipped-token restart - [ ] `interactive-debugger.lisp` — ASCII REPL using `:debugger-hook` - [ ] `lib/common-lisp/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md` ### Phase 4 — CLOS - [ ] `defclass` with `:initarg`/`:initform`/`:accessor`/`:reader`/`:writer`/`:allocation` - [ ] `make-instance`, `slot-value`, `(setf slot-value)`, `with-slots`, `with-accessors` - [ ] `defgeneric` with `:method-combination` (standard, plus `+`, `and`, `or`) - [ ] `defmethod` with `:before` / `:after` / `:around` qualifiers - [ ] `call-next-method` (continuation), `next-method-p` - [ ] `class-of`, `find-class`, `slot-boundp`, `change-class` (basic) - [ ] Multiple dispatch — method specificity by argument-class precedence list - [ ] Built-in classes registered for tagged values (`integer`, `float`, `string`, `symbol`, `cons`, `null`, `t`) - [ ] Classic programs: - [ ] `geometry.lisp` — `intersect` generic dispatching on (point line), (line line), (line plane)… - [ ] `mop-trace.lisp` — `:before` + `:after` printing call trace ### Phase 5 — macros + LOOP + reader macros - [ ] `defmacro`, `macrolet`, `symbol-macrolet`, `macroexpand-1`, `macroexpand` - [ ] `gensym`, `gentemp` - [ ] `set-macro-character`, `set-dispatch-macro-character`, `get-macro-character` - [ ] **The LOOP macro** — iteration drivers (`for … in/across/from/upto/downto/by`, `while`, `until`, `repeat`), accumulators (`collect`, `append`, `nconc`, `count`, `sum`, `maximize`, `minimize`), conditional clauses (`if`/`when`/`unless`/`else`), termination (`finally`/`thereis`/`always`/`never`), `named` blocks - [ ] LOOP test corpus: 30+ tests covering all clause types ### Phase 6 — packages + stdlib drive - [ ] `defpackage`, `in-package`, `export`, `use-package`, `import`, `find-package` - [ ] Package qualification at the reader level — `cl:car`, `mypkg::internal` - [ ] `:common-lisp` (`:cl`) and `:common-lisp-user` (`:cl-user`) packages - [ ] Sequence functions — `mapcar`, `mapc`, `mapcan`, `reduce`, `find`, `find-if`, `position`, `count`, `every`, `some`, `notany`, `notevery`, `remove`, `remove-if`, `subst` - [ ] List ops — `assoc`, `getf`, `nth`, `last`, `butlast`, `nthcdr`, `tailp`, `ldiff` - [ ] String ops — `string=`, `string-upcase`, `string-downcase`, `subseq`, `concatenate` - [ ] FORMAT — basic directives `~A`, `~S`, `~D`, `~F`, `~%`, `~&`, `~T`, `~{...~}` (iteration), `~[...~]` (conditional), `~^` (escape), `~P` (plural) - [ ] Drive corpus to 200+ green ## Progress log _Newest first._ - _(none yet)_ ## Blockers - _(none yet)_