lua: proper early-return via guard+raise sentinel; fixes if-then-return-end-rest +3 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled

This commit is contained in:
2026-04-24 21:46:23 +00:00
parent b1bed8e0e5
commit 68b0a279f8
6 changed files with 104 additions and 60 deletions

View File

@@ -1503,9 +1503,15 @@
(guard (guard
(e (true (list (quote lua-multi) nil (str e)))) (e (true (list (quote lua-multi) nil (str e))))
(let ((compiled (lua-transpile src))) (let ((compiled (lua-transpile src)))
(let ((wrapped (list (make-symbol "fn") (list (make-symbol "&rest") (make-symbol "__args")) (let ((guarded (list
(list (make-symbol "let") (list) compiled)))) (make-symbol "guard")
(eval-expr wrapped)))))) (list (make-symbol "e")
(list
(list (make-symbol "lua-return-sentinel?") (make-symbol "e"))
(list (make-symbol "lua-return-value") (make-symbol "e"))))
(list (make-symbol "let") (list) compiled))))
(let ((wrapped (list (make-symbol "fn") (list (make-symbol "&rest") (make-symbol "__args")) guarded)))
(eval-expr wrapped)))))))
(define loadstring lua-loadstring) (define loadstring lua-loadstring)
(define load lua-loadstring) (define load lua-loadstring)
@@ -1630,3 +1636,13 @@
(va-build skip) (va-build skip)
(dict-set! t "n" n) (dict-set! t "n" n)
t)))) t))))
;; Return-sentinel: wrap function bodies so mid-block `return` can escape.
(define
lua-return-sentinel?
(fn (e)
(and (= (type-of e) "list") (> (len e) 0) (= (first e) (quote lua-ret)))))
(define
lua-return-value
(fn (e) (if (> (len e) 1) (nth e 1) nil)))

View File

@@ -11,7 +11,7 @@
"top_failure_modes": [ "top_failure_modes": [
[ [
"other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
9 10
], ],
[ [
"timeout", "timeout",
@@ -22,11 +22,7 @@
2 2
], ],
[ [
"other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to compare incompat", "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: concat on list and string\\\\",
1
],
[
"other: Unhandled exception: \\\"Not callable: nil (kont=10 frames)\\",
1 1
] ]
], ],
@@ -47,19 +43,19 @@
"name": "attrib.lua", "name": "attrib.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 6000 "ms": 6225
}, },
{ {
"name": "big.lua", "name": "big.lua",
"status": "timeout", "status": "timeout",
"reason": "per-test timeout", "reason": "per-test timeout",
"ms": 8006 "ms": 8008
}, },
{ {
"name": "calls.lua", "name": "calls.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 4946 "ms": 5175
}, },
{ {
"name": "checktable.lua", "name": "checktable.lua",
@@ -71,7 +67,7 @@
"name": "closure.lua", "name": "closure.lua",
"status": "timeout", "status": "timeout",
"reason": "per-test timeout", "reason": "per-test timeout",
"ms": 8003 "ms": 8007
}, },
{ {
"name": "code.lua", "name": "code.lua",
@@ -82,8 +78,8 @@
{ {
"name": "constructs.lua", "name": "constructs.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to compare incompat", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: concat on list and string\\\\",
"ms": 4533 "ms": 4874
}, },
{ {
"name": "db.lua", "name": "db.lua",
@@ -94,14 +90,14 @@
{ {
"name": "errors.lua", "name": "errors.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Not callable: nil (kont=10 frames)\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 3217 "ms": 3289
}, },
{ {
"name": "events.lua", "name": "events.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 7215 "ms": 7546
}, },
{ {
"name": "files.lua", "name": "files.lua",
@@ -119,13 +115,13 @@
"name": "literals.lua", "name": "literals.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 1901 "ms": 1888
}, },
{ {
"name": "locals.lua", "name": "locals.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio",
"ms": 1693 "ms": 1721
}, },
{ {
"name": "main.lua", "name": "main.lua",
@@ -137,43 +133,43 @@
"name": "math.lua", "name": "math.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 4095 "ms": 4231
}, },
{ {
"name": "nextvar.lua", "name": "nextvar.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 7177 "ms": 7290
}, },
{ {
"name": "pm.lua", "name": "pm.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 6048 "ms": 6155
}, },
{ {
"name": "sort.lua", "name": "sort.lua",
"status": "timeout", "status": "timeout",
"reason": "per-test timeout", "reason": "per-test timeout",
"ms": 8007 "ms": 8006
}, },
{ {
"name": "strings.lua", "name": "strings.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 3990 "ms": 4215
}, },
{ {
"name": "vararg.lua", "name": "vararg.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\",
"ms": 2249 "ms": 2288
}, },
{ {
"name": "verybig.lua", "name": "verybig.lua",
"status": "fail", "status": "fail",
"reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio",
"ms": 572 "ms": 622
} }
] ]
} }

