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

136
lib/tcl/tests/coro.sx Normal file
View File

@@ -0,0 +1,136 @@
; Tcl-on-SX coroutine tests (Phase 6)
(define tcl-coro-pass 0)
(define tcl-coro-fail 0)
(define tcl-coro-failures (list))
(define
tcl-coro-assert
(fn
(label expected actual)
(if
(equal? expected actual)
(set! tcl-coro-pass (+ tcl-coro-pass 1))
(begin
(set! tcl-coro-fail (+ tcl-coro-fail 1))
(append!
tcl-coro-failures
(str label ": expected=" (str expected) " got=" (str actual)))))))
(define
tcl-run-coro-tests
(fn
()
(set! tcl-coro-pass 0)
(set! tcl-coro-fail 0)
(set! tcl-coro-failures (list))
(define interp (fn () (make-default-tcl-interp)))
(define run (fn (src) (tcl-eval-string (interp) src)))
(define
ok
(fn (label actual expected) (tcl-coro-assert label expected actual)))
; --- basic coroutine: yields one value ---
(ok "coro-single-yield"
(get (run "proc gen {} { yield hello }\ncoroutine g gen\ng") :result)
"hello")
; --- coroutine yields multiple values in order ---
(ok "coro-multi-yield-1"
(get (run "proc cnt {} { yield a; yield b; yield c }\ncoroutine c1 cnt\nc1") :result)
"a")
(ok "coro-multi-yield-2"
(get (run "proc cnt {} { yield a; yield b; yield c }\ncoroutine c1 cnt\nc1\nc1") :result)
"b")
(ok "coro-multi-yield-3"
(get (run "proc cnt {} { yield a; yield b; yield c }\ncoroutine c1 cnt\nc1\nc1\nc1") :result)
"c")
; --- coroutine with arguments to proc ---
(ok "coro-args"
(get (run "proc gen2 {n} { yield $n; yield [expr {$n + 1}] }\ncoroutine g2 gen2 10\ng2") :result)
"10")
(ok "coro-args-2"
(get (run "proc gen2 {n} { yield $n; yield [expr {$n + 1}] }\ncoroutine g2 gen2 10\ng2\ng2") :result)
"11")
; --- coroutine exhausted returns empty string ---
(ok "coro-exhausted"
(get (run "proc g3 {} { yield only }\ncoroutine c3 g3\nc3\nc3") :result)
"")
; --- yield in while loop ---
(ok "coro-while-loop-1"
(get (run "proc counter {max} { set i 0; while {$i < $max} { yield $i; incr i } }\ncoroutine cw counter 3\ncw") :result)
"0")
(ok "coro-while-loop-2"
(get (run "proc counter {max} { set i 0; while {$i < $max} { yield $i; incr i } }\ncoroutine cw counter 3\ncw\ncw") :result)
"1")
(ok "coro-while-loop-3"
(get (run "proc counter {max} { set i 0; while {$i < $max} { yield $i; incr i } }\ncoroutine cw counter 3\ncw\ncw\ncw") :result)
"2")
; --- collect all yields from coroutine ---
(ok "coro-collect-all"
(get
(run
"proc counter {n max} { while {$n < $max} { yield $n; incr n }; yield done }\ncoroutine gen1 counter 0 3\nset out {}\nfor {set i 0} {$i < 4} {incr i} { lappend out [gen1] }\nlindex $out 3")
:result)
"done")
; --- two independent coroutines ---
(ok "coro-two-independent"
(get
(run
"proc seq {start} { yield $start; yield [expr {$start+1}] }\ncoroutine ca seq 0\ncoroutine cb seq 10\nset r [ca]\nappend r \":\" [cb]")
:result)
"0:10")
; --- yield with no value returns empty string ---
(ok "coro-yield-no-val"
(get (run "proc g {} { yield }\ncoroutine cg g\ncg") :result)
"")
; --- clock seconds stub ---
(ok "clock-seconds"
(get (run "clock seconds") :result)
"0")
; --- clock milliseconds stub ---
(ok "clock-milliseconds"
(get (run "clock milliseconds") :result)
"0")
; --- clock format stub ---
(ok "clock-format"
(get (run "clock format 0") :result)
"Thu Jan 1 00:00:00 UTC 1970")
; --- file stubs ---
(ok "file-exists-stub"
(get (run "file exists /no/such/file") :result)
"0")
(ok "file-join"
(get (run "file join foo bar baz") :result)
"foo/bar/baz")
(ok "open-returns-channel"
(get (run "open /dev/null r") :result)
"file0")
(ok "eof-returns-1"
(get (run "set ch [open /dev/null r]\neof $ch") :result)
"1")
(dict
"passed"
tcl-coro-pass
"failed"
tcl-coro-fail
"failures"
tcl-coro-failures)))

192
lib/tcl/tests/error.sx Normal file
View File

