tcl: Phase 4 error handling — catch/try/throw/return-code (+39 tests, 267 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled

Implements catch, throw, try, return -code options, and error with
errorinfo/errorcode fields. catch runs sub-script isolated, captures
result and exit code (0-4); try dispatches on/finally clauses;
throw sets code 1 with errorcode; return -code parses flag options.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 09:58:32 +00:00
parent afddc92c70
commit d295ab8463
3 changed files with 447 additions and 17 deletions

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)))