lua: metatable dispatch (__index/__newindex/arith/cmp/__call/__len) +23 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled

This commit is contained in:
2026-04-24 17:57:27 +00:00
parent 4815db461b
commit 43c13c4eb1
4 changed files with 313 additions and 33 deletions

View File

@@ -41,27 +41,77 @@
((= op "^") (pow na nb))
(else (error (str "lua: unknown arith op " op))))))))
(define lua-add (fn (a b) (lua-num-op "+" a b)))
(define
lua-get-mm
(fn
(v name)
(cond
((not (= (type-of v) "dict")) nil)
(else
(let
((mt (get v "__meta")))
(cond
((= mt nil) nil)
((not (= (type-of mt) "dict")) nil)
((has-key? mt name) (get mt name))
(else nil)))))))
(define lua-sub (fn (a b) (lua-num-op "-" a b)))
(define
lua-arith
(fn
(mm op a b)
(cond
((and (= (type-of a) "number") (= (type-of b) "number"))
(lua-num-op op a b))
((and
(or (= (type-of a) "number") (= (type-of a) "string"))
(or (= (type-of b) "number") (= (type-of b) "string"))
(not (= (lua-to-number a) nil))
(not (= (lua-to-number b) nil)))
(lua-num-op op a b))
(else
(let
((m (lua-get-mm a mm)))
(cond
((not (= m nil)) (lua-first (m a b)))
(else
(let
((m2 (lua-get-mm b mm)))
(if
(not (= m2 nil))
(lua-first (m2 a b))
(error (str "lua: arith on " (type-of a) " " op " " (type-of b))))))))))))
(define lua-mul (fn (a b) (lua-num-op "*" a b)))
(define lua-add (fn (a b) (lua-arith "__add" "+" a b)))
(define lua-div (fn (a b) (lua-num-op "/" a b)))
(define lua-sub (fn (a b) (lua-arith "__sub" "-" a b)))
(define lua-mod (fn (a b) (lua-num-op "%" a b)))
(define lua-mul (fn (a b) (lua-arith "__mul" "*" a b)))
(define lua-pow (fn (a b) (lua-num-op "^" a b)))
(define lua-div (fn (a b) (lua-arith "__div" "/" a b)))
(define lua-mod (fn (a b) (lua-arith "__mod" "%" a b)))
(define lua-pow (fn (a b) (lua-arith "__pow" "^" a b)))
(define
lua-neg
(fn
(a)
(let
((na (lua-to-number a)))
(begin
(when (= na nil) (error (str "lua: neg on non-number: " a)))
(- 0 na)))))
(cond
((= (type-of a) "number") (- 0 a))
(else
(let
((na (lua-to-number a)))
(cond
((not (= na nil)) (- 0 na))
(else
(let
((m (lua-get-mm a "__unm")))
(if
(not (= m nil))
(lua-first (m a))
(error (str "lua: neg on non-number: " (type-of a))))))))))))
(define
lua-concat-coerce
@@ -76,9 +126,23 @@
lua-concat
(fn
(a b)
(let
((sa (lua-concat-coerce a)) (sb (lua-concat-coerce b)))
(str sa sb))))
(cond
((and
(or (= (type-of a) "string") (= (type-of a) "number"))
(or (= (type-of b) "string") (= (type-of b) "number")))
(str (lua-concat-coerce a) (lua-concat-coerce b)))
(else
(let
((m (lua-get-mm a "__concat")))
(cond
((not (= m nil)) (lua-first (m a b)))
(else
(let
((m2 (lua-get-mm b "__concat")))
(if
(not (= m2 nil))
(lua-first (m2 a b))
(error (str "lua: concat on " (type-of a) " and " (type-of b))))))))))))
(define
lua-eq
@@ -88,6 +152,13 @@
((and (= a nil) (= b nil)) true)
((or (= a nil) (= b nil)) false)
((and (= (type-of a) (type-of b)) (= a b)) true)
((and (= (type-of a) "dict") (= (type-of b) "dict"))
(let
((m (lua-get-mm a "__eq")))
(cond
((not (= m nil))
(let ((r (lua-first (m a b)))) (and (not (= r nil)) (not (= r false)))))
(else false))))
(else false))))
(define lua-neq (fn (a b) (not (lua-eq a b))))
@@ -99,9 +170,41 @@
(cond
((and (= (type-of a) "number") (= (type-of b) "number")) (< a b))
((and (= (type-of a) "string") (= (type-of b) "string")) (< a b))
(else (error "lua: attempt to compare incompatible types")))))
(else
(let
((m (lua-get-mm a "__lt")))
(cond
((not (= m nil))
(let ((r (lua-first (m a b)))) (and (not (= r nil)) (not (= r false)))))
(else
(let
((m2 (lua-get-mm b "__lt")))
(cond
((not (= m2 nil))
(let ((r (lua-first (m2 a b)))) (and (not (= r nil)) (not (= r false)))))
(else (error "lua: attempt to compare incompatible types")))))))))))
(define lua-le (fn (a b) (or (lua-lt a b) (lua-eq a b))))
(define
lua-le
(fn
(a b)
(cond
((and (= (type-of a) "number") (= (type-of b) "number")) (<= a b))
((and (= (type-of a) "string") (= (type-of b) "string"))
(or (< a b) (= a b)))
(else
(let
((m (lua-get-mm a "__le")))
(cond
((not (= m nil))
(let ((r (lua-first (m a b)))) (and (not (= r nil)) (not (= r false)))))
(else
(let
((m2 (lua-get-mm b "__le")))
(cond
((not (= m2 nil))
(let ((r (lua-first (m2 a b)))) (and (not (= r nil)) (not (= r false)))))
(else (not (lua-lt b a))))))))))))
(define lua-gt (fn (a b) (lua-lt b a)))
@@ -116,17 +219,22 @@
((= (type-of a) "list") (len a))
((= (type-of a) "dict")
(let
((n 0))
(begin
(define
count-loop
(fn
(i)
(if
(has-key? a (str i))
(begin (set! n i) (count-loop (+ i 1)))
n)))
(count-loop 1))))
((m (lua-get-mm a "__len")))
(cond
((not (= m nil)) (lua-first (m a)))
(else
(let
((n 0))
(begin
(define
count-loop
(fn
(i)
(if
(has-key? a (str i))
(begin (set! n i) (count-loop (+ i 1)))
n)))
(count-loop 1)))))))
(else (error (str "lua: len on non-len type: " (type-of a)))))))
(define
@@ -166,9 +274,37 @@
lua-get
(fn
(t k)
(if (= t nil) nil (let ((v (get t (str k)))) (if (= v nil) nil v)))))
(cond
((= t nil) nil)
((not (= (type-of t) "dict")) nil)
(else
(let
((key (str k)))
(cond
((has-key? t key) (get t key))
(else
(let
((m (lua-get-mm t "__index")))
(cond
((= m nil) nil)
((= (type-of m) "dict") (lua-get m k))
(else (lua-first (m t k))))))))))))
(define lua-set! (fn (t k v) (dict-set! t (str k) v)))
(define
lua-set!
(fn
(t k v)
(let
((key (str k)))
(cond
((has-key? t key) (dict-set! t key v))
(else
(let
((m (lua-get-mm t "__newindex")))
(cond
((= m nil) (dict-set! t key v))
((= (type-of m) "dict") (lua-set! m k v))
(else (begin (m t k v) nil)))))))))
(define
lua-multi?
@@ -215,3 +351,52 @@
(cond
((= (len vals) 0) (list (quote lua-multi)))
(else (cons (quote lua-multi) (lua-pack-build vals 0))))))
(define lua-setmetatable (fn (t mt) (begin (dict-set! t "__meta" mt) t)))
(define
lua-getmetatable
(fn
(t)
(cond
((not (= (type-of t) "dict")) nil)
((has-key? t "__meta") (get t "__meta"))
(else nil))))
(define setmetatable lua-setmetatable)
(define getmetatable lua-getmetatable)
(define
lua-type
(fn
(v)
(cond
((= v nil) "nil")
((= (type-of v) "number") "number")
((= (type-of v) "string") "string")
((= v true) "boolean")
((= v false) "boolean")
((= (type-of v) "dict") "table")
((or (= (type-of v) "function") (= (type-of v) "lambda")) "function")
(else (type-of v)))))
(define type lua-type)
(define sx-apply-ref apply)
(define
lua-call
(fn
(&rest args)
(let
((f (first args)) (rargs (rest args)))
(cond
((or (= (type-of f) "function") (= (type-of f) "lambda"))
(sx-apply-ref f rargs))
((= (type-of f) "dict")
(let
((m (lua-get-mm f "__call")))
(cond
((= m nil) (error "lua: attempt to call non-function"))
(else (sx-apply-ref m (cons f rargs))))))
(else (error "lua: attempt to call non-function"))))))