@@ -0,0 +1,192 @@
; Tcl-on-SX error handling tests (Phase 4)
(define tcl-err-pass 0)
(define tcl-err-fail 0)
(define tcl-err-failures (list))
(define
tcl-err-assert
(fn
(label expected actual)
(if
(equal? expected actual)
(set! tcl-err-pass (+ tcl-err-pass 1))
(begin
(set! tcl-err-fail (+ tcl-err-fail 1))
(append!
tcl-err-failures
(str label ": expected=" (str expected) " got=" (str actual)))))))
(define
tcl-run-error-tests
(fn
()
(set! tcl-err-pass 0)
(set! tcl-err-fail 0)
(set! tcl-err-failures (list))
(define interp (fn () (make-default-tcl-interp)))
(define run (fn (src) (tcl-eval-string (interp) src)))
(define
ok
(fn (label actual expected) (tcl-err-assert label expected actual)))
(define
ok?
(fn (label condition) (tcl-err-assert label true condition)))
; --- catch basic ---
(ok "catch-ok-code" (get (run "catch {set x 1}") :result) "0")
(ok "catch-ok-result-var" (tcl-var-get (run "catch {set x hello} r") "r") "hello")
(ok "catch-ok-returns-0" (get (run "catch {set x hello} r") :result) "0")
; --- catch error ---
(ok "catch-error-code" (get (run "catch {error oops} r") :result) "1")
(ok "catch-error-result-var" (tcl-var-get (run "catch {error oops} r") "r") "oops")
; --- catch outer code stays 0 ---
(ok? "catch-outer-code-ok" (= (get (run "catch {error boom} r") :code) 0))
; --- catch code 2 (return) ---
(ok "catch-return-code" (get (run "proc p {} {return hello}\ncatch {p} r") :result) "0")
(ok "catch-return-val" (tcl-var-get (run "proc p {} {return hello}\ncatch {p} r") "r") "hello")
; --- catch code 3 (break) ---
(ok "catch-break-code" (get (run "catch {break} r") :result) "3")
; --- catch code 4 (continue) ---
(ok "catch-continue-code" (get (run "catch {continue} r") :result) "4")
; --- catch no resultVar ---
(ok "catch-no-var-ok" (get (run "catch {set x 1}") :result) "0")
(ok "catch-no-var-err" (get (run "catch {error boom}") :result) "1")
; --- catch with optsVar ---
(ok? "catch-opts-var-set"
(let
((i (run "catch {error boom} r opts")))
(not (equal? (tcl-var-get i "opts") ""))))
(ok? "catch-opts-contains-code"
(let
((i (run "catch {error boom} r opts")))
(let
((opts-str (tcl-var-get i "opts")))
(not (equal? (tcl-string-first "-code" opts-str 0) "-1")))))
; --- catch nested ---
(ok "catch-nested"
(tcl-var-get (run "catch {catch {error inner} r2} outer") "r2")
"inner")
; --- return -code error ---
(ok "return-code-error-code"
(get (run "catch {return -code error oops} r") :result)
"1")
(ok "return-code-error-val"
(tcl-var-get (run "catch {return -code error oops} r") "r")
"oops")
; --- return -code ok ---
(ok "return-code-ok"
(get (run "catch {return -code ok hello} r") :result)
"0")
(ok "return-code-ok-val"
(tcl-var-get (run "catch {return -code ok hello} r") "r")
"hello")
; --- return -code break ---
(ok "return-code-break"
(get (run "catch {return -code break} r") :result)
"3")
; --- return -code continue ---
(ok "return-code-continue"
(get (run "catch {return -code continue} r") :result)
"4")
; --- return -code numeric ---
(ok "return-code-numeric-5"
(get (run "catch {return -code 5 msg} r") :result)
"5")
; --- return plain still code 2 (catch sees raw return code) ---
(ok "return-plain-code"
(get (run "catch {return hello} r") :result)
"2")
(ok "return-plain-val"
(tcl-var-get (run "catch {return hello} r") "r")
"hello")
; --- proc return -code error ---
(ok "proc-return-code-error"
(get (run "proc p {} {return -code error bad}\ncatch {p} r") :result)
"1")
(ok "proc-return-code-error-val"
(tcl-var-get (run "proc p {} {return -code error bad}\ncatch {p} r") "r")
"bad")
; --- error with info/code args ---
(ok? "error-errorinfo-stored"
(let
((i (run "catch {error msg myinfo mycode} r")))
(= (get i :code) 0)))
; --- throw ---
(ok "throw-code" (get (run "catch {throw MYERR something} r") :result) "1")
(ok "throw-msg" (tcl-var-get (run "catch {throw MYERR something} r") "r") "something")
; --- try basic ok ---
(ok "try-ok-result"
(get (run "try {set x hello} on ok {r} {set r2 $r}") :result)
"hello")
; --- try on error ---
(ok "try-on-error-handled"
(get (run "try {error boom} on error {e} {set caught $e}") :result)
"boom")
(ok "try-on-error-var"
(tcl-var-get (run "try {error boom} on error {e} {set caught $e}") "caught")
"boom")
; --- try finally always runs ---
(ok "try-finally-ok"
(tcl-var-get (run "try {set x 1} finally {set done yes}") "done")
"yes")
(ok "try-finally-error"
(tcl-var-get (run "catch {try {error boom} finally {set done yes}} r") "done")
"yes")
; --- try on error + finally ---
(ok "try-error-finally"
(tcl-var-get
(run "try {error oops} on error {e} {set caught $e} finally {set cleaned yes}")
"cleaned")
"yes")
(ok "try-error-finally-caught"
(tcl-var-get
(run "try {error oops} on error {e} {set caught $e} finally {set cleaned yes}")
"caught")
"oops")
; --- try on ok and on error ---
(ok "try-multi-clause-ok"
(tcl-var-get
(run "try {set x 1} on ok {r} {set which ok} on error {e} {set which err}")
"which")
"ok")
(ok "try-multi-clause-err"
(tcl-var-get
(run "try {error boom} on ok {r} {set which ok} on error {e} {set which err}")
"which")
"err")
; --- catch preserves output ---
(ok "catch-output-preserved"
(get (run "puts -nonewline before\ncatch {puts -nonewline inside\nerror oops}\nputs -nonewline after")
:output)
"beforeinsideafter")
(dict
"passed"
tcl-err-pass
"failed"
tcl-err-fail
"failures"
tcl-err-failures)))

338
lib/tcl/tests/eval.sx Normal file
View File