View File

@@ -5,11 +5,10 @@ fail=13 timeout=3 skip=8 total=24
## Top failure modes ## Top failure modes
- **9x** other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ - **10x** other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\
- **3x** timeout - **3x** timeout
- **2x** other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio - **2x** other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio
- **1x** other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to compare incompat - **1x** other: Unhandled exception: \"Unhandled exception: \\\"lua: concat on list and string\\
- **1x** other: Unhandled exception: \"Not callable: nil (kont=10 frames)\
## Per-test results ## Per-test results
@@ -17,25 +16,25 @@ fail=13 timeout=3 skip=8 total=24
|---|---|---|---:| |---|---|---|---:|
| all.lua | skip | driver uses dofile to chain other tests | 0 | | all.lua | skip | driver uses dofile to chain other tests | 0 |
| api.lua | skip | requires testC (C debug library) | 0 | | api.lua | skip | requires testC (C debug library) | 0 |
| attrib.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6000 | | attrib.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6225 |
| big.lua | timeout | per-test timeout | 8006 | | big.lua | timeout | per-test timeout | 8008 |
| calls.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4946 | | calls.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 5175 |
| checktable.lua | skip | internal debug helpers | 0 | | checktable.lua | skip | internal debug helpers | 0 |
| closure.lua | timeout | per-test timeout | 8003 | | closure.lua | timeout | per-test timeout | 8007 |
| code.lua | skip | bytecode inspection via debug library | 0 | | code.lua | skip | bytecode inspection via debug library | 0 |
| constructs.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to compare incompat | 4533 | | constructs.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: concat on list and string\\ | 4874 |
| db.lua | skip | debug library | 0 | | db.lua | skip | debug library | 0 |
| errors.lua | fail | other: Unhandled exception: \"Not callable: nil (kont=10 frames)\ | 3217 | | errors.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3289 |
| events.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7215 | | events.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7546 |
| files.lua | skip | io library | 0 | | files.lua | skip | io library | 0 |
| gc.lua | skip | collectgarbage / finalisers | 0 | | gc.lua | skip | collectgarbage / finalisers | 0 |
| literals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 1901 | | literals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 1888 |
| locals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 1693 | | locals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 1721 |
| main.lua | skip | standalone interpreter driver | 0 | | main.lua | skip | standalone interpreter driver | 0 |
| math.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4095 | | math.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4231 |
| nextvar.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7177 | | nextvar.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7290 |
| pm.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6048 | | pm.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6155 |
| sort.lua | timeout | per-test timeout | 8007 | | sort.lua | timeout | per-test timeout | 8006 |
| strings.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3990 | | strings.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4215 |
| vararg.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2249 | | vararg.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2288 |
| verybig.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 572 | | verybig.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 622 |

View File