View File

@@ -594,6 +594,71 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 770)
(eval "(lua-eval-ast \"local t = {} local s = t s.x = 42 return t.x\")")
;; ── Phase 4: metatables ────────────────────────────────────────
;; setmetatable / getmetatable
(epoch 800)
(eval "(lua-eval-ast \"local t = {} local r = setmetatable(t, {}) if r == t then return 1 else return 0 end\")")
(epoch 801)
(eval "(lua-eval-ast \"local mt = {} local t = setmetatable({}, mt) if getmetatable(t) == mt then return 1 else return 0 end\")")
;; type()
(epoch 810)
(eval "(lua-eval-ast \"return type(1)\")")
(epoch 811)
(eval "(lua-eval-ast \"return type(\\\"s\\\")\")")
(epoch 812)
(eval "(lua-eval-ast \"return type({})\")")
(epoch 813)
(eval "(lua-eval-ast \"return type(nil)\")")
(epoch 814)
(eval "(lua-eval-ast \"return type(true)\")")
(epoch 815)
(eval "(lua-eval-ast \"return type(function() end)\")")
;; __index
(epoch 820)
(eval "(lua-eval-ast \"local base = {x = 10} local t = setmetatable({}, {__index = base}) return t.x\")")
(epoch 821)
(eval "(lua-eval-ast \"local base = {x = 10} local t = setmetatable({y = 20}, {__index = base}) return t.x + t.y\")")
(epoch 822)
(eval "(lua-eval-ast \"local t = setmetatable({}, {__index = function(tbl, k) return k .. \\\"!\\\" end}) return t.hi\")")
;; __newindex
(epoch 830)
(eval "(lua-eval-ast \"local log = {} local t = setmetatable({}, {__newindex = function(tbl, k, v) log[1] = k end}) t.foo = 1 return log[1]\")")
;; Arithmetic metamethods
(epoch 840)
(eval "(lua-eval-ast \"local mt = {__add = function(a, b) return 99 end} local x = setmetatable({}, mt) return x + 1\")")
(epoch 841)
(eval "(lua-eval-ast \"local mt = {__add = function(a, b) return a.v + b.v end} local x = setmetatable({v = 3}, mt) local y = setmetatable({v = 4}, mt) return x + y\")")
(epoch 842)
(eval "(lua-eval-ast \"local mt = {__sub = function(a, b) return 7 end, __mul = function(a, b) return 11 end} local t = setmetatable({}, mt) return t - t + (t * t)\")")
(epoch 843)
(eval "(lua-eval-ast \"local mt = {__unm = function(a) return 42 end} local t = setmetatable({}, mt) return -t\")")
(epoch 844)
(eval "(lua-eval-ast \"local mt = {__concat = function(a, b) return \\\"cat\\\" end} local t = setmetatable({}, mt) return t .. \\\"x\\\"\")")
;; Comparison metamethods
(epoch 850)
(eval "(lua-eval-ast \"local mt = {__eq = function(a, b) return true end} local x = setmetatable({}, mt) local y = setmetatable({}, mt) if x == y then return 1 else return 0 end\")")
(epoch 851)
(eval "(lua-eval-ast \"local mt = {__lt = function(a, b) return true end} local x = setmetatable({}, mt) local y = setmetatable({}, mt) if x < y then return 1 else return 0 end\")")
(epoch 852)
(eval "(lua-eval-ast \"local mt = {__le = function(a, b) return true end} local x = setmetatable({}, mt) local y = setmetatable({}, mt) if x <= y then return 1 else return 0 end\")")
;; __call
(epoch 860)
(eval "(lua-eval-ast \"local mt = {__call = function(t, x) return x + 100 end} local obj = setmetatable({}, mt) return obj(5)\")")
;; __len
(epoch 870)
(eval "(lua-eval-ast \"local mt = {__len = function(t) return 99 end} local t = setmetatable({}, mt) return #t\")")
;; Classic OO pattern
(epoch 880)
(eval "(lua-eval-ast \"local Animal = {} Animal.__index = Animal function Animal:new(name) local o = setmetatable({name = name}, self) return o end function Animal:getName() return self.name end local a = Animal:new(\\\"Rex\\\") return a:getName()\")")
EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
@@ -899,6 +964,31 @@ check 760 "t[k]=v reads via t.foo" '88'
check 761 "t[concat]=v reads via t.xy" '7'
check 770 "reference semantics t=s" '42'
# ── Phase 4: metatables ───────────────────────────────────────
check 800 "setmetatable returns t" '1'
check 801 "getmetatable roundtrip" '1'
check 810 "type(1)" '"number"'
check 811 "type(string)" '"string"'
check 812 "type({})" '"table"'
check 813 "type(nil)" '"nil"'
check 814 "type(true)" '"boolean"'
check 815 "type(function)" '"function"'
check 820 "__index table lookup" '10'
check 821 "__index chain + self" '30'
check 822 "__index as function" '"hi!"'
check 830 "__newindex fires" '"foo"'
check 840 "__add table+number" '99'
check 841 "__add two tables" '7'
check 842 "__sub + __mul chain" '18'
check 843 "__unm" '42'
check 844 "__concat" '"cat"'
check 850 "__eq" '1'
check 851 "__lt" '1'
check 852 "__le" '1'
check 860 "__call" '105'
check 870 "__len" '99'
check 880 "OO pattern self:m()" '"Rex"'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL Lua-on-SX tests passed"