@@ -0,0 +1,338 @@
; Tcl-on-SX eval tests
(define tcl-eval-pass 0)
(define tcl-eval-fail 0)
(define tcl-eval-failures (list))
(define
tcl-eval-assert
(fn
(label expected actual)
(if
(equal? expected actual)
(set! tcl-eval-pass (+ tcl-eval-pass 1))
(begin
(set! tcl-eval-fail (+ tcl-eval-fail 1))
(append!
tcl-eval-failures
(str label ": expected=" (str expected) " got=" (str actual)))))))
(define
tcl-run-eval-tests
(fn
()
(set! tcl-eval-pass 0)
(set! tcl-eval-fail 0)
(set! tcl-eval-failures (list))
(define interp (fn () (make-default-tcl-interp)))
(define run (fn (src) (tcl-eval-string (interp) src)))
(define
ok
(fn (label actual expected) (tcl-eval-assert label expected actual)))
(define
ok?
(fn (label condition) (tcl-eval-assert label true condition)))
(tcl-eval-assert "set-result" "hello" (get (run "set x hello") :result))
(tcl-eval-assert
"set-stored"
"hello"
(tcl-var-get (run "set x hello") "x"))
(tcl-eval-assert
"var-sub"
"hello"
(tcl-var-get (run "set x hello\nset y $x") "y"))
(tcl-eval-assert
"puts"
"world\n"
(get (run "set x world\nputs $x") :output))
(tcl-eval-assert
"puts-nonewline"
"hi"
(get (run "puts -nonewline hi") :output))
(tcl-eval-assert "incr" "6" (tcl-var-get (run "set x 5\nincr x") "x"))
(tcl-eval-assert
"incr-delta"
"8"
(tcl-var-get (run "set x 5\nincr x 3") "x"))
(tcl-eval-assert
"incr-neg"
"7"
(tcl-var-get (run "set x 10\nincr x -3") "x"))
(tcl-eval-assert
"append"
"foobar"
(tcl-var-get (run "set x foo\nappend x bar") "x"))
(tcl-eval-assert
"append-new"
"hello"
(tcl-var-get (run "append x hello") "x"))
(tcl-eval-assert
"cmdsub-result"
"6"
(get (run "set x 5\nset y [incr x]") :result))
(tcl-eval-assert
"cmdsub-y"
"6"
(tcl-var-get (run "set x 5\nset y [incr x]") "y"))
(tcl-eval-assert
"cmdsub-x"
"6"
(tcl-var-get (run "set x 5\nset y [incr x]") "x"))
(tcl-eval-assert
"multi-cmd"
"second"
(get (run "set x first\nset x second") :result))
(tcl-eval-assert "semi-x" "1" (tcl-var-get (run "set x 1; set y 2") "x"))
(tcl-eval-assert "semi-y" "2" (tcl-var-get (run "set x 1; set y 2") "y"))
(tcl-eval-assert
"braced-nosub"
"$x"
(tcl-var-get (run "set x 42\nset y {$x}") "y"))
(tcl-eval-assert
"concat-word"
"foobar"
(tcl-var-get (run "set x foo\nset y ${x}bar") "y"))
(tcl-eval-assert
"set-get"
"world"
(get (run "set x world\nset x") :result))
(tcl-eval-assert
"puts-channel"
"hello\n"
(get (run "puts stdout hello") :output))
(ok "if-true" (get (run "set x 0\nif {1} {set x 1}") :result) "1")
(ok "if-false" (get (run "set x 0\nif {0} {set x 1}") :result) "0")
(ok
"if-else-t"
(tcl-var-get (run "if {1} {set x yes} else {set x no}") "x")
"yes")
(ok
"if-else-f"
(tcl-var-get (run "if {0} {set x yes} else {set x no}") "x")
"no")
(ok
"if-cmp"
(tcl-var-get
(run "set x 5\nif {$x > 3} {set r big} else {set r small}")
"r")
"big")
(ok
"while"
(tcl-var-get
(run "set i 0\nset s 0\nwhile {$i < 5} {incr i\nincr s $i}")
"s")
"15")
(ok
"while-break"
(tcl-var-get
(run "set i 0\nwhile {1} {incr i\nif {$i == 3} {break}}")
"i")
"3")
(ok
"for"
(tcl-var-get
(run "set s 0\nfor {set i 1} {$i <= 5} {incr i} {incr s $i}")
"s")
"15")
(ok
"foreach"
(tcl-var-get (run "set s 0\nforeach x {1 2 3 4 5} {incr s $x}") "s")
"15")
(ok
"foreach-list"
(get (run "set acc \"\"\nforeach w {hello world} {append acc $w}") :result)
"helloworld")
(ok
"lappend"
(tcl-var-get (run "lappend lst a\nlappend lst b\nlappend lst c") "lst")
"a b c")
(ok?
"unset-gone"
(let
((i (run "set x 42\nunset x")))
(let
((frame (get i :frame)))
(nil? (get (get frame :locals) "x")))))
(ok "eval" (tcl-var-get (run "eval {set x hello}") "x") "hello")
(ok "expr-precedence" (get (run "expr {3 + 4 * 2}") :result) "11")
(ok "expr-parens" (get (run "expr {(3 + 4) * 2}") :result) "14")
(ok "expr-unary-minus" (get (run "expr {-5}") :result) "-5")
(ok "expr-unary-not-0" (get (run "expr {!0}") :result) "1")
(ok "expr-unary-not-1" (get (run "expr {!1}") :result) "0")
(ok "expr-power" (get (run "expr {2 ** 10}") :result) "1024")
(ok "expr-le" (get (run "expr {3 <= 3}") :result) "1")
(ok "expr-ge" (get (run "expr {4 >= 5}") :result) "0")
(ok "expr-and" (get (run "expr {1 && 1}") :result) "1")
(ok "expr-or" (get (run "expr {0 || 1}") :result) "1")
(ok "expr-var-sub" (get (run "set x 7\nexpr {$x * 3}") :result) "21")
(ok "expr-abs-neg" (get (run "expr {abs(-3)}") :result) "3")
(ok "expr-abs-pos" (get (run "expr {abs(5)}") :result) "5")
(ok "expr-pow-fn" (get (run "expr {pow(2, 8)}") :result) "256")
(ok "expr-max" (get (run "expr {max(3, 7)}") :result) "7")
(ok "expr-min" (get (run "expr {min(3, 7)}") :result) "3")
(ok "expr-sqrt-9" (get (run "expr {sqrt(9)}") :result) "3")
(ok "expr-sqrt-16" (get (run "expr {sqrt(16)}") :result) "4")
(ok "expr-mod" (get (run "expr {17 % 5}") :result) "2")
(ok "expr-nospace" (get (run "expr {3+4*2}") :result) "11")
(ok "expr-add" (get (run "expr {3 + 4}") :result) "7")
(ok "expr-cmp" (get (run "expr {5 > 3}") :result) "1")
(ok
"break-stops"
(tcl-var-get (run "set x 0\nwhile {1} {set x 1\nbreak\nset x 99}") "x")
"1")
(ok
"continue"
(tcl-var-get
(run
"set s 0\nfor {set i 1} {$i <= 5} {incr i} {if {$i == 3} {continue}\nincr s $i}")
"s")
"12")
(ok
"switch"
(tcl-var-get
(run "set x foo\nswitch $x {{foo} {set r yes} {bar} {set r no}}")
"r")
"yes")
(ok
"switch-default"
(tcl-var-get
(run "set x baz\nswitch $x {{foo} {set r yes} default {set r other}}")
"r")
"other")
(ok
"nested-if"
(tcl-var-get
(run
"set x 5\nif {$x > 10} {set r big} elseif {$x > 3} {set r mid} else {set r small}")
"r")
"mid")
(ok "str-length" (get (run "string length hello") :result) "5")
(ok "str-length-empty" (get (run "string length {}") :result) "0")
(ok "str-index" (get (run "string index hello 1") :result) "e")
(ok "str-index-oob" (get (run "string index hello 99") :result) "")
(ok "str-range" (get (run "string range hello 1 3") :result) "ell")
(ok "str-range-clamp" (get (run "string range hello 3 99") :result) "lo")
(ok "str-compare-eq" (get (run "string compare abc abc") :result) "0")
(ok "str-compare-lt" (get (run "string compare abc abd") :result) "-1")
(ok "str-compare-gt" (get (run "string compare b a") :result) "1")
(ok "str-match-star" (get (run "string match h*o hello") :result) "1")
(ok "str-match-q" (get (run "string match h?llo hello") :result) "1")
(ok "str-match-no" (get (run "string match h*x hello") :result) "0")
(ok "str-toupper" (get (run "string toupper hello") :result) "HELLO")
(ok "str-tolower" (get (run "string tolower WORLD") :result) "world")
(ok "str-trim" (get (run "string trim { hi }") :result) "hi")
(ok "str-trimleft" (get (run "string trimleft { hi }") :result) "hi ")
(ok "str-trimright" (get (run "string trimright { hi }") :result) " hi")
(ok "str-trim-chars" (get (run "string trim {xxhelloxx} x") :result) "hello")
(ok "str-map" (get (run "string map {a X b Y} {abc}") :result) "XYc")
(ok "str-repeat" (get (run "string repeat ab 3") :result) "ababab")
(ok "str-first" (get (run "string first ll hello") :result) "2")
(ok "str-first-miss" (get (run "string first z hello") :result) "-1")
(ok "str-last" (get (run "string last l hello") :result) "3")
(ok "str-is-int" (get (run "string is integer 42") :result) "1")
(ok "str-is-not-int" (get (run "string is integer foo") :result) "0")
(ok "str-is-alpha" (get (run "string is alpha hello") :result) "1")
(ok "str-is-alpha-no" (get (run "string is alpha hello1") :result) "0")
(ok "str-is-boolean" (get (run "string is boolean true") :result) "1")
(ok "str-cat" (get (run "string cat foo bar baz") :result) "foobarbaz")
; --- list command tests ---
(ok "list-simple" (get (run "list a b c") :result) "a b c")
(ok "list-brace-elem" (get (run "list {a b} c") :result) "{a b} c")
(ok "list-empty" (get (run "list") :result) "")
(ok "lindex-1" (get (run "lindex {a b c} 1") :result) "b")
(ok "lindex-0" (get (run "lindex {a b c} 0") :result) "a")
(ok "lindex-oob" (get (run "lindex {a b c} 5") :result) "")
(ok "lrange" (get (run "lrange {a b c d} 1 2") :result) "b c")
(ok "lrange-full" (get (run "lrange {a b c} 0 end") :result) "a b c")
(ok "llength" (get (run "llength {a b c}") :result) "3")
(ok "llength-empty" (get (run "llength {}") :result) "0")
(ok "lreverse" (get (run "lreverse {1 2 3}") :result) "3 2 1")
(ok "lsearch-found" (get (run "lsearch {a b c} b") :result) "1")
(ok "lsearch-missing" (get (run "lsearch {a b c} z") :result) "-1")
(ok "lsearch-exact" (get (run "lsearch -exact {foo bar} foo") :result) "0")
(ok "lsort-asc" (get (run "lsort {banana apple cherry}") :result) "apple banana cherry")
(ok "lsort-int" (get (run "lsort -integer {10 2 30 5}") :result) "2 5 10 30")
(ok "lsort-dec" (get (run "lsort -decreasing {c a b}") :result) "c b a")
(ok "lreplace" (get (run "lreplace {a b c d} 1 2 X Y") :result) "a X Y d")
(ok "linsert" (get (run "linsert {a b c} 1 X Y") :result) "a X Y b c")
(ok "linsert-end" (get (run "linsert {a b} end Z") :result) "a b Z")
(ok "concat" (get (run "concat {a b} {c d}") :result) "a b c d")
(ok "split-sep" (get (run "split {a:b:c} :") :result) "a b c")
(ok "split-ws" (get (run "split {a b c}") :result) "a b c")
(ok "join-sep" (get (run "join {a b c} -") :result) "a-b-c")
(ok "join-default" (get (run "join {a b c}") :result) "a b c")
(ok "list-var" (get (run "set L {x y z}\nllength $L") :result) "3")
; --- dict command tests ---
(ok "dict-create" (get (run "dict create a 1 b 2") :result) "a 1 b 2")
(ok "dict-create-empty" (get (run "dict create") :result) "")
(ok "dict-get" (get (run "dict get {a 1 b 2} a") :result) "1")
(ok "dict-get-b" (get (run "dict get {a 1 b 2} b") :result) "2")
(ok "dict-exists-yes" (get (run "dict exists {a 1 b 2} a") :result) "1")
(ok "dict-exists-no" (get (run "dict exists {a 1 b 2} z") :result) "0")
(ok "dict-set-new" (get (run "set d {}\ndict set d x 42") :result) "x 42")
(ok "dict-set-update" (get (run "set d {a 1 b 2}\ndict set d a 99") :result) "a 99 b 2")
(ok "dict-set-stored" (tcl-var-get (run "set d {a 1}\ndict set d b 2") "d") "a 1 b 2")
(ok "dict-unset" (get (run "set d {a 1 b 2}\ndict unset d a") :result) "b 2")
(ok "dict-unset-stored" (tcl-var-get (run "set d {a 1 b 2}\ndict unset d a") "d") "b 2")
(ok "dict-keys" (get (run "dict keys {a 1 b 2}") :result) "a b")
(ok "dict-keys-pattern" (get (run "dict keys {abc 1 abd 2 xyz 3} ab*") :result) "abc abd")
(ok "dict-values" (get (run "dict values {a 1 b 2}") :result) "1 2")
(ok "dict-size" (get (run "dict size {a 1 b 2 c 3}") :result) "3")
(ok "dict-size-empty" (get (run "dict size {}") :result) "0")
(ok "dict-for" (tcl-var-get (run "set acc {}\ndict for {k v} {a 1 b 2} {append acc $k$v}") "acc") "a1b2")
(ok "dict-merge-disjoint" (get (run "dict merge {a 1} {b 2}") :result) "a 1 b 2")
(ok "dict-merge-overlap" (get (run "dict merge {a 1 b 2} {b 99}") :result) "a 1 b 99")
(ok "dict-incr-existing" (get (run "set d {x 5}\ndict incr d x") :result) "x 6")
(ok "dict-incr-delta" (get (run "set d {x 5}\ndict incr d x 3") :result) "x 8")
(ok "dict-incr-missing" (get (run "set d {}\ndict incr d n") :result) "n 1")
(ok "dict-append" (get (run "set d {x hello}\ndict append d x _hi") :result) "x hello_hi")
(ok "dict-append-new" (get (run "set d {}\ndict append d k val") :result) "k val")
; --- proc tests ---
(ok "proc-basic" (get (run "proc add {a b} {expr {$a + $b}}\nadd 3 4") :result) "7")
(ok "proc-return" (get (run "proc greet {name} {set msg \"hi $name\"\nreturn $msg}\ngreet World") :result) "hi World")
(ok "proc-factorial" (get (run "proc factorial {n} {if {$n <= 1} {return 1}\nexpr {$n * [factorial [expr {$n - 1}]]}}\nfactorial 5") :result) "120")
(ok "proc-args" (get (run "proc sum args {set t 0\nforeach x $args {incr t $x}\nreturn $t}\nsum 1 2 3 4") :result) "10")
(ok "proc-isolated" (get (run "set x outer\nproc p {} {set x inner\nreturn $x}\np") :result) "inner")
(ok "proc-caller-unchanged" (tcl-var-get (run "set x outer\nproc p {} {set x inner\nreturn $x}\np\nset dummy 1") "x") "outer")
(ok "proc-output" (get (run "proc hello {} {puts -nonewline hi}\nhello") :output) "hi")
; --- upvar tests ---
(ok "upvar-incr" (tcl-var-get (run "proc incr2 {varname} {upvar 1 $varname v\nincr v}\nset counter 10\nincr2 counter\nset counter") "counter") "11")
(ok "upvar-double" (tcl-var-get (run "proc double-it {varname} {upvar 1 $varname x\nset x [expr {$x * 2}]}\nset val 5\ndouble-it val\nset val") "val") "10")
(ok "upvar-result" (get (run "proc double-it {varname} {upvar 1 $varname x\nset x [expr {$x * 2}]}\nset val 5\ndouble-it val\nset val") :result) "10")
; --- uplevel tests ---
(ok "uplevel-set" (tcl-var-get (run "proc setvar {name val} {uplevel 1 \"set $name $val\"}\nsetvar x 99\nset x") "x") "99")
(ok "uplevel-get" (get (run "proc getvar {name} {uplevel 1 \"set $name\"}\nset y 77\ngetvar y") :result) "77")
; --- global tests ---
(ok "global-read" (get (run "set g 100\nproc getg {} {global g\nreturn $g}\ngetg") :result) "100")
(ok "global-write" (tcl-var-get (run "set g 0\nproc bumping {} {global g\nincr g}\nbumping\nbumping\nset g") "g") "2")
; --- info tests ---
(ok "info-level-0" (get (run "info level") :result) "0")
(ok "info-level-proc" (get (run "proc p {} {info level}\np") :result) "1")
(ok "info-procs" (let ((r (get (run "proc myfn {} {}\ninfo procs") :result))) (contains? (tcl-list-split r) "myfn")) true)
(ok "info-args" (get (run "proc add {a b} {expr {$a+$b}}\ninfo args add") :result) "a b")
(ok "info-commands-has-set" (let ((r (get (run "info commands") :result))) (contains? (tcl-list-split r) "set")) true)
; --- classic programs ---
(ok
"classic-for-each-line"
(get
(run "proc for-each-line {var lines body} {\n foreach item $lines {\n uplevel 1 [list set $var $item]\n uplevel 1 $body\n }\n}\nset total 0\nfor-each-line line {hello world foo} {\n incr total [string length $line]\n}\nset total")
:result)
"13")
(ok
"classic-assert"
(get
(run "proc assert {expr_str} {\n set result [uplevel 1 [list expr $expr_str]]\n if {!$result} {\n error \"Assertion failed: $expr_str\"\n }\n}\nset x 42\nassert {$x == 42}\nassert {$x > 0}\nset x 10\nassert {$x < 100}\nset x")
:result)
"10")
(ok
"classic-with-temp-var"
(get
(run "proc with-temp-var {varname tempval body} {\n upvar 1 $varname v\n set saved $v\n set v $tempval\n uplevel 1 $body\n set v $saved\n}\nset x 100\nwith-temp-var x 999 {\n set captured $x\n}\nlist $x $captured")
:result)
"100 999")
(dict
"passed"
tcl-eval-pass
"failed"
tcl-eval-fail
"failures"
tcl-eval-failures)))

