Merge remote-tracking branch 'origin/loops/tcl' into architecture

This commit is contained in:
2026-05-07 18:29:26 +00:00
4 changed files with 481 additions and 43 deletions

View File

@@ -292,13 +292,15 @@
(> (len result-stack) caller-stack-len)
(nth result-stack caller-stack-len)
(get interp :frame))))
(assoc interp
; Forward result-interp as base so state changes inside
; the proc (e.g. :fileevents, :timers, :procs) propagate;
; restore caller's frame/stack/result/output/code.
(assoc result-interp
:frame updated-caller
:frame-stack updated-below
:result result-val
:output (str caller-output proc-output)
:code (if (= code 2) 0 code)
:commands (get result-interp :commands))))))))))))))
:code (if (= code 2) 0 code))))))))))))))
(define
tcl-eval-cmd
@@ -2887,12 +2889,54 @@
((equal? sub "seconds") (assoc interp :result (str (clock-seconds))))
((equal? sub "milliseconds") (assoc interp :result (str (clock-milliseconds))))
((equal? sub "format")
(assoc interp :result (clock-format
(floor (parse-int (first rest-args)))
(if (> (len rest-args) 1) (nth rest-args (- (len rest-args) 1)) "%a %b %e %H:%M:%S %Z %Y"))))
((equal? sub "scan") (assoc interp :result "0"))
; clock format $secs ?-format $fmt? ?-timezone $tz? ?-gmt 0|1?
(let
((t (floor (parse-int (first rest-args))))
(opts (rest rest-args)))
(let
((fmt (tcl-clock-opt opts "-format" "%a %b %e %H:%M:%S %Z %Y"))
(tz (tcl-clock-tz opts)))
(assoc interp :result (clock-format t fmt tz)))))
((equal? sub "scan")
; clock scan $str ?-format $fmt? ?-timezone $tz? ?-gmt 0|1?
(let
((s (first rest-args)) (opts (rest rest-args)))
(let
((fmt (tcl-clock-opt opts "-format" "%Y-%m-%d %H:%M:%S"))
(tz (tcl-clock-tz opts)))
(assoc interp :result (str (clock-scan s fmt tz))))))
(else (error (str "clock: unknown subcommand \"" sub "\""))))))))
; Helper: extract a -flag $val pair from clock args.
(define
tcl-clock-opt
(fn
(opts flag default)
(cond
((< (len opts) 2) default)
((equal? (first opts) flag) (nth opts 1))
(else (tcl-clock-opt (rest (rest opts)) flag default)))))
; Helper: derive tz string from clock opts (-timezone or -gmt).
(define
tcl-clock-tz
(fn
(opts)
(let
((tz-explicit (tcl-clock-opt opts "-timezone" nil))
(gmt-flag (tcl-clock-opt opts "-gmt" nil)))
(cond
((not (nil? tz-explicit))
(cond
((equal? tz-explicit ":UTC") "utc")
((equal? tz-explicit "UTC") "utc")
((equal? tz-explicit "GMT") "utc")
(else "local")))
((equal? gmt-flag "1") "utc")
((equal? gmt-flag "true") "utc")
((not (nil? gmt-flag)) "local")
(else "utc")))))
(define
tcl-cmd-open
(fn
@@ -2973,26 +3017,31 @@
(interp args)
(let
((chan (first args)) (rest-args (rest args)))
(if
(= 0 (len rest-args))
(assoc
interp
:result (str "-blocking " (if (channel-blocking? chan) "1" "0")))
(if
(and
(= 2 (len rest-args))
(equal? (first rest-args) "-blocking"))
(cond
((= 0 (len rest-args))
(assoc
interp
:result (str "-blocking " (if (channel-blocking? chan) "1" "0"))))
((and
(= 2 (len rest-args))
(equal? (first rest-args) "-blocking"))
(let
((b (nth rest-args 1)))
(let
((_ (channel-set-blocking! chan (not (or (equal? b "0") (equal? b "false"))))))
(assoc interp :result "")))
(if
(and
(= 1 (len rest-args))
(equal? (first rest-args) "-blocking"))
(assoc interp :result (if (channel-blocking? chan) "1" "0"))
(assoc interp :result "")))))))
((_
(channel-set-blocking!
chan
(not (or (equal? b "0") (equal? b "false"))))))
(assoc interp :result ""))))
((and
(= 1 (len rest-args))
(equal? (first rest-args) "-blocking"))
(assoc interp :result (if (channel-blocking? chan) "1" "0")))
((and
(= 1 (len rest-args))
(equal? (first rest-args) "-error"))
(assoc interp :result (channel-async-error chan)))
(else (assoc interp :result ""))))))
; ============================================================
@@ -3253,6 +3302,13 @@
(assoc
(tcl-fileevent-set interp server-chan "readable" handler)
:result server-chan))))))
((equal? (first args) "-async")
(if
(< (len args) 3)
(error "socket: usage: socket -async host port")
(let
((host (nth args 1)) (port (parse-int (nth args 2))))
(assoc interp :result (socket-connect-async host port)))))
((= 2 (len args))
(let
((host (first args)) (port (parse-int (nth args 1))))
@@ -3609,16 +3665,52 @@
(equal? dot-idx "-1")
nm
(substring nm 0 (parse-int dot-idx)))))))
((equal? sub "isfile") (assoc interp :result "0"))
((equal? sub "isdir") (assoc interp :result "0"))
((equal? sub "isdirectory") (assoc interp :result "0"))
((equal? sub "readable") (assoc interp :result "0"))
((equal? sub "writable") (assoc interp :result "0"))
((equal? sub "size") (assoc interp :result "0"))
((equal? sub "mkdir") (assoc interp :result ""))
((equal? sub "copy") (assoc interp :result ""))
((equal? sub "rename") (assoc interp :result ""))
((equal? sub "delete") (assoc interp :result ""))
((equal? sub "isfile")
(assoc interp :result (if (file-isfile? (first rest-args)) "1" "0")))
((equal? sub "isdir")
(assoc interp :result (if (file-isdir? (first rest-args)) "1" "0")))
((equal? sub "isdirectory")
(assoc interp :result (if (file-isdir? (first rest-args)) "1" "0")))
((equal? sub "readable")
(assoc interp :result (if (file-readable? (first rest-args)) "1" "0")))
((equal? sub "writable")
(assoc interp :result (if (file-writable? (first rest-args)) "1" "0")))
((equal? sub "size")
(assoc interp :result (str (file-size (first rest-args)))))
((equal? sub "mtime")
(assoc interp :result (str (file-mtime (first rest-args)))))
((equal? sub "atime")
(let ((s (file-stat (first rest-args))))
(assoc interp :result (if (nil? s) "0" (str (get s :atime))))))
((equal? sub "type")
(let ((s (file-stat (first rest-args))))
(assoc interp :result (if (nil? s) "" (get s :type)))))
((equal? sub "mkdir")
(let ((_ (file-mkdir (first rest-args))))
(assoc interp :result "")))
((equal? sub "copy")
(let
((paths
(filter (fn (a) (not (equal? (slice a 0 1) "-"))) rest-args)))
(let ((_ (file-copy (first paths) (nth paths 1))))
(assoc interp :result ""))))
((equal? sub "rename")
(let
((paths
(filter (fn (a) (not (equal? (slice a 0 1) "-"))) rest-args)))
(let ((_ (file-rename (first paths) (nth paths 1))))
(assoc interp :result ""))))
((equal? sub "delete")
(let
((paths
(filter (fn (a) (not (equal? (slice a 0 1) "-"))) rest-args)))
(let
((_
(reduce
(fn (acc p) (let ((_ (file-delete p))) acc))
nil
paths)))
(assoc interp :result ""))))
(else (error (str "file: unknown subcommand \"" sub "\""))))))))
(define

View File

@@ -59,7 +59,7 @@ cat > "$TMPFILE" << EPOCHS
(eval "tcl-test-summary")
EPOCHS
OUTPUT=$(timeout 1200 "$SX_SERVER" < "$TMPFILE" 2>&1)
OUTPUT=$(timeout 2400 "$SX_SERVER" < "$TMPFILE" 2>&1)
[ "$VERBOSE" = "-v" ] && echo "$OUTPUT"
# Extract summary line from epoch 11 output

View File

@@ -303,6 +303,118 @@
:result)
"3")
; 42-49. Phase 5d file metadata + ops
(ok "file-isfile-true"
(get
(run
"set f /tmp/tcl-phase5d-1.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset r [file isfile $f]\nfile delete $f\nreturn $r")
:result)
"1")
(ok "file-isfile-false-on-dir"
(get (run "file isfile /tmp") :result)
"0")
(ok "file-isdir-true"
(get (run "file isdir /tmp") :result)
"1")
(ok "file-size"
(get
(run
"set f /tmp/tcl-phase5d-2.txt\nset c [open $f w]\nputs -nonewline $c hello\nclose $c\nset s [file size $f]\nfile delete $f\nreturn $s")
:result)
"5")
(ok "file-readable-true"
(get (run "file readable /tmp") :result)
"1")
(ok "file-readable-missing"
(get (run "file readable /no/such/path/here") :result)
"0")
(ok "file-mkdir-then-isdir"
(get
(run
"set d /tmp/tcl-phase5d-mkdir/sub\nfile mkdir $d\nset r [file isdir $d]\nfile delete $d\nfile delete /tmp/tcl-phase5d-mkdir\nreturn $r")
:result)
"1")
(ok "file-copy-roundtrip"
(get
(run
"set s /tmp/tcl-phase5d-src.txt\nset d /tmp/tcl-phase5d-dst.txt\nset c [open $s w]\nputs -nonewline $c copydata\nclose $c\nfile copy $s $d\nset c [open $d r]\nset out [read $c]\nclose $c\nfile delete $s\nfile delete $d\nreturn $out")
:result)
"copydata")
(ok "file-rename-then-exists"
(get
(run
"set s /tmp/tcl-phase5d-r1.txt\nset d /tmp/tcl-phase5d-r2.txt\nset c [open $s w]\nputs -nonewline $c x\nclose $c\nfile rename $s $d\nset r [list [file exists $s] [file exists $d]]\nfile delete $d\nreturn $r")
:result)
"0 1")
(ok "file-mtime-positive"
(get
(run
"set f /tmp/tcl-phase5d-mt.txt\nset c [open $f w]\nputs -nonewline $c x\nclose $c\nset m [file mtime $f]\nfile delete $f\nexpr {$m > 0}")
:result)
"1")
; 52-56. Phase 5e clock format options + clock scan
(ok "clock-format-utc"
(get
(run "clock format 0 -format {%Y-%m-%d %H:%M:%S} -gmt 1")
:result)
"1970-01-01 00:00:00")
(ok "clock-format-fmt-default"
(get
(run "clock format 1710513000 -format {%Y-%m-%d} -gmt 1")
:result)
"2024-03-15")
(ok "clock-scan-roundtrip"
(get
(run "set t [clock scan {2024-06-15 12:00:00} -format {%Y-%m-%d %H:%M:%S} -gmt 1]\nclock format $t -format {%Y-%m-%d %H:%M:%S} -gmt 1")
:result)
"2024-06-15 12:00:00")
(ok "clock-scan-returns-int"
(get
(run "expr {[clock scan {1970-01-01 00:00:00} -format {%Y-%m-%d %H:%M:%S} -gmt 1] == 0}")
:result)
"1")
(ok "clock-format-percent-pct"
(get
(run "clock format 0 -format {%Y%%%m} -gmt 1")
:result)
"1970%01")
; 57-59. Phase 5f socket -async (non-blocking connect)
(ok "socket-async-completes-writable"
(get
(run
"proc h {sock host port} { close $sock }\nset srv [socket -server h 18930]\nset c [socket -async localhost 18930]\nset ready 0\nfileevent $c writable {global ready; set ready 1}\nvwait ready\nclose $c\nclose $srv\nset ready")
:result)
"1")
(ok "socket-async-then-write"
(get
(run
"proc accept {sock host port} { global accepted_sock; set accepted_sock $sock; fileevent $sock readable [list reader $sock] }\nproc reader {sock} { global received; gets $sock line; set received $line; close $sock }\nset srv [socket -server accept 18931]\nset c [socket -async localhost 18931]\nfileevent $c writable {global wready; set wready 1; fileevent $::ch writable {}}\nset ::ch $c\nvwait wready\nputs $c async-data\nflush $c\nvwait received\nclose $c\nclose $srv\nset received")
:result)
"async-data")
(ok "socket-async-no-error"
(get
(run
"proc h {sock host port} { close $sock }\nset srv [socket -server h 18932]\nset c [socket -async localhost 18932]\nset r 0\nfileevent $c writable {global r; set r 1}\nvwait r\nset err [fconfigure $c -error]\nclose $c\nclose $srv\nreturn $err")
:result)
"")
(dict
"passed"
tcl-idiom-pass