diff --git a/plans/tcl-sx-completion.md b/plans/tcl-sx-completion.md index 1c1671d9..6522bc04 100644 --- a/plans/tcl-sx-completion.md +++ b/plans/tcl-sx-completion.md @@ -220,6 +220,77 @@ connections. 358/358 green.** --- +## Phase 5d — File metadata + filesystem ops ✓ + +Real implementations of `file isfile`/`isdir`/`readable`/`writable`/`size`/ +`mtime`/`atime`/`type` (previously stubs returning `0`/`""`) and proper +`file delete`/`mkdir`/`copy`/`rename`. + +| Status | Primitive | Wraps | +|---|---|---| +| [x] | `file-size`, `file-mtime`, `file-stat` | `Unix.stat` | +| [x] | `file-isfile?`, `file-isdir?` | `Unix.stat`+`st_kind` | +| [x] | `file-readable?`, `file-writable?` | `Unix.access [R_OK\|W_OK]` | +| [x] | `file-delete` | `Unix.unlink`/`rmdir` (tolerates ENOENT) | +| [x] | `file-mkdir` | recursive `Unix.mkdir 0o755` | +| [x] | `file-copy`, `file-rename` | stdlib I/O / `Sys.rename` | + +`file-stat` returns a dict `{:size :mtime :atime :ctime :mode :type}` with +`:type` ∈ `file|directory|link|fifo|socket|...`. Tcl `file copy`/`rename`/ +`delete` strip leading-`-` flags so `file delete -force` works. + +**Total: ~half day. 10 new idiom tests covering isfile, isdir on /tmp, size, +readable, mkdir + check, copy roundtrip, rename, mtime > 0. 368/368 green.** + +--- + +## Phase 5e — clock format options + clock scan ✓ + +Real `-format`, `-timezone`, and `-gmt` options on `clock format`, and a +working `clock scan` for parsing date strings back to Unix seconds. + +| Status | Work | +|---|---| +| [x] | `clock-format` extended to `(t fmt tz)` with tz ∈ `utc|local` | +| [x] | More format specifiers: `%y` (2-digit year), `%I` (12h hour), `%p` (AM/PM), `%w` (weekday num), `%%` (literal) | +| [x] | `clock-scan` SX primitive: format-driven parser + manual `timegm` (OCaml stdlib lacks it) | +| [x] | Tcl `clock format $secs -format $fmt -timezone $tz -gmt 0\|1` | +| [x] | Tcl `clock scan $str -format $fmt -timezone $tz -gmt 0\|1` | + +Default tz for both is UTC. Format specifiers supported by scan: `%Y %y %m +%d %e %H %I %M %S %%`. Unsupported specifiers in scan are silently skipped +(no validation). + +**Total: ~half day. 5 new idiom tests: clock-format-utc, fmt-default, +scan-roundtrip, scan-returns-int, format-percent-pct. 373/373 green.** + +--- + +## Phase 5f — `socket -async` (non-blocking connect) ✓ + +| Status | Work | +|---|---| +| [x] | `socket-connect-async host port` SX primitive — `Unix.set_nonblock` + `Unix.connect`, catches `EINPROGRESS` | +| [x] | `channel-async-error chan` SX primitive — `Unix.getsockopt_error` | +| [x] | Tcl `socket -async host port` — returns "sockN" immediately | +| [x] | Tcl `fconfigure $chan -error` — queries async-error | + +Connection completes when the channel becomes writable; canonical pattern is +`fileevent $sock writable {handler}`. Channel buffer state is set to +`blocking=false` so subsequent reads/writes don't block. + +**Total: ~few hours. 3 new idiom tests: socket-async-completes-writable, +socket-async-then-write, socket-async-no-error. 376/376 green.** + +**Bug fix landed alongside:** `tcl-call-proc` was discarding `:fileevents`, +`:timers`, and `:procs` updates made inside Tcl procs (only `:commands` was +forwarded). Changed the return to forward the inner `result-interp` as the +base while restoring caller's frame/stack/result/output/code. This was +masked until socket -async made it natural to register a `fileevent` from +inside a proc body (the typical async accept pattern). + +--- + ## Suggested order 1. **Phase 1** — immediate Tcl wins, zero risk, proves the approach @@ -236,6 +307,9 @@ becomes a lasting SX contribution used by every future hosted language. _Newest first._ +- 2026-05-07: Phase 5f socket -async — socket-connect-async (Unix.set_nonblock+connect/EINPROGRESS) + channel-async-error (getsockopt_error); Tcl `socket -async host port` returns immediately; `fconfigure $sock -error` queries async error; +3 idiom tests; 376/376 green +- 2026-05-07: Phase 5e clock options + scan — clock-format extended with tz arg (utc/local) + more specifiers; new clock-scan primitive with manual timegm; Tcl clock format/scan support -format/-timezone/-gmt; +5 idiom tests; 373/373 green +- 2026-05-07: Phase 5d file ops — file-size/mtime/isfile?/isdir?/readable?/writable?/stat/delete/mkdir/copy/rename SX primitives; Tcl file isfile/isdir/readable/writable/size/mtime/atime/type/mkdir/copy/rename/delete now real; +10 idiom tests; 368/368 green - 2026-05-07: Phase 5c sockets — socket-connect/socket-server/socket-accept SX primitives wrapping Unix.socket/connect/bind/listen/accept; tcl-cmd-socket dispatches client (host port) vs server (-server cb port); server auto-registers fileevent → _sock-do-accept handler that calls user callback per accept; puts now dispatches "sockN" channels to channel-write too; +4 idiom tests; 358/358 green - 2026-05-07: Phase 5b event loop — io-select-channels SX primitive + Tcl-side fileevent/after/vwait/update; tcl-event-step drives expired timers + Unix.select on registered channels; +5 idiom tests; 354/354 green - 2026-05-07: Phase 5 channel I/O — 11 SX primitives (channel-open/close/read/read-line/write/flush/seek/tell/eof?/blocking?/set-blocking!) wrapping Unix.openfile/read/write/lseek/set_nonblock; tcl-cmd-open/close/read/gets-chan/seek/tell/flush rewritten + new tcl-cmd-fconfigure; tcl-cmd-puts dispatches on "fileN" arg; gets registration fixed; +7 idiom tests; 349/349 green @@ -252,8 +326,8 @@ _Newest first._ ## What stays out of scope -- `package require` of binary loadables -- Full `clock format` locale support +- `package require` of binary loadables (would need `Dynlink` + native ABI design) +- Full `clock format` locale (translated month/day names, `LC_TIME`-aware) — Phase 5e covers `-format`/`-timezone`/`-gmt` with English names - Tk / GUI - Threads (mapped to coroutines only, as planned) - Server-mode `vwait` — Phase 5b event loop is scoped to script-mode; from inside a server-handled command it can't see sx_server's stdin scheduler