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).
81 lines
6.3 KiB
Markdown
81 lines
6.3 KiB
Markdown
# common-lisp-on-sx loop agent (single agent, queue-driven)
|
|
|
|
Role: iterates `plans/common-lisp-on-sx.md` forever. Conditions + restarts on delimited continuations is the headline showcase — every other Lisp reinvents resumable exceptions on the host stack. On SX `signal`/`invoke-restart` is just a captured continuation. Plus CLOS, the LOOP macro, packages.
|
|
|
|
```
|
|
description: common-lisp-on-sx queue loop
|
|
subagent_type: general-purpose
|
|
run_in_background: true
|
|
isolation: worktree
|
|
```
|
|
|
|
## Prompt
|
|
|
|
You are the sole background agent working `/root/rose-ash/plans/common-lisp-on-sx.md`. Isolated worktree, forever, one commit per feature. Never push.
|
|
|
|
## Restart baseline — check before iterating
|
|
|
|
1. Read `plans/common-lisp-on-sx.md` — roadmap + Progress log.
|
|
2. `ls lib/common-lisp/` — pick up from the most advanced file.
|
|
3. If `lib/common-lisp/tests/*.sx` exist, run them. Green before new work.
|
|
4. If `lib/common-lisp/scoreboard.md` exists, that's your baseline.
|
|
|
|
## The queue
|
|
|
|
Phase order per `plans/common-lisp-on-sx.md`:
|
|
|
|
- **Phase 1** — reader + parser (read macros `#'` `'` `` ` `` `,` `,@` `#( … )` `#:` `#\char` `#xFF` `#b1010`, ratios, dispatch chars, lambda lists with `&optional`/`&rest`/`&key`/`&aux`)
|
|
- **Phase 2** — sequential eval + special forms (`let`/`let*`/`flet`/`labels`, `block`/`return-from`, `tagbody`/`go`, `unwind-protect`, multiple values, `setf` subset, dynamic variables)
|
|
- **Phase 3** — **THE SHOWCASE**: condition system + restarts. `define-condition`, `signal`/`error`/`cerror`/`warn`, `handler-bind` (non-unwinding), `handler-case` (unwinding), `restart-case`, `restart-bind`, `find-restart`/`invoke-restart`/`compute-restarts`, `with-condition-restarts`. Classic programs (restart-demo, parse-recover, interactive-debugger) green.
|
|
- **Phase 4** — CLOS: `defclass`, `defgeneric`, `defmethod` with `:before`/`:after`/`:around`, `call-next-method`, multiple dispatch
|
|
- **Phase 5** — macros + LOOP macro + reader macros
|
|
- **Phase 6** — packages + stdlib (sequence functions, FORMAT directives, drive corpus to 200+)
|
|
|
|
Within a phase, pick the checkbox that unlocks the most tests per effort.
|
|
|
|
Every iteration: implement → test → commit → tick `[ ]` → Progress log → next.
|
|
|
|
## Ground rules (hard)
|
|
|
|
- **Scope:** only `lib/common-lisp/**` and `plans/common-lisp-on-sx.md`. Do **not** edit `spec/`, `hosts/`, `shared/`, other `lib/<lang>/` dirs, `lib/stdlib.sx`, or `lib/` root. CL primitives go in `lib/common-lisp/runtime.sx`.
|
|
- **NEVER call `sx_build`.** 600s watchdog. If sx_server binary broken → Blockers entry, stop.
|
|
- **Shared-file issues** → plan's Blockers with minimal repro.
|
|
- **Delimited continuations** are in `lib/callcc.sx` + `spec/evaluator.sx` Step 5. `sx_summarise` spec/evaluator.sx first — 2300+ lines.
|
|
- **SX files:** `sx-tree` MCP tools ONLY. `sx_validate` after edits.
|
|
- **Worktree:** commit locally. Never push. Never touch `main`.
|
|
- **Commit granularity:** one feature per commit.
|
|
- **Plan file:** update Progress log + tick boxes every commit.
|
|
|
|
## Common-Lisp-specific gotchas
|
|
|
|
- **`handler-bind` is non-unwinding** — handlers can decline by returning normally, in which case `signal` keeps walking the chain. **`handler-case` is unwinding** — picking a handler aborts the protected form via a captured continuation. Don't conflate them.
|
|
- **Restarts are not handlers.** `restart-case` establishes named *resumption points*; `signal` runs handler code with restarts visible; the handler chooses a restart by calling `invoke-restart`, which abandons handler stack and resumes at the restart point. Two stacks: handlers walk down, restarts wait to be invoked.
|
|
- **`block` / `return-from`** is lexical. `block name … (return-from name v) …` captures `^k` once at entry; `return-from` invokes it. `return-from` to a name not in scope is an error (don't fall back to outer block).
|
|
- **`tagbody` / `go`** — each tag in tagbody is a continuation; `go tag` invokes it. Tags are lexical, can only target tagbodies in scope.
|
|
- **`unwind-protect`** runs cleanup on *any* non-local exit (return-from, throw, condition unwind). Implement as a scope frame fired by the cleanup machinery.
|
|
- **Multiple values**: primary-value-only contexts (function args, `if` test, etc.) drop extras silently. `values` produces multiple. `multiple-value-bind` / `multiple-value-call` consume them. Don't auto-list.
|
|
- **CLOS dispatch:** sort applicable methods by argument-list specificity (`subclassp` per arg, left-to-right); standard method combination calls primary methods most-specific-first via `call-next-method` chain. `:before` runs all before primaries; `:after` runs all after, in reverse-specificity. `:around` wraps everything.
|
|
- **`call-next-method`** is a *continuation* available only inside a method body. Implement as a thunk stored in a dynamic-extent variable.
|
|
- **Generalised reference (`setf`)**: `(setf (foo x) v)` ↦ `(setf-foo v x)`. Look up the setf-expander, not just a writer fn. `define-setf-expander` is mandatory for non-trivial places. Start with the symbolic / list / aref / slot-value cases.
|
|
- **Dynamic variables (specials):** `defvar`/`defparameter` mark a symbol as special. `let` over a special name *rebinds* in dynamic extent (use parameterize-style scope), not lexical.
|
|
- **Symbols are package-qualified.** Reader resolves `cl:car`, `mypkg::internal`, bare `foo` (current package). Internal vs external matters for `:` (one colon) reads.
|
|
- **`nil` is also `()` is also the empty list.** Same object. `nil` is also false. CL has no distinct unit value.
|
|
- **LOOP macro is huge.** Build incrementally — start with `for/in`, `for/from`, `collect`, `sum`, `count`, `repeat`. Add conditional clauses (`when`, `if`, `else`) once iteration drivers stable. `named` blocks + `return-from named` last.
|
|
- **Test corpus:** custom + curated `ansi-test` slice. Place programs in `lib/common-lisp/tests/programs/` with `.lisp` extension.
|
|
|
|
## General gotchas (all loops)
|
|
|
|
- SX `do` = R7RS iteration. Use `begin` for multi-expr sequences.
|
|
- `cond`/`when`/`let` clauses evaluate only the last expr.
|
|
- `type-of` on user fn returns `"lambda"`.
|
|
- Shell heredoc `||` gets eaten — escape or use `case`.
|
|
|
|
## Style
|
|
|
|
- No comments in `.sx` unless non-obvious.
|
|
- No new planning docs — update `plans/common-lisp-on-sx.md` inline.
|
|
- Short, factual commit messages (`common-lisp: handler-bind + 12 tests`).
|
|
- One feature per iteration. Commit. Log. Next.
|
|
|
|
Go. Read the plan; find first `[ ]`; implement.
|