lua: generic for-in; ipairs/pairs/next; arity-tolerant fns +9 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled

This commit is contained in:
2026-04-24 18:23:50 +00:00
parent e224fb2db0
commit 0934c4bd28
5 changed files with 313 additions and 42 deletions

View File

@@ -534,50 +534,73 @@
(let
((body (parse-block)))
(begin (consume! "keyword" "end") (list (quote lua-do) body))))))
(define
parse-for
(fn
()
(define parse-for-num-rest
(fn (name)
(begin
(consume! "op" "=")
(let ((start (parse-expr)))
(begin
(consume! "op" ",")
(let ((stop (parse-expr)) (step nil))
(begin
(when (at-op? ",")
(begin
(advance-tok!)
(set! step (parse-expr))))
(consume! "keyword" "do")
(let ((body (parse-block)))
(begin
(consume! "keyword" "end")
(list (quote lua-for-num) name start stop step body))))))))))
(define parse-for-in-names
(fn (names)
(cond
((at-op? ",")
(begin
(advance-tok!)
(let ((nt (peek-tok)))
(begin
(when (not (= (lua-tok-type nt) "ident"))
(error "lua-parse: expected name after , in for"))
(let ((nm (lua-tok-value nt)))
(begin
(advance-tok!)
(parse-for-in-names (append names (list nm)))))))))
(else names))))
(define parse-for-in-exps
(fn (exps)
(cond
((at-op? ",")
(begin
(advance-tok!)
(parse-for-in-exps (append exps (list (parse-expr))))))
(else exps))))
(define parse-for-in-rest
(fn (names)
(begin
(consume! "keyword" "in")
(let ((exps (parse-for-in-exps (list (parse-expr)))))
(begin
(consume! "keyword" "do")
(let ((body (parse-block)))
(begin
(consume! "keyword" "end")
(list (quote lua-for-in) names exps body))))))))
(define parse-for
(fn ()
(begin
(consume! "keyword" "for")
(let
((t (peek-tok)))
(let ((t (peek-tok)))
(begin
(when
(not (= (lua-tok-type t) "ident"))
(when (not (= (lua-tok-type t) "ident"))
(error "lua-parse: expected name in for"))
(let
((name (lua-tok-value t)))
(let ((name (lua-tok-value t)))
(begin
(advance-tok!)
(when
(not (at-op? "="))
(error "lua-parse: only numeric for supported"))
(consume! "op" "=")
(let
((start (parse-expr)))
(begin
(consume! "op" ",")
(let
((stop (parse-expr)) (step nil))
(begin
(when
(at-op? ",")
(begin
(advance-tok!)
(set! step (parse-expr))))
(consume! "keyword" "do")
(let
((body (parse-block)))
(begin
(consume! "keyword" "end")
(list
(quote lua-for-num)
name
start
stop
step
body))))))))))))))
(cond
((at-op? "=") (parse-for-num-rest name))
(else
(parse-for-in-rest (parse-for-in-names (list name))))))))))))
(define
parse-funcname
(fn

View File

@@ -448,3 +448,84 @@
(cond
((lua-multi? r) (cons (quote lua-multi) (cons true (rest r))))
(else (list (quote lua-multi) true r)))))))
(define
lua-ipairs-iter
(fn
(t i)
(let
((nk (+ i 1)))
(let
((v (get t (str nk))))
(if (= v nil) nil (list (quote lua-multi) nk v))))))
(define
lua-ipairs
(fn
(t)
(list (quote lua-multi) lua-ipairs-iter t 0)))
(define ipairs lua-ipairs)
(define
lua-next-skip-meta
(fn
(ks)
(cond
((= (len ks) 0) (list))
((= (first ks) "__meta") (lua-next-skip-meta (rest ks)))
(else ks))))
(define
lua-next-after
(fn
(ks prev-key)
(cond
((= (len ks) 0) (list))
((= (first ks) (str prev-key)) (lua-next-skip-meta (rest ks)))
(else (lua-next-after (rest ks) prev-key)))))
(define
lua-key-to-value
(fn
(k)
(cond
((= k "true") true)
((= k "false") false)
(else
(let
((n (lua-to-number k))) (if (= n nil) k n))))))
(define
lua-next
(fn
(&rest args)
(let
((t (first args))
(prev (if (> (len (rest args)) 0) (first (rest args)) nil)))
(let
((all-keys (keys t)))
(let
((ks (if (= prev nil) (lua-next-skip-meta all-keys) (lua-next-after all-keys prev))))
(cond
((= (len ks) 0) nil)
(else
(let
((k (first ks)))
(list (quote lua-multi) (lua-key-to-value k) (get t k))))))))))
(define next lua-next)
(define
lua-pairs
(fn
(t)
(list (quote lua-multi) lua-next t nil)))
(define pairs lua-pairs)
(define
lua-arg
(fn
(args i)
(if (< i (len args)) (nth args i) nil)))

