diff --git a/lib/lua/runtime.sx b/lib/lua/runtime.sx index 99a49521..b23d721b 100644 --- a/lib/lua/runtime.sx +++ b/lib/lua/runtime.sx @@ -1238,3 +1238,80 @@ (define print lua-print) (define tostring lua-tostring) (define tonumber lua-tonumber) + +;; ── os library (minimal stub — no real clock/filesystem) ────── +(define os {}) +(define __os-counter 0) + +(define + lua-os-time + (fn (&rest args) + (begin + (set! __os-counter (+ __os-counter 1)) + __os-counter))) + +(define + lua-os-clock + (fn () + (/ __os-counter 1000))) + +(define + lua-os-difftime + (fn (t2 t1) (- t2 t1))) + +(define + lua-os-date + (fn (&rest args) + (let ((fmt (if (> (len args) 0) (first args) "%c"))) + (cond + ((= fmt "*t") + (let ((d {})) + (begin + (dict-set! d "year" 1970) + (dict-set! d "month" 1) + (dict-set! d "day" 1) + (dict-set! d "hour" 0) + (dict-set! d "min" 0) + (dict-set! d "sec" 0) + (dict-set! d "wday" 5) + (dict-set! d "yday" 1) + (dict-set! d "isdst" false) + d))) + (else "1970-01-01 00:00:00"))))) + +(define + lua-os-getenv + (fn (name) nil)) + +(define + lua-os-exit + (fn (&rest args) (error "lua: os.exit called"))) + +(define + lua-os-remove + (fn (name) + (list (quote lua-multi) nil "os.remove not supported"))) + +(define + lua-os-rename + (fn (a b) + (list (quote lua-multi) nil "os.rename not supported"))) + +(define + lua-os-tmpname + (fn () "/tmp/lua_stub")) + +(define + lua-os-execute + (fn (&rest args) 0)) + +(dict-set! os "time" lua-os-time) +(dict-set! os "clock" lua-os-clock) +(dict-set! os "difftime" lua-os-difftime) +(dict-set! os "date" lua-os-date) +(dict-set! os "getenv" lua-os-getenv) +(dict-set! os "exit" lua-os-exit) +(dict-set! os "remove" lua-os-remove) +(dict-set! os "rename" lua-os-rename) +(dict-set! os "tmpname" lua-os-tmpname) +(dict-set! os "execute" lua-os-execute) diff --git a/lib/lua/test.sh b/lib/lua/test.sh index 92ca2d5f..acc0d006 100755 --- a/lib/lua/test.sh +++ b/lib/lua/test.sh @@ -858,6 +858,24 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1441) (eval "(lua-eval-ast \"if io.open(\\\"x\\\") == nil then return 1 else return 0 end\")") +;; ── Phase 6: os stub ────────────────────────────────────────── +(epoch 1500) +(eval "(lua-eval-ast \"return type(os.time())\")") +(epoch 1501) +(eval "(lua-eval-ast \"local t1 = os.time() local t2 = os.time() return t2 > t1\")") +(epoch 1502) +(eval "(lua-eval-ast \"return os.difftime(100, 60)\")") +(epoch 1503) +(eval "(lua-eval-ast \"return type(os.clock())\")") +(epoch 1504) +(eval "(lua-eval-ast \"return type(os.date())\")") +(epoch 1505) +(eval "(lua-eval-ast \"local d = os.date(\\\"*t\\\") return d.year\")") +(epoch 1506) +(eval "(lua-eval-ast \"if os.getenv(\\\"HOME\\\") == nil then return 1 else return 0 end\")") +(epoch 1507) +(eval "(lua-eval-ast \"return type(os.tmpname())\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1289,6 +1307,16 @@ check 1431 "tonumber(\"abc\") → nil" '1' check 1440 "io.read() → nil" '1' check 1441 "io.open(x) → nil" '1' +# ── Phase 6: os stub ────────────────────────────────────────── +check 1500 "os.time → number" '"number"' +check 1501 "os.time monotonic" 'true' +check 1502 "os.difftime" '40' +check 1503 "os.clock → number" '"number"' +check 1504 "os.date() default" '"string"' +check 1505 "os.date(*t).year" '1970' +check 1506 "os.getenv → nil" '1' +check 1507 "os.tmpname → string" '"string"' + 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 ff6602de..0eeaaf93 100644 --- a/plans/lua-on-sx.md +++ b/plans/lua-on-sx.md @@ -72,7 +72,7 @@ Each item: implement → tests → tick box → update progress log. - [x] `math` — full surface - [x] `table` — `insert`, `remove`, `concat`, `sort`, `unpack` - [x] `io` — minimal stub (read/write to SX IO surface) -- [ ] `os` — time/date subset +- [x] `os` — time/date subset ### Phase 7 — modules + full conformance - [ ] `require` / `package` via SX `define-library`/`import` @@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log. _Newest first. Agent appends on every commit._ +- 2026-04-24: lua: `os` stub — time/clock monotonic counter, difftime, date (default string / `*t` dict), getenv/remove/rename/tmpname/execute/exit stubs. Phase 6 complete. 342 tests. - 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. @@ -109,6 +110,7 @@ _Shared-file issues that need someone else to fix. Minimal repro only._ ## Known limitations (own code, not shared) +- **`os` library is a stub** — `os.time()` returns a monotonic counter (not Unix epoch), `os.clock()` = counter/1000, `os.date()` returns hardcoded "1970-01-01 00:00:00" or a `*t` table with fixed fields; `os.getenv` returns nil; `os.remove`/`rename` return nil+error. No real clock/filesystem access. - **`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`).