193
lib/tcl/tests/idioms.sx Normal file
View File

@@ -0,0 +1,193 @@
; Tcl-on-SX idiom corpus (Phase 6)
; Classic Tcl idioms covering lists, dicts, procs, patterns
(define tcl-idiom-pass 0)
(define tcl-idiom-fail 0)
(define tcl-idiom-failures (list))
(define
tcl-idiom-assert
(fn
(label expected actual)
(if
(equal? expected actual)
(set! tcl-idiom-pass (+ tcl-idiom-pass 1))
(begin
(set! tcl-idiom-fail (+ tcl-idiom-fail 1))
(append!
tcl-idiom-failures
(str label ": expected=" (str expected) " got=" (str actual)))))))
(define
tcl-run-idiom-tests
(fn
()
(set! tcl-idiom-pass 0)
(set! tcl-idiom-fail 0)
(set! tcl-idiom-failures (list))
(define interp (fn () (make-default-tcl-interp)))
(define run (fn (src) (tcl-eval-string (interp) src)))
(define
ok
(fn (label actual expected) (tcl-idiom-assert label expected actual)))
; 1. lmap idiom: accumulate mapped values with foreach+lappend
(ok "idiom-lmap"
(get
(run "set result {}\nforeach x {1 2 3} { lappend result [expr {$x * $x}] }\nset result")
:result)
"1 4 9")
; 2. Recursive list flatten
(ok "idiom-flatten"
(get
(run
"proc flatten {lst} { set out {}\n foreach item $lst {\n if {[llength $item] > 1} {\n foreach sub [flatten $item] { lappend out $sub }\n } else {\n lappend out $item\n }\n }\n return $out\n}\nflatten {1 {2 3} {4 {5 6}}}")
:result)
"1 2 3 4 5 6")
; 3. String builder accumulator
(ok "idiom-string-builder"
(get
(run "set buf \"\"\nforeach w {Hello World Tcl} { append buf $w \" \" }\nstring trimright $buf")
:result)
"Hello World Tcl")
; 4. Default parameter via info exists
(ok "idiom-default-param"
(get
(run "if {![info exists x]} { set x 42 }\nset x")
:result)
"42")
; 5. Association list lookup (parallel key/value lists)
(ok "idiom-alist-lookup"
(get
(run
"set keys {a b c}\nset vals {10 20 30}\nset idx [lsearch $keys b]\nlindex $vals $idx")
:result)
"20")
; 6. Proc with optional args via args
(ok "idiom-optional-args"
(get
(run
"proc greet {name args} {\n set greeting \"Hello\"\n if {[llength $args] > 0} { set greeting [lindex $args 0] }\n return \"$greeting $name\"\n}\ngreet World Hi")
:result)
"Hi World")
; 7. Builder pattern: dict create from args
(ok "idiom-dict-builder"
(get
(run
"proc build-dict {args} { dict create {*}$args }\ndict get [build-dict name Alice age 30] name")
:result)
"Alice")
; 8. Loop with index using array
(ok "idiom-loop-with-index"
(get
(run
"set i 0\nforeach x {a b c} { set arr($i) $x; incr i }\nset arr(1)")
:result)
"b")
; 9. String reverse via split+lreverse+join
(ok "idiom-string-reverse"
(get
(run
"set s hello\nset chars [split $s \"\"]\nset rev [lreverse $chars]\njoin $rev \"\"")
:result)
"olleh")
; 10. Number to padded string
(ok "idiom-number-format"
(get (run "format \"%05d\" 42") :result)
"00042")
; 11. Dict comprehension pattern
(ok "idiom-dict-comprehension"
(get
(run
"set squares {}\nforeach n {1 2 3 4} { dict set squares $n [expr {$n * $n}] }\ndict get $squares 3")
:result)
"9")
; 12. Stack ADT using list: push/pop
(ok "idiom-stack"
(get
(run
"proc stack-push {stackvar val} { upvar $stackvar s; lappend s $val }\nproc stack-pop {stackvar} { upvar $stackvar s; set val [lindex $s end]; set s [lrange $s 0 end-1]; return $val }\nset stk {}\nstack-push stk 10\nstack-push stk 20\nstack-push stk 30\nstack-pop stk")
:result)
"30")
; 13. Queue ADT using list: enqueue/dequeue
(ok "idiom-queue"
(get
(run
"proc q-enq {qvar val} { upvar $qvar q; lappend q $val }\nproc q-deq {qvar} { upvar $qvar q; set val [lindex $q 0]; set q [lrange $q 1 end]; return $val }\nset q {}\nq-enq q alpha\nq-enq q beta\nq-enq q gamma\nq-deq q")
:result)
"alpha")
; 14. Pipeline via proc chaining
(ok "idiom-pipeline"
(get
(run
"proc double {x} { expr {$x * 2} }\nproc add1 {x} { expr {$x + 1} }\nproc pipeline {val procs} { foreach p $procs { set val [$p $val] }; return $val }\npipeline 5 {double add1 double}")
:result)
"22")
; 15. Memoize pattern using dict (simple cache, not recursive)
(ok "idiom-memoize"
(get
(run
"set cache {}\nproc cached-square {n} { global cache\n if {[dict exists $cache $n]} { return [dict get $cache $n] }\n set r [expr {$n * $n}]\n dict set cache $n $r\n return $r\n}\nset a [cached-square 7]\nset b [cached-square 7]\nset c [cached-square 8]\nexpr {$a == $b && $c == 64}")
:result)
"1")
; 16. Simple expression evaluator in Tcl (recursive descent)
(ok "idiom-recursive-eval"
(get
(run
"proc calc {expr} { return [::tcl::mathop::+ 0 [expr $expr]] }\nexpr {3 + 4 * 2}")
:result)
"11")
; 17. Apply proc to each pair in a dict
(ok "idiom-dict-for"
(get
(run
"set d [dict create a 1 b 2 c 3]\nset total 0\ndict for {k v} $d { incr total $v }\nset total")
:result)
"6")
; 18. Find max in list
(ok "idiom-find-max"
(get
(run
"proc list-max {lst} {\n set m [lindex $lst 0]\n foreach x $lst { if {$x > $m} { set m $x } }\n return $m\n}\nlist-max {3 1 4 1 5 9 2 6}")
:result)
"9")
; 19. Filter list by predicate
(ok "idiom-filter-list"
(get
(run
"proc list-filter {lst pred} {\n set out {}\n foreach x $lst { if {[$pred $x]} { lappend out $x } }\n return $out\n}\nproc is-even {n} { expr {$n % 2 == 0} }\nlist-filter {1 2 3 4 5 6} is-even")
:result)
"2 4 6")
; 20. Zip two lists
(ok "idiom-zip"
(get
(run
"proc zip {a b} {\n set out {}\n set n [llength $a]\n for {set i 0} {$i < $n} {incr i} {\n lappend out [lindex $a $i]\n lappend out [lindex $b $i]\n }\n return $out\n}\nzip {1 2 3} {a b c}")
:result)
"1 a 2 b 3 c")
(dict
"passed"
tcl-idiom-pass
"failed"
tcl-idiom-fail
"failures"
tcl-idiom-failures)))

