Files
rose-ash/plans/common-lisp-on-sx.md
giles fb72c4ab9c sx-loops: add common-lisp, apl, ruby, tcl (12 slots)
Plans + briefings for four new language loops, each with a delcc/JIT
showcase that the runtime already supports natively:

- common-lisp — conditions + restarts on delimited continuations
- apl — rank-polymorphic primitives + 6 operators on the JIT
- ruby — fibers as delcc, blocks/yield as escape continuations
- tcl — uplevel/upvar via first-class env chain, the Dodekalogue

Launcher scripts now spawn 12 windows (was 8).
2026-04-25 09:25:30 +00:00

7.8 KiB

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/<lang>/**. 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.lispintersect 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)