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).
8.4 KiB
8.4 KiB
Tcl-on-SX: uplevel/upvar = stack-walking delcc, everything-is-a-string
The headline showcase is uplevel/upvar — Tcl's superpower for defining your own control structures. uplevel evaluates a script in the caller's stack frame; upvar aliases a variable in the caller. On a normal language host this requires deep VM cooperation; on SX it falls out of the env-chain made first-class via captured continuations. Plus the Dodekalogue (12 rules), command-substitution everywhere, and "everything is a string" homoiconicity.
End-state goal: Tcl 8.6-flavoured subset, the Dodekalogue parser, namespaces, try/catch/return -code, coroutine (built on fibers), classic programs that show off uplevel-driven DSLs, ~150 hand-written tests.
Scope decisions (defaults — override by editing before we spawn)
- Syntax: Tcl 8.6 surface. The 12-rule Dodekalogue. Brace-quoted scripts deferred-evaluate; double-quoted ones substitute.
- Conformance: "Reads like Tcl, runs like Tcl." Slice of Tcl's own test suite, not full TCT.
- Test corpus: custom + curated
tcl-tests/slice. Plus classic programs: define-your-ownfor-each-line, expression-language compiler-in-Tcl, fiber-based event loop. - Out of scope: Tk, sockets beyond a stub, threads (mapped to
coroutineonly),package requireof binary loadables,dde/registryWindows shims, fullclock formatlocale support. - Channels:
putsandgetsonstdout/stdin/stderr;openon regular files; no async I/O beyond whatcoroutinegives.
Ground rules
- Scope: only touch
lib/tcl/**andplans/tcl-on-sx.md. Don't editspec/,hosts/,shared/, or any otherlib/<lang>/**. Tcl primitives go inlib/tcl/runtime.sx. - SX files: use
sx-treeMCP tools only. - Commits: one feature per commit. Keep
## Progress logupdated and tick roadmap boxes.
Architecture sketch
Tcl source
│
▼
lib/tcl/tokenizer.sx — the Dodekalogue: words, [..], ${..}, "..", {..}, ;, \n, \, #
│
▼
lib/tcl/parser.sx — list-of-words AST (script = list of commands; command = list of words)
│
▼
lib/tcl/transpile.sx — AST → SX AST (entry: tcl-eval-script)
│
▼
lib/tcl/runtime.sx — env stack, command table, uplevel/upvar, coroutines, BIFs
Core mapping:
- Value = string. Internally we cache a "shimmer" representation (list, dict, integer, double) for performance, but every value can be re-stringified.
- Variable = entry in current frame's env. Frames form a stack; level-0 is the global frame.
- Command = entry in command table; first word of any list dispatches into it. User-defined via
proc. Built-ins are SX functions registered in the table. - Frame =
{:locals (dict) :level n :parent frame}. Eachproccall pushes a frame; commands run in current frame. uplevel #N script= walk frame chain to absolute level N (or relative if no#); evaluate script in that frame's env.upvar [#N] varname localname= bindlocalnamein the current frame as an alias tovarnamein the level-N frame (env-chain delegate).return -code N= control flow as integers: 0=ok, 1=error, 2=return, 3=break, 4=continue.catchtraps any non-zero;tryadds named handlers.coroutine= fiber on top ofperform/cek-resume.yield/yieldtosuspend; calling the coroutine command resumes.- List / dict = list-shaped string ("element1 element2 …") with a cached parsed form. Modifications dirty the string cache.
Roadmap
Phase 1 — tokenizer + parser (the Dodekalogue)
- Tokenizer applying the 12 rules:
- Commands separated by
;or newlines - Words separated by whitespace within a command
- Double-quoted words:
\escapes +[…]+${…}+$varsubstitution - Brace-quoted words: literal, no substitution; brace count must balance
- Argument expansion:
{*}list - Command substitution:
[script]evaluates script, takes its return value - Variable substitution:
$name,${name},$arr(idx),$arr($i) - Backslash substitution:
\n,\t,\\,\xNN,\uNNNN,\<newline>continues - Comments:
#only at the start of a command - Order of substitution is left-to-right, single-pass
- Substitutions don't recurse — substituted text is not re-parsed
- The result of any substitution is the value, not a new script
- Commands separated by
- Parser: script = list of commands; command = list of words; word = literal string + list of substitutions
- 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 expris 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
Phase 3 — proc + uplevel + upvar (THE SHOWCASE)
proc name args body— register user-defined command; args supports defaults{name default}and restargs- Frame stack: each proc call pushes a frame with locals dict; pop on return
uplevel ?level? script— evaluatescriptin level-N frame's env; default level is 1 (caller).#0is global,#1is relative-1upvar ?level? otherVar localVar ?…?— alias localVar to a variable in level-N frame; reads/writes go through the aliasinfo level,info level N,info frame,info vars,info locals,info globals,info commands,info procs,info args,info bodyglobal var ?…?— alias to global frame (sugar forupvar #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 usinguplevelassert.tcl— assertion macro that reports caller's linewith-temp-var.tcl— scoped variable rebind viaupvar
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 valuecatch script ?resultVar? ?optionsVar?— runs script, returns code; sets resultVar to return value/message; optionsVar to the dicttry script ?on code var body ...? ?trap pattern var body...? ?finally body?throw type messageerror message ?info? ?code?- Stack-trace with
errorInfo/errorCode - 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 pathfor resolution chainprocandvariablework inside namespaces
Phase 6 — coroutines + drive corpus
coroutine name cmd ?args…?— start a coroutine; future calls tonameresume ityield ?value?— suspend, return value to resumeryieldto cmd ?args…?— symmetric transfercoroutinesemantics 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.sxcovering classic Welch/Jones idioms
Progress log
Newest first.
- (none yet)
Blockers
- (none yet)