From 24fde8aa2f816be86827c6b723a8a3da5c90000d Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 24 Apr 2026 19:19:36 +0000 Subject: [PATCH] lua: require/package via package.preload +5 tests --- lib/lua/runtime.sx | 25 +++++++++++++++++++++++++ lib/lua/test.sh | 19 +++++++++++++++++++ plans/lua-on-sx.md | 4 +++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/lua/runtime.sx b/lib/lua/runtime.sx index b23d721b..d97600fe 100644 --- a/lib/lua/runtime.sx +++ b/lib/lua/runtime.sx @@ -1315,3 +1315,28 @@ (dict-set! os "rename" lua-os-rename) (dict-set! os "tmpname" lua-os-tmpname) (dict-set! os "execute" lua-os-execute) + +;; ── package / require ───────────────────────────────────────── +(define package {}) +(define __package-loaded {}) +(define __package-preload {}) + +(dict-set! package "loaded" __package-loaded) +(dict-set! package "preload" __package-preload) +(dict-set! package "path" "?;?.lua") + +(define + lua-require + (fn (name) + (cond + ((has-key? __package-loaded name) (get __package-loaded name)) + ((has-key? __package-preload name) + (let ((loader (get __package-preload name))) + (let ((m (lua-call loader name))) + (let ((result (if (= m nil) true m))) + (begin + (dict-set! __package-loaded name result) + result))))) + (else (error (str "lua: module '" name "' not found")))))) + +(define require lua-require) diff --git a/lib/lua/test.sh b/lib/lua/test.sh index acc0d006..fb29e326 100755 --- a/lib/lua/test.sh +++ b/lib/lua/test.sh @@ -876,6 +876,18 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1507) (eval "(lua-eval-ast \"return type(os.tmpname())\")") +;; ── Phase 7: require / package ──────────────────────────────── +(epoch 1600) +(eval "(lua-eval-ast \"package.preload.mymath = function() local m = {} m.add = function(a, b) return a + b end return m end local mm = require(\\\"mymath\\\") return mm.add(3, 4)\")") +(epoch 1601) +(eval "(lua-eval-ast \"package.preload.counter = function() local n = 0 local m = {} m.inc = function() n = n + 1 return n end return m end local c1 = require(\\\"counter\\\") local c2 = require(\\\"counter\\\") c1.inc() c1.inc() return c2.inc()\")") +(epoch 1602) +(eval "(lua-eval-ast \"local ok, err = pcall(require, \\\"nope\\\") if ok then return 0 else return 1 end\")") +(epoch 1603) +(eval "(lua-eval-ast \"package.preload.x = function() return {val = 42} end require(\\\"x\\\") return package.loaded.x.val\")") +(epoch 1604) +(eval "(lua-eval-ast \"package.preload.noret = function() end local r = require(\\\"noret\\\") return type(r)\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1317,6 +1329,13 @@ check 1505 "os.date(*t).year" '1970' check 1506 "os.getenv → nil" '1' check 1507 "os.tmpname → string" '"string"' +# ── Phase 7: require / package ──────────────────────────────── +check 1600 "require(preload) + call" '7' +check 1601 "require returns cached" '3' +check 1602 "require unknown module errors" '1' +check 1603 "package.loaded populated" '42' +check 1604 "nil return caches as true" '"boolean"' + 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 0eeaaf93..b60a6801 100644 --- a/plans/lua-on-sx.md +++ b/plans/lua-on-sx.md @@ -75,13 +75,14 @@ Each item: implement → tests → tick box → update progress log. - [x] `os` — time/date subset ### Phase 7 — modules + full conformance -- [ ] `require` / `package` via SX `define-library`/`import` +- [x] `require` / `package` via SX `define-library`/`import` - [ ] Drive PUC-Rio scoreboard to 100% ## Progress log _Newest first. Agent appends on every commit._ +- 2026-04-24: lua: `require`/`package` via preload-only (no filesystem search). `package.loaded` caching, nil-returning modules cache as `true`, unknown modules error. 347 tests. - 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. @@ -110,6 +111,7 @@ _Shared-file issues that need someone else to fix. Minimal repro only._ ## Known limitations (own code, not shared) +- **`require` supports `package.preload` only** — no filesystem search (we don't have Lua-file resolution inside sx_server). Users register a loader in `package.preload.name` and `require("name")` calls it with name as arg. Results cached in `package.loaded`; nil return caches as `true` per Lua convention. - **`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.