View File

@@ -681,6 +681,35 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 911)
(eval "(lua-eval-ast \"local function f() return 99 end local function handler(e) return e end local ok, v = xpcall(f, handler) return v\")")
;; ── Phase 4: generic `for … in …` ──────────────────────────────
;; ipairs over array
(epoch 950)
(eval "(lua-eval-ast \"local t = {10, 20, 30} local sum = 0 for i, v in ipairs(t) do sum = sum + v end return sum\")")
(epoch 951)
(eval "(lua-eval-ast \"local t = {10, 20, 30} local last = 0 for i, v in ipairs(t) do last = i end return last\")")
(epoch 952)
(eval "(lua-eval-ast \"local t = {} local count = 0 for i, v in ipairs(t) do count = count + 1 end return count\")")
(epoch 953)
(eval "(lua-eval-ast \"local t = {1, 2, nil, 4} local c = 0 for i, v in ipairs(t) do c = c + 1 end return c\")")
;; pairs over hash
(epoch 960)
(eval "(lua-eval-ast \"local t = {a = 1, b = 2, c = 3} local sum = 0 for k, v in pairs(t) do sum = sum + v end return sum\")")
(epoch 961)
(eval "(lua-eval-ast \"local t = {x = 1, y = 2, z = 3} local n = 0 for k, v in pairs(t) do n = n + 1 end return n\")")
;; custom stateful iterator (if-else-return, works around early-return limit)
(epoch 970)
(eval "(lua-eval-ast \"local function range(n) local i = 0 return function() i = i + 1 if i > n then return nil else return i end end end local c = 0 for x in range(5) do c = c + x end return c\")")
;; 3-value iterator form (f, s, var)
(epoch 971)
(eval "(lua-eval-ast \"local function step(max, i) if i >= max then return nil else return i + 1, (i + 1) * (i + 1) end end local sum = 0 for i, v in step, 4, 0 do sum = sum + v end return sum\")")
;; pairs ignores __meta key
(epoch 980)
(eval "(lua-eval-ast \"local t = setmetatable({x = 1, y = 2}, {}) local n = 0 for k in pairs(t) do n = n + 1 end return n\")")
EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
@@ -1022,6 +1051,17 @@ check 906 "nested pcall" '"outer:inner"'
check 910 "xpcall invokes handler" '"H:raw"'
check 911 "xpcall ok path" '99'
# ── Phase 4: generic `for … in …` ─────────────────────────────
check 950 "ipairs sum" '60'
check 951 "ipairs last index" '3'
check 952 "ipairs empty → 0" '0'
check 953 "ipairs stops at nil" '2'
check 960 "pairs hash sum" '6'
check 961 "pairs hash count" '3'
check 970 "stateful closure iter" '15'
check 971 "3-value iterator form" '30'
check 980 "pairs skips __meta" '2'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL Lua-on-SX tests passed"

View File