147
lib/tcl/tests/namespace.sx Normal file
View File

@@ -0,0 +1,147 @@
; Tcl-on-SX namespace tests (Phase 5)
(define tcl-ns-pass 0)
(define tcl-ns-fail 0)
(define tcl-ns-failures (list))
(define
tcl-ns-assert
(fn
(label expected actual)
(if
(equal? expected actual)
(set! tcl-ns-pass (+ tcl-ns-pass 1))
(begin
(set! tcl-ns-fail (+ tcl-ns-fail 1))
(append!
tcl-ns-failures
(str label ": expected=" (str expected) " got=" (str actual)))))))
(define
tcl-run-namespace-tests
(fn
()
(set! tcl-ns-pass 0)
(set! tcl-ns-fail 0)
(set! tcl-ns-failures (list))
(define interp (fn () (make-default-tcl-interp)))
(define run (fn (src) (tcl-eval-string (interp) src)))
(define
ok
(fn (label actual expected) (tcl-ns-assert label expected actual)))
(define
ok?
(fn (label condition) (tcl-ns-assert label true condition)))
; --- namespace current ---
(ok "ns-current-global"
(get (run "namespace current") :result)
"::")
; --- namespace eval defines proc ---
(ok "ns-eval-proc-result"
(get (run "namespace eval myns { proc foo {} { return bar } }\nmyns::foo") :result)
"bar")
; --- fully qualified call ---
(ok "ns-qualified-call"
(get (run "namespace eval myns { proc greet {name} { return \"hello $name\" } }\n::myns::greet World") :result)
"hello World")
; --- namespace current inside eval ---
(ok "ns-current-inside"
(get (run "namespace eval myns { namespace current }") :result)
"::myns")
; --- namespace current restored after eval ---
(ok "ns-current-restored"
(get (run "namespace eval myns { set x 1 }\nnamespace current") :result)
"::")
; --- relative call from within namespace ---
(ok "ns-relative-call"
(get (run "namespace eval math {\n proc double {x} { expr {$x * 2} }\n proc quad {x} { double [double $x] }\n}\nmath::quad 3") :result)
"12")
; --- proc defined as qualified name inside namespace eval ---
(ok "ns-qualified-proc-name"
(get (run "namespace eval utils { proc ::utils::helper {x} { return $x } }\n::utils::helper done") :result)
"done")
; --- namespace exists ---
(ok "ns-exists-yes"
(get (run "namespace eval testns { proc p {} {} }\nnamespace exists testns") :result)
"1")
(ok "ns-exists-no"
(get (run "namespace exists nosuchns") :result)
"0")
(ok "ns-exists-global"
(get (run "proc top {} {}\nnamespace exists ::") :result)
"1")
; --- namespace delete ---
(ok "ns-delete-removes"
(get (run "namespace eval todel { proc pp {} { return yes } }\nnamespace delete todel\nnamespace exists todel") :result)
"0")
; --- namespace which ---
(ok "ns-which-found"
(get (run "namespace eval wns { proc wfn {} {} }\nnamespace which -command wns::wfn") :result)
"::wns::wfn")
(ok "ns-which-not-found"
(get (run "namespace which -command nosuchfn") :result)
"")
; --- namespace ensemble create auto-map ---
(ok "ns-ensemble-add"
(get (run "namespace eval mymath {\n proc add {a b} { expr {$a + $b} }\n proc mul {a b} { expr {$a * $b} }\n namespace ensemble create\n}\nmymath add 3 4") :result)
"7")
(ok "ns-ensemble-mul"
(get (run "namespace eval mymath {\n proc add {a b} { expr {$a + $b} }\n proc mul {a b} { expr {$a * $b} }\n namespace ensemble create\n}\nmymath mul 3 4") :result)
"12")
; --- namespace ensemble with -map ---
(ok "ns-ensemble-map"
(get (run "namespace eval ops {\n proc do-add {a b} { expr {$a + $b} }\n namespace ensemble create -map {plus ::ops::do-add}\n}\nops plus 5 6") :result)
"11")
; --- proc inside namespace eval with args ---
(ok "ns-proc-args"
(get (run "namespace eval calc {\n proc sum {a b c} { expr {$a + $b + $c} }\n}\ncalc::sum 1 2 3") :result)
"6")
; --- info procs inside namespace ---
(ok? "ns-info-procs-in-ns"
(let
((r (get (run "namespace eval foo { proc bar {} {} }\nnamespace eval foo { info procs }") :result)))
(contains? (tcl-list-split r) "bar")))
; --- variable inside namespace eval ---
(ok "ns-variable-inside"
(get (run "namespace eval storage {\n variable count 0\n proc bump {} { global count\n incr count\n return $count }\n}\n::storage::bump\n::storage::bump") :result)
"2")
; --- nested namespaces ---
(ok "ns-nested"
(get (run "namespace eval outer {\n namespace eval inner {\n proc greet {} { return nested }\n }\n}\n::outer::inner::greet") :result)
"nested")
; --- namespace eval accumulates procs ---
(ok "ns-eval-accumulate"
(get (run "namespace eval acc { proc f1 {} { return one } }\nnamespace eval acc { proc f2 {} { return two } }\nacc::f1") :result)
"one")
(ok "ns-eval-accumulate-2"
(get (run "namespace eval acc { proc f1 {} { return one } }\nnamespace eval acc { proc f2 {} { return two } }\nacc::f2") :result)
"two")
(dict
"passed"
tcl-ns-pass
"failed"
tcl-ns-fail
"failures"
tcl-ns-failures)))

