From c6b7e19892fd1fa6be88dd72404c8428c796d582 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 24 Apr 2026 19:07:31 +0000 Subject: [PATCH] lua: io stub (buffered) + print/tostring/tonumber +12 tests --- lib/lua/runtime.sx | 109 +++++++++++++++++++++++++++++++++++++++++++++ lib/lua/test.sh | 40 +++++++++++++++++ plans/lua-on-sx.md | 4 +- 3 files changed, 152 insertions(+), 1 deletion(-) diff --git a/lib/lua/runtime.sx b/lib/lua/runtime.sx index 1e13f01f..99a49521 100644 --- a/lib/lua/runtime.sx +++ b/lib/lua/runtime.sx @@ -1129,3 +1129,112 @@ (dict-set! table "maxn" lua-table-maxn) (define unpack lua-unpack) + +;; ── io library (minimal stub — buffered; no real stdio) ─────── +(define io {}) +(define __io-buffer "") + +(define + lua-io-write + (fn (&rest args) + (begin + (define + loop + (fn (i) + (when (< i (len args)) + (begin + (set! __io-buffer (str __io-buffer (lua-concat-coerce (nth args i)))) + (loop (+ i 1)))))) + (loop 0) + io))) + +(define + lua-io-read + (fn (&rest args) nil)) + +(define + lua-io-open + (fn (&rest args) nil)) + +(define + lua-io-lines + (fn (&rest args) (fn (&rest __) nil))) + +(define + lua-io-close + (fn (&rest args) true)) + +(define + lua-io-flush + (fn (&rest args) nil)) + +(define + lua-io-buffer + (fn () + (let ((out __io-buffer)) + (begin + (set! __io-buffer "") + out)))) + +;; print(a, b, c) — args joined by tab, trailing newline +(define + lua-print + (fn (&rest args) + (begin + (define + loop + (fn (i) + (when (< i (len args)) + (begin + (when (> i 0) (set! __io-buffer (str __io-buffer "\t"))) + (set! __io-buffer (str __io-buffer (lua-to-display (nth args i)))) + (loop (+ i 1)))))) + (loop 0) + (set! __io-buffer (str __io-buffer "\n")) + nil))) + +(define + lua-to-display + (fn (v) + (cond + ((= v nil) "nil") + ((= v true) "true") + ((= v false) "false") + ((= (type-of v) "string") v) + ((= (type-of v) "number") (str v)) + ((= (type-of v) "dict") (str "table")) + ((or (= (type-of v) "function") (= (type-of v) "lambda")) "function") + (else (str v))))) + +(define + lua-tostring + (fn (v) + (cond + ((= (type-of v) "dict") + (let ((m (lua-get-mm v "__tostring"))) + (cond + ((not (= m nil)) (lua-first (m v))) + (else (lua-to-display v))))) + (else (lua-to-display v))))) + +(define + lua-tonumber + (fn (&rest args) + (let ((v (first args)) + (base (if (> (len args) 1) (nth args 1) nil))) + (cond + ((= (type-of v) "number") v) + ((= (type-of v) "string") (lua-to-number v)) + (else nil))))) + +(dict-set! io "write" lua-io-write) +(dict-set! io "read" lua-io-read) +(dict-set! io "open" lua-io-open) +(dict-set! io "lines" lua-io-lines) +(dict-set! io "close" lua-io-close) +(dict-set! io "flush" lua-io-flush) +(dict-set! io "__buffer" lua-io-buffer) + +(define print lua-print) +(define tostring lua-tostring) +(define tonumber lua-tonumber) diff --git a/lib/lua/test.sh b/lib/lua/test.sh index 8566769a..92ca2d5f 100755 --- a/lib/lua/test.sh +++ b/lib/lua/test.sh @@ -832,6 +832,32 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1341) (eval "(lua-eval-ast \"local t = {10, 20, 30, 40} local a, b = table.unpack(t, 2, 3) return a + b\")") +;; ── Phase 6: io stub + print/tostring/tonumber ──────────────── +(epoch 1400) +(eval "(lua-eval-ast \"io.write(\\\"hello\\\") return io.__buffer()\")") +(epoch 1401) +(eval "(lua-eval-ast \"io.write(\\\"a\\\", \\\"b\\\", \\\"c\\\") return io.__buffer()\")") +(epoch 1402) +(eval "(lua-eval-ast \"io.write(1, \\\" \\\", 2) return io.__buffer()\")") +(epoch 1410) +(eval "(lua-eval-ast \"print(\\\"x\\\", \\\"y\\\") return io.__buffer()\")") +(epoch 1411) +(eval "(lua-eval-ast \"print(1, 2, 3) return io.__buffer()\")") +(epoch 1420) +(eval "(lua-eval-ast \"return tostring(42)\")") +(epoch 1421) +(eval "(lua-eval-ast \"return tostring(nil)\")") +(epoch 1422) +(eval "(lua-eval-ast \"return tostring({})\")") +(epoch 1430) +(eval "(lua-eval-ast \"return tonumber(\\\"42\\\")\")") +(epoch 1431) +(eval "(lua-eval-ast \"if tonumber(\\\"abc\\\") == nil then return 1 else return 0 end\")") +(epoch 1440) +(eval "(lua-eval-ast \"if io.read() == nil then return 1 else return 0 end\")") +(epoch 1441) +(eval "(lua-eval-ast \"if io.open(\\\"x\\\") == nil then return 1 else return 0 end\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1249,6 +1275,20 @@ check 1331 "table.sort desc via cmp" '301' check 1340 "unpack global" '60' check 1341 "table.unpack(t,i,j)" '50' +# ── Phase 6: io stub + print/tostring/tonumber ──────────────── +check 1400 "io.write single" '"hello"' +check 1401 "io.write multi strings" '"abc"' +check 1402 "io.write numbers + spaces" '"1 2"' +check 1410 "print two args tab-sep + NL" '"x\ty\n"' +check 1411 "print three ints" '"1\t2\t3\n"' +check 1420 "tostring(42)" '"42"' +check 1421 "tostring(nil)" '"nil"' +check 1422 "tostring({})" '"table"' +check 1430 "tonumber(\"42\")" '42' +check 1431 "tonumber(\"abc\") → nil" '1' +check 1440 "io.read() → nil" '1' +check 1441 "io.open(x) → nil" '1' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "ok $PASS/$TOTAL Lua-on-SX tests passed" diff --git a/plans/lua-on-sx.md b/plans/lua-on-sx.md index 804bba48..ff6602de 100644 --- a/plans/lua-on-sx.md +++ b/plans/lua-on-sx.md @@ -71,7 +71,7 @@ Each item: implement → tests → tick box → update progress log. - [x] `string` — `format`, `sub`, `find`, `match`, `gmatch`, `gsub`, `len`, `rep`, `upper`, `lower`, `byte`, `char` - [x] `math` — full surface - [x] `table` — `insert`, `remove`, `concat`, `sort`, `unpack` -- [ ] `io` — minimal stub (read/write to SX IO surface) +- [x] `io` — minimal stub (read/write to SX IO surface) - [ ] `os` — time/date subset ### Phase 7 — modules + full conformance @@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log. _Newest first. Agent appends on every commit._ +- 2026-04-24: lua: `io` stub + `print`/`tostring`/`tonumber` globals. io buffers to internal `__io-buffer` (tests drain it via `io.__buffer()`). print: tab-sep + NL. tostring respects `__tostring` metamethod. 334 tests. - 2026-04-24: lua: `table` lib — insert (append / at pos, shifts up), remove (last / at pos, shifts down), concat (sep, i, j), sort (insertion sort, optional cmp), unpack + table.unpack, maxn. Caught trap: local helper named `shift` collides with SX's `shift` special form → renamed to `tbl-shift-up`/`tbl-shift-down`. 322 tests. - 2026-04-24: lua: `math` lib — pi/huge + abs/ceil/floor/sqrt/exp/log/log10/pow/trig (sin/cos/tan/asin/acos/atan/atan2)/deg/rad/min/max (&rest)/fmod/modf/random (0/1/2 arg)/randomseed. Most ops delegate to SX primitives; log w/ base via change-of-base. 309 tests. - 2026-04-24: lua: `string` lib — len/upper/lower/rep/sub (1-idx + neg)/byte/char/find/match/gmatch/gsub/format. Patterns are literal-only (no `%d`/etc.); format is `%s`/`%d`/`%f`/`%%` only. `string.char` uses printable-ASCII lookup + tab/nl/cr. 292 tests. @@ -108,6 +109,7 @@ _Shared-file issues that need someone else to fix. Minimal repro only._ ## Known limitations (own code, not shared) +- **`io` library is a stub** — `io.write`/`print` append to an internal `__io-buffer` (accessible via `io.__buffer()` which returns + clears it) instead of real stdout. `io.read`/`open`/`lines` return nil. Suitable for tests that inspect output; no actual stdio. - **`string.find`/`match`/`gmatch`/`gsub` patterns are LITERAL only** — no `%d`/`%a`/`.`/`*`/`+`/etc. Implementing Lua patterns is a separate work item; literal search covers the common case. - **`string.format`** supports only `%s`, `%d`, `%f`, `%%`. No width/precision flags (`%.2f`, `%5d`). - **`string.char`** supports printable ASCII 32–126 plus `\t`/`\n`/`\r`; other codes error.