phase 22 tcl: string-buffer/channel/regexp/format/coroutine in lib/tcl/runtime.sx (37 forms), 56/56 tests
This commit is contained in:
279
lib/tcl/runtime.sx
Normal file
279
lib/tcl/runtime.sx
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
;; lib/tcl/runtime.sx — Tcl primitives on SX
|
||||||
|
;;
|
||||||
|
;; Provides Tcl-idiomatic wrappers over SX built-ins.
|
||||||
|
;; Primitives used:
|
||||||
|
;; make-regexp/regexp-match/regexp-match-all/... (Phase 19)
|
||||||
|
;; make-set/set-add!/set-member?/set-remove!/set->list (Phase 18)
|
||||||
|
;; call/cc (core evaluator)
|
||||||
|
;; quotient/remainder (Phase 15 / builtin)
|
||||||
|
;; string->list/list->string/char->integer (Phase 13)
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 1. String buffer — Tcl append / string accumulation
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-sb-new)
|
||||||
|
(let
|
||||||
|
((sb (dict)))
|
||||||
|
(dict-set! sb "_tcl_sb" true)
|
||||||
|
(dict-set! sb "_buf" "")
|
||||||
|
sb))
|
||||||
|
|
||||||
|
(define (tcl-sb? v) (and (dict? v) (dict-has? v "_tcl_sb")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-sb-append! sb s)
|
||||||
|
(dict-set! sb "_buf" (str (get sb "_buf") s))
|
||||||
|
sb)
|
||||||
|
|
||||||
|
(define (tcl-sb-value sb) (get sb "_buf"))
|
||||||
|
|
||||||
|
(define (tcl-sb-clear! sb) (dict-set! sb "_buf" "") sb)
|
||||||
|
|
||||||
|
(define (tcl-sb-length sb) (len (get sb "_buf")))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 2. String port (channel) — Tcl channel abstraction
|
||||||
|
;; Read channel: created from a string, supports gets/read.
|
||||||
|
;; Write channel: accumulates puts output, queryable via tcl-chan-string.
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-chan-in-new str)
|
||||||
|
(let
|
||||||
|
((c (dict)))
|
||||||
|
(dict-set! c "_tcl_chan" true)
|
||||||
|
(dict-set! c "_mode" "read")
|
||||||
|
(dict-set! c "_chars" (string->list str))
|
||||||
|
(dict-set! c "_pos" 0)
|
||||||
|
c))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-chan-out-new)
|
||||||
|
(let
|
||||||
|
((c (dict)))
|
||||||
|
(dict-set! c "_tcl_chan" true)
|
||||||
|
(dict-set! c "_mode" "write")
|
||||||
|
(dict-set! c "_buf" "")
|
||||||
|
c))
|
||||||
|
|
||||||
|
(define (tcl-chan? v) (and (dict? v) (dict-has? v "_tcl_chan")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-chan-eof? c)
|
||||||
|
(and
|
||||||
|
(= (get c "_mode") "read")
|
||||||
|
(>= (get c "_pos") (len (get c "_chars")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-chan-read-char c)
|
||||||
|
(if
|
||||||
|
(tcl-chan-eof? c)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((ch (nth (get c "_chars") (get c "_pos"))))
|
||||||
|
(dict-set! c "_pos" (+ (get c "_pos") 1))
|
||||||
|
ch)))
|
||||||
|
|
||||||
|
;; gets — read one line (up to newline or EOF), return without trailing newline
|
||||||
|
(define
|
||||||
|
(tcl-chan-gets c)
|
||||||
|
(letrec
|
||||||
|
((go (fn (acc) (let ((ch (tcl-chan-read-char c))) (cond ((= ch nil) (list->string (reverse acc))) ((= (char->integer ch) 10) (list->string (reverse acc))) (else (go (cons ch acc))))))))
|
||||||
|
(go (list))))
|
||||||
|
|
||||||
|
;; read — read all remaining chars
|
||||||
|
(define
|
||||||
|
(tcl-chan-read c)
|
||||||
|
(letrec
|
||||||
|
((go (fn (acc) (let ((ch (tcl-chan-read-char c))) (if (= ch nil) (list->string (reverse acc)) (go (cons ch acc)))))))
|
||||||
|
(go (list))))
|
||||||
|
|
||||||
|
;; puts — write string to write channel (no newline)
|
||||||
|
(define
|
||||||
|
(tcl-chan-puts! c s)
|
||||||
|
(when
|
||||||
|
(= (get c "_mode") "write")
|
||||||
|
(dict-set! c "_buf" (str (get c "_buf") s)))
|
||||||
|
c)
|
||||||
|
|
||||||
|
;; puts-line — write string + newline (Tcl default puts behaviour)
|
||||||
|
(define (tcl-chan-puts-line! c s) (tcl-chan-puts! c (str s "\n")))
|
||||||
|
|
||||||
|
;; string — get accumulated content of write channel
|
||||||
|
(define (tcl-chan-string c) (get c "_buf"))
|
||||||
|
|
||||||
|
;; tell — current read position
|
||||||
|
(define (tcl-chan-tell c) (get c "_pos"))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 3. Regexp — Tcl regexp / regsub wrappers
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define (tcl-re-new pattern) (make-regexp pattern ""))
|
||||||
|
|
||||||
|
(define (tcl-re-new-flags pattern flags) (make-regexp pattern flags))
|
||||||
|
|
||||||
|
(define (tcl-re? v) (regexp? v))
|
||||||
|
|
||||||
|
(define (tcl-re-match? rx str) (not (= (regexp-match rx str) nil)))
|
||||||
|
|
||||||
|
(define (tcl-re-match rx str) (regexp-match rx str))
|
||||||
|
|
||||||
|
(define (tcl-re-match-all rx str) (regexp-match-all rx str))
|
||||||
|
|
||||||
|
(define (tcl-re-sub rx str replacement) (regexp-replace rx str replacement))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-re-sub-all rx str replacement)
|
||||||
|
(regexp-replace-all rx str replacement))
|
||||||
|
|
||||||
|
(define (tcl-re-split rx str) (regexp-split rx str))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 4. Format — Tcl format command (%s %d %f %x %o %%)
|
||||||
|
;; tcl-format takes a format string and a list of arguments.
|
||||||
|
;; Example: (tcl-format "%s is %d" (list "Alice" 30)) → "Alice is 30"
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
;; Digit characters for base conversion
|
||||||
|
(define tcl-hex-chars (string->list "0123456789abcdef"))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-digits-for-base n base digit-chars)
|
||||||
|
(let
|
||||||
|
((abs-n (if (< n 0) (- 0 n) n)))
|
||||||
|
(letrec
|
||||||
|
((go (fn (n acc) (if (= n 0) (if (= (len acc) 0) "0" (list->string acc)) (go (quotient n base) (cons (nth digit-chars (remainder n base)) acc))))))
|
||||||
|
(let
|
||||||
|
((unsigned (go abs-n (list))))
|
||||||
|
(if (< n 0) (str "-" unsigned) unsigned)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-format-hex n)
|
||||||
|
(tcl-digits-for-base (truncate n) 16 tcl-hex-chars))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-format-oct n)
|
||||||
|
(tcl-digits-for-base (truncate n) 8 (string->list "01234567")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-format fmt args)
|
||||||
|
(letrec
|
||||||
|
((chars (string->list fmt))
|
||||||
|
(go
|
||||||
|
(fn
|
||||||
|
(cs arg-list result)
|
||||||
|
(if
|
||||||
|
(= (len cs) 0)
|
||||||
|
result
|
||||||
|
(let
|
||||||
|
((c-int (char->integer (first cs))))
|
||||||
|
(if
|
||||||
|
(= c-int 37)
|
||||||
|
(if
|
||||||
|
(= (len (rest cs)) 0)
|
||||||
|
result
|
||||||
|
(let
|
||||||
|
((spec-int (char->integer (first (rest cs)))))
|
||||||
|
(cond
|
||||||
|
((= spec-int 37)
|
||||||
|
(go (rest (rest cs)) arg-list (str result "%")))
|
||||||
|
((= spec-int 115)
|
||||||
|
(go
|
||||||
|
(rest (rest cs))
|
||||||
|
(rest arg-list)
|
||||||
|
(str result (str (first arg-list)))))
|
||||||
|
((= spec-int 100)
|
||||||
|
(go
|
||||||
|
(rest (rest cs))
|
||||||
|
(rest arg-list)
|
||||||
|
(str result (str (truncate (first arg-list))))))
|
||||||
|
((= spec-int 102)
|
||||||
|
(go
|
||||||
|
(rest (rest cs))
|
||||||
|
(rest arg-list)
|
||||||
|
(str result (str (+ 0 (first arg-list))))))
|
||||||
|
((= spec-int 120)
|
||||||
|
(go
|
||||||
|
(rest (rest cs))
|
||||||
|
(rest arg-list)
|
||||||
|
(str result (tcl-format-hex (first arg-list)))))
|
||||||
|
((= spec-int 111)
|
||||||
|
(go
|
||||||
|
(rest (rest cs))
|
||||||
|
(rest arg-list)
|
||||||
|
(str result (tcl-format-oct (first arg-list)))))
|
||||||
|
(else
|
||||||
|
(go
|
||||||
|
(rest (rest cs))
|
||||||
|
arg-list
|
||||||
|
(str
|
||||||
|
result
|
||||||
|
"%"
|
||||||
|
(list->string (list (first (rest cs))))))))))
|
||||||
|
(go
|
||||||
|
(rest cs)
|
||||||
|
arg-list
|
||||||
|
(str result (list->string (list (first cs)))))))))))
|
||||||
|
(go chars args "")))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 5. Coroutine — Tcl-style coroutine using call/cc
|
||||||
|
;; tcl-co-yield works reliably when called from top-level fns.
|
||||||
|
;; Avoid calling tcl-co-yield from letrec-bound lambdas (JIT limitation).
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define tcl-current-co nil)
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-co-new body)
|
||||||
|
(let
|
||||||
|
((co (dict)))
|
||||||
|
(dict-set! co "_tcl_co" true)
|
||||||
|
(dict-set! co "_state" "new")
|
||||||
|
(dict-set! co "_cont" nil)
|
||||||
|
(dict-set! co "_resumer" nil)
|
||||||
|
(dict-set! co "_parent" nil)
|
||||||
|
(dict-set!
|
||||||
|
co
|
||||||
|
"_body"
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((result (body)))
|
||||||
|
(dict-set! co "_state" "dead")
|
||||||
|
(set! tcl-current-co (get co "_parent"))
|
||||||
|
((get co "_resumer") result))))
|
||||||
|
co))
|
||||||
|
|
||||||
|
(define (tcl-co? v) (and (dict? v) (dict-has? v "_tcl_co")))
|
||||||
|
|
||||||
|
(define (tcl-co-alive? co) (not (= (get co "_state") "dead")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-co-yield val)
|
||||||
|
(call/cc
|
||||||
|
(fn
|
||||||
|
(resume-k)
|
||||||
|
(let
|
||||||
|
((cur tcl-current-co))
|
||||||
|
(dict-set! cur "_cont" resume-k)
|
||||||
|
(dict-set! cur "_state" "suspended")
|
||||||
|
(set! tcl-current-co (get cur "_parent"))
|
||||||
|
((get cur "_resumer") val)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-co-resume co)
|
||||||
|
(call/cc
|
||||||
|
(fn
|
||||||
|
(return-k)
|
||||||
|
(dict-set! co "_parent" tcl-current-co)
|
||||||
|
(dict-set! co "_resumer" return-k)
|
||||||
|
(set! tcl-current-co co)
|
||||||
|
(dict-set! co "_state" "running")
|
||||||
|
(if
|
||||||
|
(= (get co "_cont") nil)
|
||||||
|
((get co "_body"))
|
||||||
|
((get co "_cont") nil)))))
|
||||||
62
lib/tcl/test.sh
Executable file
62
lib/tcl/test.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib/tcl/test.sh — smoke-test the Tcl runtime layer.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
echo "ERROR: sx_server.exe not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||||
|
|
||||||
|
cat > "$TMPFILE" << 'EPOCHS'
|
||||||
|
(epoch 1)
|
||||||
|
(load "lib/tcl/runtime.sx")
|
||||||
|
(epoch 2)
|
||||||
|
(load "lib/tcl/tests/runtime.sx")
|
||||||
|
(epoch 3)
|
||||||
|
(eval "(list tcl-test-pass tcl-test-fail)")
|
||||||
|
EPOCHS
|
||||||
|
|
||||||
|
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||||
|
|
||||||
|
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
|
||||||
|
if [ -z "$LINE" ]; then
|
||||||
|
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||||
|
| sed -E 's/^\(ok 3 //; s/\)$//')
|
||||||
|
fi
|
||||||
|
if [ -z "$LINE" ]; then
|
||||||
|
echo "ERROR: could not extract summary"
|
||||||
|
echo "$OUTPUT" | tail -20
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
|
||||||
|
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
|
||||||
|
TOTAL=$((P + F))
|
||||||
|
|
||||||
|
if [ "$F" -eq 0 ]; then
|
||||||
|
echo "ok $P/$TOTAL lib/tcl tests passed"
|
||||||
|
else
|
||||||
|
echo "FAIL $P/$TOTAL passed, $F failed"
|
||||||
|
TMPFILE2=$(mktemp)
|
||||||
|
cat > "$TMPFILE2" << 'EPOCHS2'
|
||||||
|
(epoch 1)
|
||||||
|
(load "lib/tcl/runtime.sx")
|
||||||
|
(epoch 2)
|
||||||
|
(load "lib/tcl/tests/runtime.sx")
|
||||||
|
(epoch 3)
|
||||||
|
(eval "(map (fn (f) (list (get f :name) (get f :got) (get f :expected))) tcl-test-fails)")
|
||||||
|
EPOCHS2
|
||||||
|
FAILS=$(timeout 60 "$SX_SERVER" < "$TMPFILE2" 2>/dev/null | grep -E '^\(ok-len 3' -A1 | tail -1 || true)
|
||||||
|
echo " Details: $FAILS"
|
||||||
|
rm -f "$TMPFILE2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$F" -eq 0 ]
|
||||||
146
lib/tcl/tests/runtime.sx
Normal file
146
lib/tcl/tests/runtime.sx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
;; lib/tcl/tests/runtime.sx — Tests for lib/tcl/runtime.sx
|
||||||
|
|
||||||
|
(define tcl-test-pass 0)
|
||||||
|
(define tcl-test-fail 0)
|
||||||
|
(define tcl-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
(tcl-test name got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! tcl-test-pass (+ tcl-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! tcl-test-fail (+ tcl-test-fail 1))
|
||||||
|
(set! tcl-test-fails (append tcl-test-fails (list {:got got :expected expected :name name}))))))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 1. String buffer
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define sb1 (tcl-sb-new))
|
||||||
|
(tcl-test "sb? new" (tcl-sb? sb1) true)
|
||||||
|
(tcl-test "sb? non-sb" (tcl-sb? "hello") false)
|
||||||
|
(tcl-test "sb value empty" (tcl-sb-value sb1) "")
|
||||||
|
(tcl-test "sb length empty" (tcl-sb-length sb1) 0)
|
||||||
|
(tcl-sb-append! sb1 "hello")
|
||||||
|
(tcl-test "sb value after append" (tcl-sb-value sb1) "hello")
|
||||||
|
(tcl-sb-append! sb1 " ")
|
||||||
|
(tcl-sb-append! sb1 "world")
|
||||||
|
(tcl-test "sb value after multi-append" (tcl-sb-value sb1) "hello world")
|
||||||
|
(tcl-test "sb length" (tcl-sb-length sb1) 11)
|
||||||
|
(tcl-sb-clear! sb1)
|
||||||
|
(tcl-test "sb value after clear" (tcl-sb-value sb1) "")
|
||||||
|
(tcl-test "sb length after clear" (tcl-sb-length sb1) 0)
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 2. String port (channel)
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define chin1 (tcl-chan-in-new "hello\nworld\nfoo"))
|
||||||
|
(tcl-test "chan? read" (tcl-chan? chin1) true)
|
||||||
|
(tcl-test "chan eof? no" (tcl-chan-eof? chin1) false)
|
||||||
|
(tcl-test "chan gets line1" (tcl-chan-gets chin1) "hello")
|
||||||
|
(tcl-test "chan gets line2" (tcl-chan-gets chin1) "world")
|
||||||
|
(tcl-test "chan gets line3" (tcl-chan-gets chin1) "foo")
|
||||||
|
(tcl-test "chan eof? yes" (tcl-chan-eof? chin1) true)
|
||||||
|
(tcl-test "chan gets at eof" (tcl-chan-gets chin1) "")
|
||||||
|
|
||||||
|
(define chin2 (tcl-chan-in-new "abcdef"))
|
||||||
|
(tcl-test "chan read all" (tcl-chan-read chin2) "abcdef")
|
||||||
|
(tcl-test "chan read empty" (tcl-chan-read chin2) "")
|
||||||
|
|
||||||
|
(define chout1 (tcl-chan-out-new))
|
||||||
|
(tcl-test "chan? write" (tcl-chan? chout1) true)
|
||||||
|
(tcl-chan-puts! chout1 "hello")
|
||||||
|
(tcl-chan-puts! chout1 " world")
|
||||||
|
(tcl-test "chan string" (tcl-chan-string chout1) "hello world")
|
||||||
|
(tcl-chan-puts-line! chout1 "!")
|
||||||
|
(tcl-test "chan string with newline" (tcl-chan-string chout1) "hello world!\n")
|
||||||
|
|
||||||
|
(define chout2 (tcl-chan-out-new))
|
||||||
|
(tcl-chan-puts-line! chout2 "line1")
|
||||||
|
(tcl-chan-puts-line! chout2 "line2")
|
||||||
|
(tcl-test "chan multi-line" (tcl-chan-string chout2) "line1\nline2\n")
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 3. Regexp
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define rx1 (tcl-re-new "hel+o"))
|
||||||
|
(tcl-test "re? yes" (tcl-re? rx1) true)
|
||||||
|
(tcl-test "re? no" (tcl-re? "hello") false)
|
||||||
|
(tcl-test "re match? yes" (tcl-re-match? rx1 "say hello") true)
|
||||||
|
(tcl-test "re match? no" (tcl-re-match? rx1 "goodbye") false)
|
||||||
|
|
||||||
|
(define m1 (tcl-re-match rx1 "say hello world"))
|
||||||
|
(tcl-test "re match result" (get m1 "match") "hello")
|
||||||
|
|
||||||
|
(define rx2 (tcl-re-new "[0-9]+"))
|
||||||
|
(define all (tcl-re-match-all rx2 "a1b22c333"))
|
||||||
|
(tcl-test "re match-all count" (len all) 3)
|
||||||
|
(tcl-test "re match-all last" (get (nth all 2) "match") "333")
|
||||||
|
|
||||||
|
(tcl-test "re sub" (tcl-re-sub rx2 "a1b2" "N") "aNb2")
|
||||||
|
(tcl-test "re sub-all" (tcl-re-sub-all rx2 "a1b2" "N") "aNbN")
|
||||||
|
|
||||||
|
(define rx3 (tcl-re-new "[ ,]+"))
|
||||||
|
(tcl-test "re split" (tcl-re-split rx3 "a b,c") (list "a" "b" "c"))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 4. Format
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(tcl-test "format %s" (tcl-format "hello %s" (list "world")) "hello world")
|
||||||
|
(tcl-test "format %d" (tcl-format "n=%d" (list 42)) "n=42")
|
||||||
|
(tcl-test "format %d truncates float" (tcl-format "n=%d" (list 3.9)) "n=3")
|
||||||
|
(tcl-test
|
||||||
|
"format %s %d"
|
||||||
|
(tcl-format "%s is %d" (list "age" 30))
|
||||||
|
"age is 30")
|
||||||
|
(tcl-test "format %%" (tcl-format "100%% done" (list)) "100% done")
|
||||||
|
(tcl-test "format %x" (tcl-format "%x" (list 255)) "ff")
|
||||||
|
(tcl-test "format %x 16" (tcl-format "0x%x" (list 16)) "0x10")
|
||||||
|
(tcl-test "format %o" (tcl-format "%o" (list 8)) "10")
|
||||||
|
(tcl-test "format %o 255" (tcl-format "%o" (list 255)) "377")
|
||||||
|
(tcl-test "format no spec" (tcl-format "plain text" (list)) "plain text")
|
||||||
|
(tcl-test
|
||||||
|
"format multiple"
|
||||||
|
(tcl-format "%s=%d (0x%x)" (list "val" 255 255))
|
||||||
|
"val=255 (0xff)")
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 5. Coroutine
|
||||||
|
;; tcl-co-yield works from top-level helper functions.
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define
|
||||||
|
co1
|
||||||
|
(tcl-co-new
|
||||||
|
(fn () (tcl-co-yield 1) (tcl-co-yield 2) 3)))
|
||||||
|
|
||||||
|
(tcl-test "co? yes" (tcl-co? co1) true)
|
||||||
|
(tcl-test "co? no" (tcl-co? 42) false)
|
||||||
|
(tcl-test "co alive? before" (tcl-co-alive? co1) true)
|
||||||
|
(define cor1 (tcl-co-resume co1))
|
||||||
|
(tcl-test "co resume 1" cor1 1)
|
||||||
|
(tcl-test "co alive? mid" (tcl-co-alive? co1) true)
|
||||||
|
(define cor2 (tcl-co-resume co1))
|
||||||
|
(tcl-test "co resume 2" cor2 2)
|
||||||
|
(define cor3 (tcl-co-resume co1))
|
||||||
|
(tcl-test "co resume 3 completion" cor3 3)
|
||||||
|
(tcl-test "co alive? dead" (tcl-co-alive? co1) false)
|
||||||
|
|
||||||
|
;; Top-level helper for recursive yield (avoids JIT letrec limitation)
|
||||||
|
(define
|
||||||
|
(tcl-co-count-down i)
|
||||||
|
(when
|
||||||
|
(>= i 1)
|
||||||
|
(tcl-co-yield i)
|
||||||
|
(tcl-co-count-down (- i 1))))
|
||||||
|
|
||||||
|
(define co2 (tcl-co-new (fn () (tcl-co-count-down 3) "done")))
|
||||||
|
(tcl-test "co loop 3" (tcl-co-resume co2) 3)
|
||||||
|
(tcl-test "co loop 2" (tcl-co-resume co2) 2)
|
||||||
|
(tcl-test "co loop 1" (tcl-co-resume co2) 1)
|
||||||
|
(tcl-test "co loop done" (tcl-co-resume co2) "done")
|
||||||
|
(tcl-test "co loop dead" (tcl-co-alive? co2) false)
|
||||||
Reference in New Issue
Block a user