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).
84 lines
6.5 KiB
Markdown
84 lines
6.5 KiB
Markdown
# ruby-on-sx loop agent (single agent, queue-driven)
|
|
|
|
Role: iterates `plans/ruby-on-sx.md` forever. Fibers via delcc is the headline showcase — `Fiber.new`/`Fiber.yield`/`Fiber.resume` are textbook delimited continuations with sugar, where MRI does it via C-stack swapping. Plus blocks/yield (lexical escape continuations, same shape as Smalltalk's non-local return), method_missing, and singleton classes.
|
|
|
|
```
|
|
description: ruby-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/ruby-on-sx.md`. Isolated worktree, forever, one commit per feature. Never push.
|
|
|
|
## Restart baseline — check before iterating
|
|
|
|
1. Read `plans/ruby-on-sx.md` — roadmap + Progress log.
|
|
2. `ls lib/ruby/` — pick up from the most advanced file.
|
|
3. If `lib/ruby/tests/*.sx` exist, run them. Green before new work.
|
|
4. If `lib/ruby/scoreboard.md` exists, that's your baseline.
|
|
|
|
## The queue
|
|
|
|
Phase order per `plans/ruby-on-sx.md`:
|
|
|
|
- **Phase 1** — tokenizer + parser. Keywords, identifier sigils (`@` ivar, `@@` cvar, `$` global), strings with interpolation, `%w[]`/`%i[]`, symbols, blocks `{|x| …}` and `do |x| … end`, splats, default args, method def
|
|
- **Phase 2** — object model + sequential eval. Class table, ancestor-chain dispatch, `super`, singleton classes, `method_missing` fallback, dynamic constant lookup
|
|
- **Phase 3** — blocks + procs + lambdas. Method captures escape continuation `^k`; `yield` / `return` / `break` / `next` / `redo` semantics; lambda strict arity vs proc lax
|
|
- **Phase 4** — **THE SHOWCASE**: fibers via delcc. `Fiber.new`/`Fiber.resume`/`Fiber.yield`/`Fiber.transfer`. Classic programs (generator, producer-consumer, tree-walk) green
|
|
- **Phase 5** — modules + mixins + metaprogramming. `include`/`prepend`/`extend`, `define_method`, `class_eval`/`instance_eval`, `respond_to?`/`respond_to_missing?`, hooks
|
|
- **Phase 6** — stdlib drive. `Enumerable` mixin, `Comparable`, Array/Hash/Range/String/Integer methods, 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/ruby/**` and `plans/ruby-on-sx.md`. Do **not** edit `spec/`, `hosts/`, `shared/`, other `lib/<lang>/` dirs, `lib/stdlib.sx`, or `lib/` root. Ruby primitives go in `lib/ruby/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.
|
|
|
|
## Ruby-specific gotchas
|
|
|
|
- **Block `return` vs lambda `return`.** Inside a block `{ ... return v }`, `return` invokes the *enclosing method's* escape continuation (non-local return). Inside a lambda `->(){ ... return v }`, `return` returns from the *lambda*. Don't conflate. Implement: blocks bind their `^method-k`; lambdas bind their own `^lambda-k`.
|
|
- **`break` from inside a block** invokes a different escape — the *iteration loop's* escape — and the loop returns the break-value. `next` is escape from current iteration, returns iteration value. `redo` re-enters current iteration without advancing.
|
|
- **Proc arity is lax.** `proc { |a, b, c| … }.call(1, 2)` ↦ `c = nil`. Lambda is strict — same call raises ArgumentError. Check arity at call site for lambdas only.
|
|
- **Block argument unpacking.** `[[1,2],[3,4]].each { |a, b| … }` — single Array arg auto-unpacks for blocks (not lambdas). One arg, one Array → unpack. Frequent footgun.
|
|
- **Method dispatch chain order:** prepended modules → class methods → included modules → superclass → BasicObject → method_missing. `super` walks from the *defining* class's position, not the receiver class's.
|
|
- **Singleton classes** are lazily allocated. Looking up the chain for an object passes through its singleton class first, then its actual class. `class << obj; …; end` opens the singleton.
|
|
- **`method_missing`** — fallback when ancestor walk misses. Receives `(name_symbol, *args, &blk)`. Pair with `respond_to_missing?` for `respond_to?` to also report true. Do **not** swallow NoMethodError silently.
|
|
- **Ivars are per-object dicts.** Reading an unset ivar yields `nil` and a warning (`-W`). Don't error.
|
|
- **Constant lookup** is first lexical (Module.nesting), then inheritance (Module.ancestors of the innermost class). Different from method lookup.
|
|
- **`Object#send`** invokes private and public methods alike; `Object#public_send` skips privates.
|
|
- **Class reopening.** `class Foo; def bar; …; end; end` plus a later `class Foo; def baz; …; end; end` adds methods to the same class. Class table lookups must be by-name, mutable; methods dict is mutable.
|
|
- **Fiber semantics.** `Fiber.new { |arg| … }` creates a fiber suspended at entry. First `Fiber.resume(v)` enters with `arg = v`. Inside, `Fiber.yield(w)` returns `w` to the resumer; the next `Fiber.resume(v')` returns `v'` to the yield site. End of block returns final value to last resumer; subsequent `Fiber.resume` raises FiberError.
|
|
- **`Fiber.transfer`** is symmetric — either side can transfer to the other; no resume/yield asymmetry. Implement on top of the same continuation pair, just don't enforce direction.
|
|
- **Symbols are interned.** `:foo == :foo` is identity. Use SX symbols.
|
|
- **Strings are mutable.** `s = "abc"; s << "d"; s == "abcd"`. Hash keys can be strings; hash dups string keys at insertion to be safe (or freeze them).
|
|
- **Truthiness:** only `false` and `nil` are falsy. `0`, `""`, `[]` are truthy.
|
|
- **Test corpus:** custom + curated RubySpec slice. Place programs in `lib/ruby/tests/programs/` with `.rb` 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/ruby-on-sx.md` inline.
|
|
- Short, factual commit messages (`ruby: Fiber.yield + Fiber.resume (+8)`).
|
|
- One feature per iteration. Commit. Log. Next.
|
|
|
|
Go. Read the plan; find first `[ ]`; implement.
|