@@ -936,6 +936,14 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 1842) (epoch 1842)
(eval "(lua-eval-ast \"if -2^2 == -4 then return 1 else return 0 end\")") (eval "(lua-eval-ast \"if -2^2 == -4 then return 1 else return 0 end\")")
;; ── Early-return inside nested block (guard+raise sentinel) ──
(epoch 1850)
(eval "(lua-eval-ast \"local function f(n) if n < 0 then return -1 end return n * 2 end return f(-5)\")")
(epoch 1851)
(eval "(lua-eval-ast \"local function f(n) if n < 0 then return -1 end return n * 2 end return f(7)\")")
(epoch 1852)
(eval "(lua-eval-ast \"function f(i) if type(i) ~= \\\"number\\\" then return i, \\\"jojo\\\" end if i > 0 then return i, f(i-1) end end local a, b = f(3) return a\")")
EPOCHS EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
@@ -1414,6 +1422,11 @@ check 1840 "-2^2 = -4" '-4'
check 1841 "2^3^2 = 512 (right-assoc)" '512' check 1841 "2^3^2 = 512 (right-assoc)" '512'
check 1842 "-2^2 == -4 true" '1' check 1842 "-2^2 == -4 true" '1'
# ── Early-return inside nested block ─────────────────────────
check 1850 "early return negative path" '-1'
check 1851 "non-early return path" '14'
check 1852 "nested early-return recursion" '3'
TOTAL=$((PASS + FAIL)) TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL Lua-on-SX tests passed" echo "ok $PASS/$TOTAL Lua-on-SX tests passed"

View File