@@ -35,6 +35,7 @@
((= tag (quote lua-while)) (lua-tx-while node))
((= tag (quote lua-repeat)) (lua-tx-repeat node))
((= tag (quote lua-for-num)) (lua-tx-for-num node))
((= tag (quote lua-for-in)) (lua-tx-for-in node))
((= tag (quote lua-do)) (lua-tx-do node))
((= tag (quote lua-break)) (quote lua-break-marker))
((= tag (quote lua-return)) (lua-tx-return node))
@@ -160,6 +161,19 @@
(lua-tx (nth f 2))))
(else (error "lua-transpile: unknown table field")))))
(define
lua-tx-function-bindings
(fn
(params i)
(if
(>= i (len params))
(list)
(cons
(list
(make-symbol (nth params i))
(list (make-symbol "lua-arg") (make-symbol "__args") i))
(lua-tx-function-bindings params (+ i 1))))))
(define
lua-tx-function
(fn
@@ -168,9 +182,20 @@
((params (nth node 1))
(is-vararg (nth node 2))
(body (nth node 3)))
(let
((sym-params (map make-symbol params)))
(list (make-symbol "fn") sym-params (lua-tx body))))))
(cond
((= (len params) 0)
(list
(make-symbol "fn")
(list (make-symbol "&rest") (make-symbol "__args"))
(lua-tx body)))
(else
(list
(make-symbol "fn")
(list (make-symbol "&rest") (make-symbol "__args"))
(list
(make-symbol "let")
(lua-tx-function-bindings params 0)
(lua-tx body))))))))
(define
lua-tx-block
@@ -536,3 +561,100 @@
(nth lhss i)
(list (make-symbol "lua-nth-ret") tmp i))
(lua-tx-multi-assign-pairs lhss tmp (+ i 1))))))
(define
lua-tx-for-in-decls
(fn
(names i)
(if
(>= i (len names))
(list)
(cons
(list (make-symbol "define") (make-symbol (nth names i)) nil)
(lua-tx-for-in-decls names (+ i 1))))))
(define
lua-tx-for-in-sets
(fn
(names rets-sym i)
(if
(>= i (len names))
(list)
(cons
(list
(make-symbol "set!")
(make-symbol (nth names i))
(list (make-symbol "lua-nth-ret") rets-sym i))
(lua-tx-for-in-sets names rets-sym (+ i 1))))))
(define
lua-tx-for-in-step-body
(fn
(names body v-sym loop-sym first-name)
(list
(make-symbol "when")
(list (make-symbol "not") (list (make-symbol "=") first-name nil))
(list
(make-symbol "begin")
(list (make-symbol "set!") v-sym first-name)
body
(list loop-sym)))))
(define
lua-tx-for-in-loop-body
(fn
(names body f-sym s-sym v-sym rets-sym loop-sym first-name)
(list
(make-symbol "let")
(list
(list
rets-sym
(list
(make-symbol "lua-pack-return")
(list
(make-symbol "list")
(list (make-symbol "lua-call") f-sym s-sym v-sym)))))
(cons
(make-symbol "begin")
(append
(lua-tx-for-in-sets names rets-sym 0)
(list (lua-tx-for-in-step-body names body v-sym loop-sym first-name)))))))
(define
lua-tx-for-in
(fn
(node)
(let
((names (nth node 1))
(exps (nth node 2))
(body (lua-tx (nth node 3))))
(let
((pack-sym (make-symbol "__for_pack"))
(f-sym (make-symbol "__for_f"))
(s-sym (make-symbol "__for_s"))
(v-sym (make-symbol "__for_var"))
(rets-sym (make-symbol "__for_rets"))
(loop-sym (make-symbol "__for_loop"))
(first-name (make-symbol (first names))))
(list
(make-symbol "let")
(list (list pack-sym (lua-tx-multi-rhs exps)))
(list
(make-symbol "let")
(list
(list f-sym (list (make-symbol "lua-nth-ret") pack-sym 0))
(list s-sym (list (make-symbol "lua-nth-ret") pack-sym 1))
(list v-sym (list (make-symbol "lua-nth-ret") pack-sym 2)))
(cons
(make-symbol "begin")
(append
(lua-tx-for-in-decls names 0)
(list
(list
(make-symbol "define")
loop-sym
(list
(make-symbol "fn")
(list)
(lua-tx-for-in-loop-body names body f-sym s-sym v-sym rets-sym loop-sym first-name)))
(list loop-sym))))))))))

View File

@@ -62,7 +62,7 @@ Each item: implement → tests → tick box → update progress log.
### Phase 4 — metatables + error handling (next run)
- [x] Metatable dispatch: `__index`, `__newindex`, `__add`/`__sub`/…, `__eq`, `__lt`, `__call`, `__tostring`, `__len`
- [x] `pcall`/`xpcall`/`error` via handler-bind
- [ ] Generic `for … in …`
- [x] Generic `for … in …`
### Phase 5 — coroutines (the showcase)
- [ ] `coroutine.create`/`.resume`/`.yield`/`.status`/`.wrap` via `perform`/`cek-resume`
@@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log.
_Newest first. Agent appends on every commit._
- 2026-04-24: lua: generic `for … in …` — parser split (`=` → num, else `in`), new `lua-for-in` node, transpile to `let`-bound `f,s,var` + recursive `__for_loop`. Added `ipairs`/`pairs`/`next`/`lua-arg` globals. Lua fns now arity-tolerant (`&rest __args` + indexed bind) — needed because generic for always calls iter with 2 args. Noted early-return-in-nested-block as pre-existing limitation. 265 tests.
- 2026-04-24: lua: `pcall`/`xpcall`/`error` via SX `guard` + `raise`. Added `lua-apply` (arity-dispatch 0-8, apply fallback) because SX `apply` re-wraps raises as "Unhandled exception". Table payloads preserved (`error({code = 42})`). 256 total tests.
- 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.
@@ -100,3 +101,7 @@ _Newest first. Agent appends on every commit._
_Shared-file issues that need someone else to fix. Minimal repro only._
- _(none yet)_
## Known limitations (own code, not shared)
- **Early `return` inside nested block** — `if cond then return nil end ...rest` doesn't exit the enclosing function; `rest` runs anyway. Use `if cond then return X else return Y end` instead. Likely needs guard+raise sentinel for proper fix.