From 2b448d99bc02d354998ab8b48dda510219f921ff Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 24 Apr 2026 17:10:52 +0000 Subject: [PATCH] lua: multi-return + unpack at call sites (+10 tests) --- lib/lua/runtime.sx | 46 ++++++++++++++++++ lib/lua/test.sh | 34 +++++++++++++ lib/lua/transpile.sx | 112 ++++++++++++++++++++++++++++++++++++++++--- plans/lua-on-sx.md | 3 +- 4 files changed, 187 insertions(+), 8 deletions(-) diff --git a/lib/lua/runtime.sx b/lib/lua/runtime.sx index 71b37373..d21b92bb 100644 --- a/lib/lua/runtime.sx +++ b/lib/lua/runtime.sx @@ -169,3 +169,49 @@ (if (= t nil) nil (let ((v (get t (str k)))) (if (= v nil) nil v))))) (define lua-set! (fn (t k v) (assoc t (str k) v))) + +(define + lua-multi? + (fn + (v) + (and + (= (type-of v) "list") + (> (len v) 0) + (= (first v) (quote lua-multi))))) + +(define + lua-first + (fn + (v) + (cond + ((lua-multi? v) (if (> (len v) 1) (nth v 1) nil)) + (else v)))) + +(define + lua-nth-ret + (fn + (v i) + (cond + ((lua-multi? v) + (let ((idx (+ i 1))) (if (< idx (len v)) (nth v idx) nil))) + (else (if (= i 0) v nil))))) + +(define + lua-pack-build + (fn + (vals i) + (cond + ((>= i (len vals)) (list)) + ((= i (- (len vals) 1)) + (let + ((last (nth vals i))) + (if (lua-multi? last) (rest last) (list last)))) + (else (cons (nth vals i) (lua-pack-build vals (+ i 1))))))) + +(define + lua-pack-return + (fn + (vals) + (cond + ((= (len vals) 0) (list (quote lua-multi))) + (else (cons (quote lua-multi) (lua-pack-build vals 0)))))) diff --git a/lib/lua/test.sh b/lib/lua/test.sh index f644888a..f89d956a 100755 --- a/lib/lua/test.sh +++ b/lib/lua/test.sh @@ -460,6 +460,28 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 561) (eval "(lua-eval-ast \"local function sum_to(n) local s = 0 for i = 1, n do s = s + i end return s end return sum_to(10)\")") +;; ── Phase 3: multi-return + unpack ──────────────────────────── +(epoch 570) +(eval "(lua-eval-ast \"local a, b = (function() return 1, 2 end)() return a + b\")") +(epoch 571) +(eval "(lua-eval-ast \"local function two() return 10, 20 end local a, b = two() return b - a\")") +(epoch 572) +(eval "(lua-eval-ast \"function swap(a, b) return b, a end local x, y = swap(1, 2) return x * 10 + y\")") +(epoch 573) +(eval "(lua-eval-ast \"local function three() return 1, 2, 3 end local a, b = three() return a * 10 + b\")") +(epoch 574) +(eval "(lua-eval-ast \"local function two() return 1, 2 end local a, b, c = two() if c == nil then return a + b else return 0 end\")") +(epoch 575) +(eval "(lua-eval-ast \"local function two() return 5, 7 end local function outer() return two() end local a, b = outer() return a + b\")") +(epoch 576) +(eval "(lua-eval-ast \"local function two() return 9, 9 end local a, b = two(), 5 return a * 10 + b\")") +(epoch 577) +(eval "(lua-eval-ast \"local function pair() return 4, 5 end local x = pair() return x + 1\")") +(epoch 578) +(eval "(lua-eval-ast \"local function none() return end local a, b = none() if a == nil and b == nil then return 99 else return 0 end\")") +(epoch 579) +(eval "(lua-eval-ast \"local a = 0 local b = 0 local function m() return 7, 11 end a, b = m() return a + b\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -710,6 +732,18 @@ check 551 "twice(+1, 5)" '7' check 560 "max with if" '7' check 561 "sum_to(10) with for" '55' +# ── Phase 3: multi-return + unpack ──────────────────────────── +check 570 "anon-fn returns 2, unpack" '3' +check 571 "local fn returns 2, unpack" '10' +check 572 "swap via multi-return" '21' +check 573 "extra returns discarded" '12' +check 574 "missing returns nil-padded" '3' +check 575 "tail-return passthrough" '12' +check 576 "non-last call truncated to 1st" '95' +check 577 "single-assign truncates to 1st" '5' +check 578 "empty return → all nil" '99' +check 579 "multi-assign (non-local)" '18' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "ok $PASS/$TOTAL Lua-on-SX tests passed" diff --git a/lib/lua/transpile.sx b/lib/lua/transpile.sx index b348bcf0..ddb671b4 100644 --- a/lib/lua/transpile.sx +++ b/lib/lua/transpile.sx @@ -190,9 +190,13 @@ (list (make-symbol "define") (make-symbol (first names)) - (if (> (len exps) 0) (lua-tx (first exps)) nil))) - (else - (cons (make-symbol "begin") (lua-tx-local-pairs names exps 0))))))) + (if + (> (len exps) 0) + (list (make-symbol "lua-first") (lua-tx (first exps))) + nil))) + ((= (len exps) 0) + (cons (make-symbol "begin") (lua-tx-local-pairs names exps 0))) + (else (lua-tx-multi-local names exps)))))) (define lua-tx-local-pairs @@ -216,9 +220,12 @@ ((lhss (nth node 1)) (rhss (nth node 2))) (cond ((= (len lhss) 1) - (lua-tx-single-assign (first lhss) (lua-tx (first rhss)))) - (else - (cons (make-symbol "begin") (lua-tx-assign-pairs lhss rhss 0))))))) + (lua-tx-single-assign + (first lhss) + (list (make-symbol "lua-first") (lua-tx (first rhss))))) + ((= (len rhss) 0) + (cons (make-symbol "begin") (lua-tx-assign-pairs lhss rhss 0))) + (else (lua-tx-multi-assign lhss rhss)))))) (define lua-tx-assign-pairs @@ -399,7 +406,10 @@ (cond ((= (len exps) 0) nil) ((= (len exps) 1) (lua-tx (first exps))) - (else (cons (make-symbol "list") (map lua-tx exps))))))) + (else + (list + (make-symbol "lua-pack-return") + (cons (make-symbol "list") (lua-tx-multi-args exps 0)))))))) (define lua-tx-local-function @@ -434,3 +444,91 @@ (define lua-eval-ast (fn (src) (let ((sx (lua-transpile src))) (eval-expr sx)))) + +(define + lua-tx-multi-args + (fn + (exps i) + (cond + ((>= i (len exps)) (list)) + ((= i (- (len exps) 1)) + (cons (lua-tx (nth exps i)) (lua-tx-multi-args exps (+ i 1)))) + (else + (cons + (list (make-symbol "lua-first") (lua-tx (nth exps i))) + (lua-tx-multi-args exps (+ i 1))))))) + +(define + lua-tx-multi-rhs + (fn + (exps) + (list + (make-symbol "lua-pack-return") + (cons (make-symbol "list") (lua-tx-multi-args exps 0))))) + +(define + lua-tx-multi-local + (fn + (names exps) + (let + ((tmp (make-symbol "__rets"))) + (cons + (make-symbol "begin") + (append + (lua-tx-multi-local-decls names 0) + (list + (list + (make-symbol "let") + (list (list tmp (lua-tx-multi-rhs exps))) + (cons + (make-symbol "begin") + (lua-tx-multi-local-sets names tmp 0))))))))) + +(define + lua-tx-multi-local-decls + (fn + (names i) + (if + (>= i (len names)) + (list) + (cons + (list (make-symbol "define") (make-symbol (nth names i)) nil) + (lua-tx-multi-local-decls names (+ i 1)))))) + +(define + lua-tx-multi-local-sets + (fn + (names tmp i) + (if + (>= i (len names)) + (list) + (cons + (list + (make-symbol "set!") + (make-symbol (nth names i)) + (list (make-symbol "lua-nth-ret") tmp i)) + (lua-tx-multi-local-sets names tmp (+ i 1)))))) + +(define + lua-tx-multi-assign + (fn + (lhss rhss) + (let + ((tmp (make-symbol "__rets"))) + (list + (make-symbol "let") + (list (list tmp (lua-tx-multi-rhs rhss))) + (cons (make-symbol "begin") (lua-tx-multi-assign-pairs lhss tmp 0)))))) + +(define + lua-tx-multi-assign-pairs + (fn + (lhss tmp i) + (if + (>= i (len lhss)) + (list) + (cons + (lua-tx-single-assign + (nth lhss i) + (list (make-symbol "lua-nth-ret") tmp i)) + (lua-tx-multi-assign-pairs lhss tmp (+ i 1)))))) diff --git a/plans/lua-on-sx.md b/plans/lua-on-sx.md index 8f1d8c17..4456028b 100644 --- a/plans/lua-on-sx.md +++ b/plans/lua-on-sx.md @@ -52,7 +52,7 @@ Each item: implement → tests → tick box → update progress log. ### Phase 3 — tables + functions + first PUC-Rio slice - [x] `function` (anon, local, top-level), closures -- [ ] Multi-return: return as list, unpack at call sites +- [x] Multi-return: return as list, unpack at call sites - [ ] Table constructors (array + hash + computed keys) - [ ] Raw table access `t.k` / `t[k]` (no metatables yet) - [ ] Vendor PUC-Rio 5.1.5 suite to `lib/lua/lua-tests/` (just `.lua` files) @@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log. _Newest first. Agent appends on every commit._ +- 2026-04-24: lua: multi-return — `lua-multi` tagged value, `lua-first`/`lua-nth-ret`/`lua-pack-return` runtime, tail-position spread in return/local/assign. 185 total tests. - 2026-04-24: lua: phase 3 — functions (anon/local/top-level) + closures verified (lexical capture, mutation-through-closure, recursion, HOFs). 175 total tests. - 2026-04-24: lua: phase 2 transpile — arithmetic, comparison, short-circuit logical, `..` concat, if/while/repeat/for-num/local/assign. 157 total tests green. - 2026-04-24: lua: parser (exprs with precedence, all phase-1 statements, funcbody, table ctors, method/chained calls) — 112 total tokenizer+parser tests