View File

@@ -104,7 +104,9 @@
(node)
(let
((fn-ast (nth node 1)) (args (nth node 2)))
(cons (lua-tx fn-ast) (map lua-tx args)))))
(cons
(make-symbol "lua-call")
(cons (lua-tx fn-ast) (map lua-tx args))))))
(define
lua-tx-method-call
@@ -115,8 +117,10 @@
(name (nth node 2))
(args (nth node 3)))
(cons
(list (make-symbol "lua-get") obj name)
(cons obj (map lua-tx args))))))
(make-symbol "lua-call")
(cons
(list (make-symbol "lua-get") obj name)
(cons obj (map lua-tx args)))))))
(define
lua-tx-field

View File

@@ -60,7 +60,7 @@ Each item: implement → tests → tick box → update progress log.
- [x] `scoreboard.json` + `scoreboard.md` baseline
### Phase 4 — metatables + error handling (next run)
- [ ] Metatable dispatch: `__index`, `__newindex`, `__add`/`__sub`/…, `__eq`, `__lt`, `__call`, `__tostring`, `__len`
- [x] Metatable dispatch: `__index`, `__newindex`, `__add`/`__sub`/…, `__eq`, `__lt`, `__call`, `__tostring`, `__len`
- [ ] `pcall`/`xpcall`/`error` via handler-bind
- [ ] Generic `for … in …`
@@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log.
_Newest first. Agent appends on every commit._
- 2026-04-24: lua: phase 4 — metatable dispatch (`__index`/`__newindex`/arith/compare/`__call`/`__len`), `setmetatable`/`getmetatable`/`type` globals, OO `self:method` pattern. Transpile routes all calls through `lua-call` (stashed `sx-apply-ref` to dodge user-shadowing of SX `apply`). Skipped `__tostring` (needs `tostring()` builtin). 247 total tests.
- 2026-04-24: lua: PUC-Rio scoreboard baseline — 0/16 runnable pass (0.0%). Top modes: 14× parse error, 1× `print` undef, 1× vararg transpile. Phase 3 complete.
- 2026-04-24: lua: conformance runner — `conformance.sh` shim + `conformance.py` (long-lived sx_server, epoch protocol, classify_error, writes scoreboard.{json,md}). 24 files classified in full run: 8 skip / 16 fail / 0 timeout.
- 2026-04-24: lua: vendored PUC-Rio 5.1 test suite (lua5.1-tests.tar.gz from lua.org) to `lib/lua/lua-tests/` — 22 .lua files, 6304 lines; README kept for context.