@@ -187,6 +187,17 @@
(make-symbol "arg") (make-symbol "arg")
(list (make-symbol "lua-varargs-arg-table") (make-symbol "__args") n)))) (list (make-symbol "lua-varargs-arg-table") (make-symbol "__args") n))))
(define
lua-tx-function-guard
(fn (body-sx)
(list
(make-symbol "guard")
(list (make-symbol "e")
(list
(list (make-symbol "lua-return-sentinel?") (make-symbol "e"))
(list (make-symbol "lua-return-value") (make-symbol "e"))))
body-sx)))
(define (define
lua-tx-function lua-tx-function
(fn (fn
@@ -200,7 +211,7 @@
(list (list
(make-symbol "fn") (make-symbol "fn")
(list (make-symbol "&rest") (make-symbol "__args")) (list (make-symbol "&rest") (make-symbol "__args"))
(lua-tx body))) (lua-tx-function-guard (lua-tx body))))
(else (else
(let (let
((bindings (lua-tx-function-bindings params 0))) ((bindings (lua-tx-function-bindings params 0)))
@@ -215,10 +226,11 @@
(list (list
(make-symbol "fn") (make-symbol "fn")
(list (make-symbol "&rest") (make-symbol "__args")) (list (make-symbol "&rest") (make-symbol "__args"))
(list (lua-tx-function-guard
(make-symbol "let") (list
all-bindings (make-symbol "let")
(lua-tx body)))))))))) all-bindings
(lua-tx body)))))))))))
(define (define
lua-tx-block lua-tx-block
@@ -460,13 +472,18 @@
(node) (node)
(let (let
((exps (nth node 1))) ((exps (nth node 1)))
(cond (let
((= (len exps) 0) nil) ((val
((= (len exps) 1) (lua-tx (first exps))) (cond
(else ((= (len exps) 0) nil)
(list ((= (len exps) 1) (lua-tx (first exps)))
(make-symbol "lua-pack-return") (else
(cons (make-symbol "list") (lua-tx-multi-args exps 0)))))))) (list
(make-symbol "lua-pack-return")
(cons (make-symbol "list") (lua-tx-multi-args exps 0)))))))
(list
(make-symbol "raise")
(list (make-symbol "list") (list (make-symbol "quote") (make-symbol "lua-ret")) val))))))
(define (define
lua-tx-local-function lua-tx-local-function
@@ -500,7 +517,9 @@
(define (define
lua-eval-ast lua-eval-ast
(fn (src) (let ((sx (lua-transpile src))) (eval-expr sx)))) (fn (src)
(let ((sx (lua-transpile src)))
(eval-expr (lua-tx-function-guard sx)))))
(define (define
lua-tx-multi-args lua-tx-multi-args

View File

@@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log.
_Newest first. Agent appends on every commit._ _Newest first. Agent appends on every commit._
- 2026-04-24: lua: scoreboard iteration — **proper early-return via guard+raise sentinel**. Fixes long-logged limitation: `if cond then return X end ...rest` now exits the enclosing function; `rest` is skipped. `lua-tx-return` raises `(list 'lua-ret value)`; every function body and the top-level chunk + loadstring'd chunks wrap in a guard that catches the sentinel and returns its value. Eliminates "compare incompatible types" from constructs.lua (past line 40). 368/368 green (+3 early-return tests).
- 2026-04-24: lua: scoreboard iteration — **unary-minus / `^` precedence fix**. Per Lua spec, `^` binds tighter than unary `-`, so `-2^2` should parse as `-(2^2) = -4`, not `(-2)^2 = 4`. My parser recursed into `parse-unary` and then let `^` bind to the already-negated operand. Added `parse-pow-chain` helper and changed the `else` branch of `parse-unary` to parse a primary + `^`-chain before returning; unary operators now wrap the full `^`-chain. Fixed `constructs.lua` past assert #3 (moved to compare-incompatible). 365/365 green (+3 precedence tests). - 2026-04-24: lua: scoreboard iteration — **unary-minus / `^` precedence fix**. Per Lua spec, `^` binds tighter than unary `-`, so `-2^2` should parse as `-(2^2) = -4`, not `(-2)^2 = 4`. My parser recursed into `parse-unary` and then let `^` bind to the already-negated operand. Added `parse-pow-chain` helper and changed the `else` branch of `parse-unary` to parse a primary + `^`-chain before returning; unary operators now wrap the full `^`-chain. Fixed `constructs.lua` past assert #3 (moved to compare-incompatible). 365/365 green (+3 precedence tests).
- 2026-04-24: lua: scoreboard iteration — `lua-byte-to-char` regression fix. My previous change returned 2-char strings (`"\a"` etc.) for bytes that SX string literals can't express (0, 7, 8, 11, 12, 1431, 127+), breaking `'a\0a'` length from 3 → 4. Now only 9/10/13 and printable 32-126 produce real bytes; others use a single `"?"` placeholder so `string.len` stays correct. literals.lua back to failing at assert #4 (was regressed to #2). - 2026-04-24: lua: scoreboard iteration — `lua-byte-to-char` regression fix. My previous change returned 2-char strings (`"\a"` etc.) for bytes that SX string literals can't express (0, 7, 8, 11, 12, 1431, 127+), breaking `'a\0a'` length from 3 → 4. Now only 9/10/13 and printable 32-126 produce real bytes; others use a single `"?"` placeholder so `string.len` stays correct. literals.lua back to failing at assert #4 (was regressed to #2).
- 2026-04-24: lua: scoreboard iteration — **decimal string escapes** `\ddd` (1-3 digits). Tokenizer `read-string` previously fell through to literal for digits, so `"\65"` came out as `"65"` not `"A"`. Added `read-decimal-escape!` consuming up to 3 digits while keeping value ≤255, plus `\a`/`\b`/`\f`/`\v` control escapes and `lua-byte-to-char` ASCII lookup. 362 tests (+2 escape tests). - 2026-04-24: lua: scoreboard iteration — **decimal string escapes** `\ddd` (1-3 digits). Tokenizer `read-string` previously fell through to literal for digits, so `"\65"` came out as `"65"` not `"A"`. Added `read-decimal-escape!` consuming up to 3 digits while keeping value ≤255, plus `\a`/`\b`/`\f`/`\v` control escapes and `lua-byte-to-char` ASCII lookup. 362 tests (+2 escape tests).
@@ -133,4 +134,4 @@ _Shared-file issues that need someone else to fix. Minimal repro only._
- **`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. - **`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.
- **`string.format`** supports only `%s`, `%d`, `%f`, `%%`. No width/precision flags (`%.2f`, `%5d`). - **`string.format`** supports only `%s`, `%d`, `%f`, `%%`. No width/precision flags (`%.2f`, `%5d`).
- **`string.char`** supports printable ASCII 32126 plus `\t`/`\n`/`\r`; other codes error. - **`string.char`** supports printable ASCII 32126 plus `\t`/`\n`/`\r`; other codes error.
- **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. - ~~Early `return` inside nested block~~ — **FIXED 2026-04-24** via guard+raise sentinel (`lua-ret`). All function bodies and the top-level chunk wrap in a guard that catches the return-sentinel; `return` statements raise it.