186
lib/tcl/tests/parse.sx Normal file
View File

@@ -0,0 +1,186 @@
(define tcl-parse-pass 0)
(define tcl-parse-fail 0)
(define tcl-parse-failures (list))
(define tcl-assert
(fn (label expected actual)
(if (= expected actual)
(set! tcl-parse-pass (+ tcl-parse-pass 1))
(begin
(set! tcl-parse-fail (+ tcl-parse-fail 1))
(append! tcl-parse-failures
(str label ": expected=" (str expected) " got=" (str actual)))))))
(define tcl-first-cmd
(fn (src) (nth (tcl-tokenize src) 0)))
(define tcl-cmd-words
(fn (src) (get (tcl-first-cmd src) :words)))
(define tcl-word
(fn (src wi) (nth (tcl-cmd-words src) wi)))
(define tcl-parts
(fn (src wi) (get (tcl-word src wi) :parts)))
(define tcl-part
(fn (src wi pi) (nth (tcl-parts src wi) pi)))
(define tcl-run-parse-tests
(fn ()
(set! tcl-parse-pass 0)
(set! tcl-parse-fail 0)
(set! tcl-parse-failures (list))
; empty / whitespace-only
(tcl-assert "empty" 0 (len (tcl-tokenize "")))
(tcl-assert "ws-only" 0 (len (tcl-tokenize " ")))
(tcl-assert "nl-only" 0 (len (tcl-tokenize "\n\n")))
; single command word count
(tcl-assert "1word" 1 (len (tcl-cmd-words "set")))
(tcl-assert "3words" 3 (len (tcl-cmd-words "set x 1")))
(tcl-assert "4words" 4 (len (tcl-cmd-words "set a b c")))
; word type — bare word is compound
(tcl-assert "bare-type" "compound" (get (tcl-word "set x 1" 0) :type))
(tcl-assert "bare-quoted" false (get (tcl-word "set x 1" 0) :quoted))
(tcl-assert "bare-part-type" "text" (get (tcl-part "set x 1" 0 0) :type))
(tcl-assert "bare-part-val" "set" (get (tcl-part "set x 1" 0 0) :value))
(tcl-assert "bare-part2-val" "x" (get (tcl-part "set x 1" 1 0) :value))
(tcl-assert "bare-part3-val" "1" (get (tcl-part "set x 1" 2 0) :value))
; multiple commands
(tcl-assert "semi-sep" 2 (len (tcl-tokenize "set x 1; set y 2")))
(tcl-assert "nl-sep" 2 (len (tcl-tokenize "set x 1\nset y 2")))
(tcl-assert "multi-nl" 3 (len (tcl-tokenize "a\nb\nc")))
; comments
(tcl-assert "comment-only" 0 (len (tcl-tokenize "# comment")))
(tcl-assert "comment-nl" 0 (len (tcl-tokenize "# comment\n")))
(tcl-assert "comment-then-cmd" 1 (len (tcl-tokenize "# comment\nset x 1")))
(tcl-assert "semi-then-comment" 1 (len (tcl-tokenize "set x 1; # comment")))
; brace-quoted words
(tcl-assert "brace-type" "braced" (get (tcl-word "{hello}" 0) :type))
(tcl-assert "brace-value" "hello" (get (tcl-word "{hello}" 0) :value))
(tcl-assert "brace-spaces" "hello world" (get (tcl-word "{hello world}" 0) :value))
(tcl-assert "brace-nested" "a {b} c" (get (tcl-word "{a {b} c}" 0) :value))
(tcl-assert "brace-no-var-sub" "hello $x" (get (tcl-word "{hello $x}" 0) :value))
(tcl-assert "brace-no-cmd-sub" "[expr 1]" (get (tcl-word "{[expr 1]}" 0) :value))
; double-quoted words
(tcl-assert "dq-type" "compound" (get (tcl-word "\"hello\"" 0) :type))
(tcl-assert "dq-quoted" true (get (tcl-word "\"hello\"" 0) :quoted))
(tcl-assert "dq-literal" "hello" (get (tcl-part "\"hello\"" 0 0) :value))
; variable substitution in bare word
(tcl-assert "var-type" "var" (get (tcl-part "$x" 0 0) :type))
(tcl-assert "var-name" "x" (get (tcl-part "$x" 0 0) :name))
(tcl-assert "var-long" "long_name" (get (tcl-part "$long_name" 0 0) :name))
; ${name} form
(tcl-assert "var-brace-type" "var" (get (tcl-part "${x}" 0 0) :type))
(tcl-assert "var-brace-name" "x" (get (tcl-part "${x}" 0 0) :name))
; array variable substitution
(tcl-assert "arr-type" "var-arr" (get (tcl-part "$arr(key)" 0 0) :type))
(tcl-assert "arr-name" "arr" (get (tcl-part "$arr(key)" 0 0) :name))
(tcl-assert "arr-key-len" 1 (len (get (tcl-part "$arr(key)" 0 0) :key)))
(tcl-assert "arr-key-text" "key"
(get (nth (get (tcl-part "$arr(key)" 0 0) :key) 0) :value))
; command substitution
(tcl-assert "cmd-type" "cmd" (get (tcl-part "[expr 1+1]" 0 0) :type))
(tcl-assert "cmd-src" "expr 1+1" (get (tcl-part "[expr 1+1]" 0 0) :src))
; nested command substitution
(tcl-assert "cmd-nested-src" "expr [string length x]"
(get (tcl-part "[expr [string length x]]" 0 0) :src))
; backslash substitution in double-quoted word
(let ((ps (tcl-parts "\"a\\nb\"" 0)))
(begin
(tcl-assert "bs-n-part0" "a" (get (nth ps 0) :value))
(tcl-assert "bs-n-part1" "\n" (get (nth ps 1) :value))
(tcl-assert "bs-n-part2" "b" (get (nth ps 2) :value))))
(let ((ps (tcl-parts "\"a\\tb\"" 0)))
(tcl-assert "bs-t-part1" "\t" (get (nth ps 1) :value)))
(let ((ps (tcl-parts "\"a\\\\b\"" 0)))
(tcl-assert "bs-bs-part1" "\\" (get (nth ps 1) :value)))
; mixed word: text + var + text in double-quoted
(let ((ps (tcl-parts "\"hello $name!\"" 0)))
(begin
(tcl-assert "mixed-text0" "hello " (get (nth ps 0) :value))
(tcl-assert "mixed-var1-type" "var" (get (nth ps 1) :type))
(tcl-assert "mixed-var1-name" "name" (get (nth ps 1) :name))
(tcl-assert "mixed-text2" "!" (get (nth ps 2) :value))))
; {*} expansion
(tcl-assert "expand-type" "expand" (get (tcl-word "{*}$list" 0) :type))
; line continuation between words
(tcl-assert "cont-words" 3 (len (tcl-cmd-words "set x \\\n 1")))
; continuation — third command word is correct
(tcl-assert "cont-word2-val" "1"
(get (tcl-part "set x \\\n 1" 2 0) :value))
; --- parser helpers ---
; tcl-parse is an alias for tcl-tokenize
(tcl-assert "parse-cmd-count" 1 (len (tcl-parse "set x 1")))
(tcl-assert "parse-2cmds" 2 (len (tcl-parse "set x 1; set y 2")))
; tcl-cmd-len
(tcl-assert "cmd-len-3" 3 (tcl-cmd-len (nth (tcl-parse "set x 1") 0)))
(tcl-assert "cmd-len-1" 1 (tcl-cmd-len (nth (tcl-parse "puts") 0)))
; tcl-word-simple? on braced word
(tcl-assert "simple-braced" true
(tcl-word-simple? (nth (get (nth (tcl-parse "{hello}") 0) :words) 0)))
; tcl-word-simple? on bare word with no subs
(tcl-assert "simple-bare" true
(tcl-word-simple? (nth (get (nth (tcl-parse "hello") 0) :words) 0)))
; tcl-word-simple? on word containing a var sub — false
(tcl-assert "simple-var-false" false
(tcl-word-simple? (nth (get (nth (tcl-parse "$x") 0) :words) 0)))
; tcl-word-simple? on word containing a cmd sub — false
(tcl-assert "simple-cmd-false" false
(tcl-word-simple? (nth (get (nth (tcl-parse "[expr 1]") 0) :words) 0)))
; tcl-word-literal on braced word
(tcl-assert "lit-braced" "hello world"
(tcl-word-literal (nth (get (nth (tcl-parse "{hello world}") 0) :words) 0)))
; tcl-word-literal on bare word
(tcl-assert "lit-bare" "hello"
(tcl-word-literal (nth (get (nth (tcl-parse "hello") 0) :words) 0)))
; tcl-word-literal on word with var sub returns nil
(tcl-assert "lit-var-nil" nil
(tcl-word-literal (nth (get (nth (tcl-parse "$x") 0) :words) 0)))
; tcl-nth-literal
(tcl-assert "nth-lit-0" "set"
(tcl-nth-literal (nth (tcl-parse "set x 1") 0) 0))
(tcl-assert "nth-lit-1" "x"
(tcl-nth-literal (nth (tcl-parse "set x 1") 0) 1))
(tcl-assert "nth-lit-2" "1"
(tcl-nth-literal (nth (tcl-parse "set x 1") 0) 2))
; tcl-nth-literal returns nil when word has subs
(tcl-assert "nth-lit-nil" nil
(tcl-nth-literal (nth (tcl-parse "set x $y") 0) 2))
(dict
"passed" tcl-parse-pass
"failed" tcl-parse-fail
"failures" tcl-parse-failures)))

