diff --git a/lib/lua/runtime.sx b/lib/lua/runtime.sx index dddbbfb4..1e13f01f 100644 --- a/lib/lua/runtime.sx +++ b/lib/lua/runtime.sx @@ -991,3 +991,141 @@ (dict-set! math "modf" lua-math-modf) (dict-set! math "random" lua-math-random) (dict-set! math "randomseed" lua-math-randomseed) + +;; ── table library ───────────────────────────────────────────── +(define table {}) + +(define + lua-table-insert + (fn (&rest args) + (cond + ((= (len args) 2) + (let ((t (first args)) (v (nth args 1))) + (let ((n (lua-len t))) + (dict-set! t (str (+ n 1)) v)))) + ((= (len args) 3) + (let ((t (first args)) (pos (nth args 1)) (v (nth args 2))) + (let ((n (lua-len t))) + (begin + (define + tbl-shift-up + (fn (i) + (when (>= i pos) + (begin + (dict-set! t (str (+ i 1)) (get t (str i))) + (tbl-shift-up (- i 1)))))) + (tbl-shift-up n) + (dict-set! t (str pos) v))))) + (else (error "lua: table.insert: wrong args"))))) + +(define + lua-table-remove + (fn (&rest args) + (let ((t (first args))) + (let ((n (lua-len t))) + (let ((pos (if (> (len args) 1) (nth args 1) n))) + (cond + ((<= n 0) nil) + (else + (let ((v (get t (str pos)))) + (begin + (define + tbl-shift-down + (fn (i) + (when (< i n) + (begin + (dict-set! t (str i) (get t (str (+ i 1)))) + (tbl-shift-down (+ i 1)))))) + (tbl-shift-down pos) + (dict-set! t (str n) nil) + v))))))))) + +(define + lua-table-concat + (fn (&rest args) + (let ((t (first args)) + (sep (if (> (len args) 1) (nth args 1) "")) + (i (if (> (len args) 2) (nth args 2) 1)) + (j (if (> (len args) 3) (nth args 3) (lua-len (first args))))) + (cond + ((> i j) "") + (else + (let ((out (lua-concat-coerce (get t (str i))))) + (begin + (define + loop + (fn (k) + (when (<= k j) + (begin + (set! out (str out sep (lua-concat-coerce (get t (str k))))) + (loop (+ k 1)))))) + (loop (+ i 1)) + out))))))) + +;; Simple insertion sort for tables +(define + lua-table-sort + (fn (&rest args) + (let ((t (first args)) + (comp (if (> (len args) 1) (nth args 1) nil))) + (let ((n (lua-len t))) + (begin + (define + lt? + (fn (a b) + (cond + ((= comp nil) (lua-lt a b)) + (else (lua-truthy? (lua-call comp a b)))))) + (define + insert-sorted + (fn (i) + (when (> i 1) + (let ((v (get t (str i))) (prev (get t (str (- i 1))))) + (when (lt? v prev) + (begin + (dict-set! t (str i) prev) + (dict-set! t (str (- i 1)) v) + (insert-sorted (- i 1)))))))) + (define + outer + (fn (i) + (when (<= i n) + (begin + (insert-sorted i) + (outer (+ i 1)))))) + (outer 2) + nil))))) + +(define + lua-unpack + (fn (&rest args) + (let ((t (first args)) + (i (if (> (len args) 1) (nth args 1) 1)) + (j (if (> (len args) 2) (nth args 2) (lua-len (first args))))) + (cond + ((> i j) nil) + (else + (let ((out (list (quote lua-multi)))) + (begin + (define + loop + (fn (k) + (when (<= k j) + (begin + (set! out (append out (list (get t (str k))))) + (loop (+ k 1)))))) + (loop i) + out))))))) + +(define + lua-table-maxn + (fn (t) (lua-len t))) + +(dict-set! table "insert" lua-table-insert) +(dict-set! table "remove" lua-table-remove) +(dict-set! table "concat" lua-table-concat) +(dict-set! table "sort" lua-table-sort) +(dict-set! table "unpack" lua-unpack) +(dict-set! table "maxn" lua-table-maxn) + +(define unpack lua-unpack) diff --git a/lib/lua/test.sh b/lib/lua/test.sh index 1cccca81..8566769a 100755 --- a/lib/lua/test.sh +++ b/lib/lua/test.sh @@ -804,6 +804,34 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1261) (eval "(lua-eval-ast \"local r = math.random(5, 10) return r >= 5 and r <= 10\")") +;; ── Phase 6: table library ──────────────────────────────────── +(epoch 1300) +(eval "(lua-eval-ast \"local t = {1, 2, 3} table.insert(t, 4) return #t\")") +(epoch 1301) +(eval "(lua-eval-ast \"local t = {1, 2, 3} table.insert(t, 4) return t[4]\")") +(epoch 1302) +(eval "(lua-eval-ast \"local t = {10, 30} table.insert(t, 2, 20) return t[2]\")") +(epoch 1303) +(eval "(lua-eval-ast \"local t = {10, 20, 30} table.insert(t, 1, 5) return t[1] * 100 + t[2]\")") +(epoch 1310) +(eval "(lua-eval-ast \"local t = {1, 2, 3} local v = table.remove(t) return v * 10 + #t\")") +(epoch 1311) +(eval "(lua-eval-ast \"local t = {10, 20, 30} table.remove(t, 1) return t[1] * 10 + t[2]\")") +(epoch 1320) +(eval "(lua-eval-ast \"local t = {\\\"a\\\", \\\"b\\\", \\\"c\\\"} return table.concat(t)\")") +(epoch 1321) +(eval "(lua-eval-ast \"local t = {\\\"a\\\", \\\"b\\\", \\\"c\\\"} return table.concat(t, \\\"-\\\")\")") +(epoch 1322) +(eval "(lua-eval-ast \"local t = {1, 2, 3, 4} return table.concat(t, \\\",\\\", 2, 3)\")") +(epoch 1330) +(eval "(lua-eval-ast \"local t = {3, 1, 4, 1, 5, 9, 2, 6} table.sort(t) return t[1] * 100 + t[8]\")") +(epoch 1331) +(eval "(lua-eval-ast \"local t = {3, 1, 2} table.sort(t, function(a, b) return a > b end) return t[1] * 100 + t[3]\")") +(epoch 1340) +(eval "(lua-eval-ast \"local t = {10, 20, 30} local a, b, c = unpack(t) return a + b + c\")") +(epoch 1341) +(eval "(lua-eval-ast \"local t = {10, 20, 30, 40} local a, b = table.unpack(t, 2, 3) return a + b\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1206,6 +1234,21 @@ check 1251 "math.modf(3.5) int part" '3' check 1260 "math.random(n) in range" 'true' check 1261 "math.random(m,n) in range" 'true' +# ── Phase 6: table library ──────────────────────────────────── +check 1300 "table.insert append → #t" '4' +check 1301 "table.insert value" '4' +check 1302 "table.insert(t,pos,v) mid" '20' +check 1303 "table.insert(t,1,v) prepend" '510' +check 1310 "table.remove() last" '33' +check 1311 "table.remove(t,1) shift" '230' +check 1320 "table.concat no sep" '"abc"' +check 1321 "table.concat with sep" '"a-b-c"' +check 1322 "table.concat range" '"2,3"' +check 1330 "table.sort asc" '109' +check 1331 "table.sort desc via cmp" '301' +check 1340 "unpack global" '60' +check 1341 "table.unpack(t,i,j)" '50' + 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 4e8f7b5f..804bba48 100644 --- a/plans/lua-on-sx.md +++ b/plans/lua-on-sx.md @@ -70,7 +70,7 @@ Each item: implement → tests → tick box → update progress log. ### Phase 6 — standard library - [x] `string` — `format`, `sub`, `find`, `match`, `gmatch`, `gsub`, `len`, `rep`, `upper`, `lower`, `byte`, `char` - [x] `math` — full surface -- [ ] `table` — `insert`, `remove`, `concat`, `sort`, `unpack` +- [x] `table` — `insert`, `remove`, `concat`, `sort`, `unpack` - [ ] `io` — minimal stub (read/write to SX IO surface) - [ ] `os` — time/date subset @@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log. _Newest first. Agent appends on every commit._ +- 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. - 2026-04-24: lua: phase 5 — coroutines (create/resume/yield/status/wrap) via `call/cc` (perform/cek-resume not exposed to SX userland). Handles multi-yield + final return + arg passthrough. Fix: body's final return must jump via `caller-k` to the **current** resume's caller, not unwind through the stale first-call continuation. 273 tests.