tcl: merge loops/tcl — complete Tcl 8.6 subset (329 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s

Phases 1-6: Dodekalogue tokenizer/parser, eval engine, expr mini-language,
string/list/dict commands, proc + uplevel/upvar (the headline showcase),
catch/try/throw, namespaces + ensembles, generator coroutines, idiom corpus.

Resolved add/add conflicts by taking loops/tcl (the complete tested impl)
over the architecture branch's earlier prototype.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 11:36:59 +00:00
19 changed files with 5187 additions and 354 deletions

View File

@@ -11,7 +11,7 @@ isolation: worktree
## Prompt
You are the sole background agent working `/root/rose-ash/plans/tcl-on-sx.md`. Isolated worktree, forever, one commit per feature. Never push.
You are the sole background agent working `/root/rose-ash/plans/tcl-on-sx.md`. Isolated worktree, forever, one commit per feature. Push to `origin/loops/tcl` after every commit.
## Restart baseline — check before iterating
@@ -42,7 +42,7 @@ Every iteration: implement → test → commit → tick `[ ]` → Progress log
- **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`.
- **Worktree:** commit, then push to `origin/loops/tcl`. Never touch `main`.
- **Commit granularity:** one feature per commit.
- **Plan file:** update Progress log + tick boxes every commit.

View File

@@ -50,7 +50,7 @@ Core mapping:
## Roadmap
### Phase 1 — tokenizer + parser (the Dodekalogue)
- [ ] Tokenizer applying the 12 rules:
- [x] Tokenizer applying the 12 rules:
1. Commands separated by `;` or newlines
2. Words separated by whitespace within a command
3. Double-quoted words: `\` escapes + `[…]` + `${…}` + `$var` substitution
@@ -63,74 +63,76 @@ Core mapping:
10. Order of substitution is left-to-right, single-pass
11. Substitutions don't recurse — substituted text is not re-parsed
12. The result of any substitution is the value, not a new script
- [ ] Parser: script = list of commands; command = list of words; word = literal string + list of substitutions
- [ ] Unit tests in `lib/tcl/tests/parse.sx`
- [x] Parser: script = list of commands; command = list of words; word = literal string + list of substitutions
- [x] Unit tests in `lib/tcl/tests/parse.sx`
### Phase 2 — sequential eval + core commands
- [ ] `tcl-eval-script`: walk command list, dispatch each first-word into command table
- [ ] Core commands: `set`, `unset`, `incr`, `append`, `lappend`, `puts`, `gets`, `expr`, `if`, `while`, `for`, `foreach`, `switch`, `break`, `continue`, `return`, `error`, `eval`, `subst`, `format`, `scan`
- [ ] `expr` is its own mini-language — operator precedence, function calls (`sin`, `sqrt`, `pow`, `abs`, `int`, `double`), variable substitution, command substitution
- [ ] String commands: `string length`, `string index`, `string range`, `string compare`, `string match`, `string toupper`, `string tolower`, `string trim`, `string map`, `string repeat`, `string first`, `string last`, `string is`, `string cat`
- [ ] List commands: `list`, `lindex`, `lrange`, `llength`, `lreverse`, `lsearch`, `lsort`, `lsort -integer/-real/-dictionary`, `lreplace`, `linsert`, `concat`, `split`, `join`
- [ ] Dict commands: `dict create`, `dict get`, `dict set`, `dict unset`, `dict exists`, `dict keys`, `dict values`, `dict size`, `dict for`, `dict update`, `dict merge`
- [ ] 60+ tests in `lib/tcl/tests/eval.sx`
- [x] `tcl-eval-script`: walk command list, dispatch each first-word into command table
- [x] Core commands: `set`, `unset`, `incr`, `append`, `lappend`, `puts`, `gets`, `expr`, `if`, `while`, `for`, `foreach`, `switch`, `break`, `continue`, `return`, `error`, `eval`, `subst`, `format`, `scan`
- [x] `expr` is its own mini-language — operator precedence, function calls (`sin`, `sqrt`, `pow`, `abs`, `int`, `double`), variable substitution, command substitution
- [x] String commands: `string length`, `string index`, `string range`, `string compare`, `string match`, `string toupper`, `string tolower`, `string trim`, `string map`, `string repeat`, `string first`, `string last`, `string is`, `string cat`
- [x] List commands: `list`, `lindex`, `lrange`, `llength`, `lreverse`, `lsearch`, `lsort`, `lsort -integer/-real/-dictionary`, `lreplace`, `linsert`, `concat`, `split`, `join`
- [x] Dict commands: `dict create`, `dict get`, `dict set`, `dict unset`, `dict exists`, `dict keys`, `dict values`, `dict size`, `dict for`, `dict update`, `dict merge`
- [x] 60+ tests in `lib/tcl/tests/eval.sx`
### Phase 3 — proc + uplevel + upvar (THE SHOWCASE)
- [ ] `proc name args body` — register user-defined command; args supports defaults `{name default}` and rest `args`
- [ ] Frame stack: each proc call pushes a frame with locals dict; pop on return
- [ ] `uplevel ?level? script` — evaluate `script` in level-N frame's env; default level is 1 (caller). `#0` is global, `#1` is relative-1
- [ ] `upvar ?level? otherVar localVar ?…?` — alias localVar to a variable in level-N frame; reads/writes go through the alias
- [ ] `info level`, `info level N`, `info frame`, `info vars`, `info locals`, `info globals`, `info commands`, `info procs`, `info args`, `info body`
- [ ] `global var ?…?` — alias to global frame (sugar for `upvar #0 var var`)
- [ ] `variable name ?value?` — namespace-scoped global
- [ ] Classic programs in `lib/tcl/tests/programs/`:
- [ ] `for-each-line.tcl` — define your own loop construct using `uplevel`
- [ ] `assert.tcl` — assertion macro that reports caller's line
- [ ] `with-temp-var.tcl` — scoped variable rebind via `upvar`
- [ ] `lib/tcl/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md`
- [x] `proc name args body` — register user-defined command; args supports defaults `{name default}` and rest `args`
- [x] Frame stack: each proc call pushes a frame with locals dict; pop on return
- [x] `uplevel ?level? script` — evaluate `script` in level-N frame's env; default level is 1 (caller). `#0` is global, `#1` is relative-1
- [x] `upvar ?level? otherVar localVar ?…?` — alias localVar to a variable in level-N frame; reads/writes go through the alias
- [x] `info level`, `info level N`, `info frame`, `info vars`, `info locals`, `info globals`, `info commands`, `info procs`, `info args`, `info body`
- [x] `global var ?…?` — alias to global frame (sugar for `upvar #0 var var`)
- [x] `variable name ?value?` — namespace-scoped global
- [x] Classic programs in `lib/tcl/tests/programs/`:
- [x] `for-each-line.tcl` — define your own loop construct using `uplevel`
- [x] `assert.tcl` — assertion macro that reports caller's line
- [x] `with-temp-var.tcl` — scoped variable rebind via `upvar`
- [x] `lib/tcl/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md`
### Phase 4 — control flow + error handling
- [ ] `return -code (ok|error|return|break|continue|N) -errorinfo … -errorcode … -level N value`
- [ ] `catch script ?resultVar? ?optionsVar?` — runs script, returns code; sets resultVar to return value/message; optionsVar to the dict
- [ ] `try script ?on code var body ...? ?trap pattern var body...? ?finally body?`
- [ ] `throw type message`
- [ ] `error message ?info? ?code?`
- [ ] Stack-trace with `errorInfo` / `errorCode`
- [ ] 30+ tests in `lib/tcl/tests/error.sx`
- [x] `return -code (ok|error|return|break|continue|N) -errorinfo … -errorcode … -level N value`
- [x] `catch script ?resultVar? ?optionsVar?` — runs script, returns code; sets resultVar to return value/message; optionsVar to the dict
- [x] `try script ?on code var body ...? ?trap pattern var body...? ?finally body?`
- [x] `throw type message`
- [x] `error message ?info? ?code?`
- [x] Stack-trace with `errorInfo` / `errorCode`
- [x] 30+ tests in `lib/tcl/tests/error.sx`
### Phase 5 — namespaces + ensembles
- [ ] `namespace eval ns body`, `namespace current`, `namespace which`, `namespace import`, `namespace export`, `namespace forget`, `namespace delete`
- [ ] Qualified names: `::ns::cmd`, `::ns::var`
- [ ] Ensembles: `namespace ensemble create -map { sub1 cmd1 sub2 cmd2 }`
- [ ] `namespace path` for resolution chain
- [ ] `proc` and `variable` work inside namespaces
- [x] `namespace eval ns body`, `namespace current`, `namespace which`, `namespace import`, `namespace export`, `namespace forget`, `namespace delete`
- [x] Qualified names: `::ns::cmd`, `::ns::var`
- [x] Ensembles: `namespace ensemble create -map { sub1 cmd1 sub2 cmd2 }`
- [x] `namespace path` for resolution chain
- [x] `proc` and `variable` work inside namespaces
### Phase 6 — coroutines + drive corpus
- [ ] `coroutine name cmd ?args…?` — start a coroutine; future calls to `name` resume it
- [ ] `yield ?value?` — suspend, return value to resumer
- [ ] `yieldto cmd ?args…?` — symmetric transfer
- [ ] `coroutine` semantics built on fibers (same delcc primitive as Ruby fibers)
- [ ] Classic programs: `event-loop.tcl` — cooperative scheduler with multiple coroutines
- [ ] System: `clock seconds`, `clock format`, `clock scan` (subset)
- [ ] File I/O: `open`, `close`, `read`, `gets`, `puts -nonewline`, `flush`, `eof`, `seek`, `tell`
- [ ] Drive corpus to 150+ green
- [ ] Idiom corpus — `lib/tcl/tests/idioms.sx` covering classic Welch/Jones idioms
## SX primitive baseline
Use vectors for arrays; numeric tower + rationals for numbers; ADTs for tagged data;
coroutines for fibers; string-buffer for mutable string building; bitwise ops for bit
manipulation; multiple values for multi-return; promises for lazy evaluation; hash tables
for mutable associative storage; sets for O(1) membership; sequence protocol for
polymorphic iteration; gensym for unique symbols; char type for characters; string ports
+ read/write for reader protocols; regexp for pattern matching; bytevectors for binary
data; format for string templating.
- [x] `coroutine name cmd ?args…?` — start a coroutine; future calls to `name` resume it
- [x] `yield ?value?` — suspend, return value to resumer
- [x] `yieldto cmd ?args…?` — symmetric transfer
- [x] `coroutine` semantics built on fibers (same delcc primitive as Ruby fibers)
- [x] Classic programs: `event-loop.tcl` — cooperative scheduler with multiple coroutines
- [x] System: `clock seconds`, `clock format`, `clock scan` (subset)
- [x] File I/O: `open`, `close`, `read`, `gets`, `puts -nonewline`, `flush`, `eof`, `seek`, `tell`
- [x] Drive corpus to 150+ green
- [x] Idiom corpus — `lib/tcl/tests/idioms.sx` covering classic Welch/Jones idioms
## Progress log
_Newest first._
- _(none yet)_
- 2026-05-06: Phase 6 coroutines+clock+file+idioms — generator coroutines, clock/file stubs, 20 coroutine + 20 idiom tests, event-loop.tcl, 329 tests green
- 2026-05-06: Phase 5 namespaces+ensembles — namespace eval/current/which/exists/delete/import/ensemble, qualified names, 289 tests green (22 new namespace tests)
- 2026-05-06: Phase 4 error handling — catch/try/throw/return-code/errorinfo/errorcode, 267 tests green (39 new error tests)
- 2026-05-06: Phase 3 conformance.sh + classic programs — 3/3 PASS (for-each-line/assert/with-temp-var), 228 tests green
- 2026-05-06: Phase 3 proc+uplevel+upvar+info+global — frame stack, isolated proc scope, alias-following var access, 225 tests green (67 parse + 158 eval)
- 2026-05-06: Phase 2 dict commands — 13 subcommands (create/get/set/unset/exists/keys/values/size/for/update/merge/incr/append), 206 tests green (67 parse + 139 eval)
- 2026-05-06: Phase 2 list commands — 12 commands (list/lindex/lrange/llength/lreverse/lsearch/lsort/lreplace/linsert/concat/split/join), 182 tests green (67 parse + 115 eval)
- 2026-05-06: Phase 2 string commands — 16 subcommands (length/index/range/compare/match/toupper/tolower/trim/map/repeat/first/last/is/cat), 156 tests green (67 parse + 89 eval)
- 2026-05-06: Phase 2 expr mini-language — recursive descent parser, operator precedence, parens, unary ops, pow/sqrt/abs/max/min/int/double, 127 tests green (67 parse + 60 eval)
- 2026-04-26: Phase 2 core commands — if/while/for/foreach/switch/break/continue/return/error/unset/lappend/eval/expr + :code control flow, 107 tests green (67 parse + 40 eval)
- 2026-04-26: Phase 2 eval engine — `lib/tcl/runtime.sx`, tcl-eval-script + set/puts/incr/append, 87 tests green (67 parse + 20 eval)
- 2026-04-25: Phase 1 parser — `lib/tcl/parser.sx`, word-simple?/word-literal helpers, 67 tests green, commit 6ee05259
- 2026-04-25: Phase 1 tokenizer (Dodekalogue) — `lib/tcl/tokenizer.sx`, 52 tests green, commit 666e29d5
## Blockers