View File

@@ -0,0 +1,14 @@
# expected: 10
proc assert {expr_str} {
set result [uplevel 1 [list expr $expr_str]]
if {!$result} {
error "Assertion failed: $expr_str"
}
}
set x 42
assert {$x == 42}
assert {$x > 0}
set x 10
assert {$x < 100}
set x

View File

@@ -0,0 +1,22 @@
# expected: done
# Cooperative scheduler demo using coroutines (generator style)
# coroutine eagerly collects all yields; invoking the coroutine name pops values
proc counter {n max} {
while {$n < $max} {
yield $n
incr n
}
yield done
}
coroutine gen1 counter 0 3
# gen1 yields: 0 1 2 done
set out {}
for {set i 0} {$i < 4} {incr i} {
lappend out [gen1]
}
# last val is "done"
lindex $out 3

View File

@@ -0,0 +1,14 @@
# expected: 13
proc for-each-line {var lines body} {
foreach item $lines {
uplevel 1 [list set $var $item]
uplevel 1 $body
}
}
# Usage: accumulate lengths of each "line"
set total 0
for-each-line line {hello world foo} {
incr total [string length $line]
}
set total

View File

@@ -0,0 +1,14 @@
# expected: 100 999
proc with-temp-var {varname tempval body} {
upvar 1 $varname v
set saved $v
set v $tempval
uplevel 1 $body
set v $saved
}
set x 100
with-temp-var x 999 {
set captured $x
}
list $x $captured