diff --git a/lib/lua/runtime.sx b/lib/lua/runtime.sx index d8b1fe8c..6fb811da 100644 --- a/lib/lua/runtime.sx +++ b/lib/lua/runtime.sx @@ -968,11 +968,16 @@ (define lua-string-match (fn (&rest args) - (let ((s (first args)) (pat (nth args 1))) - (let ((idx (index-of s pat))) - (cond - ((< idx 0) nil) - (else pat)))))) + (let ((s (first args)) (pat (nth args 1)) + (init (if (> (len args) 2) (nth args 2) 1))) + (let ((start-i0 (cond + ((< init 0) (let ((v (+ (len s) init))) (if (< v 0) 0 v))) + ((= init 0) 0) + (else (- init 1))))) + (let ((r (lua-pat-find pat s start-i0))) + (cond + ((= r nil) nil) + (else (substring s (first r) (nth r 1))))))))) ;; Literal-only string.gmatch: iterator producing each literal match of pat. (define @@ -983,14 +988,14 @@ (cond ((> pos (len s)) nil) (else - (let ((rest-str (if (= pos 0) s (substring s pos (len s))))) - (let ((idx (index-of rest-str pat))) - (cond - ((< idx 0) (begin (set! pos (+ (len s) 1)) nil)) - (else + (let ((r (lua-pat-find pat s pos))) + (cond + ((= r nil) (begin (set! pos (+ (len s) 1)) nil)) + (else + (let ((start (first r)) (end (nth r 1))) (begin - (set! pos (+ pos idx (len pat))) - pat))))))))))) + (set! pos (if (= end start) (+ end 1) end)) + (substring s start end)))))))))))) ;; Literal-only string.gsub: replace all occurrences of pat with repl (string only for now). (define @@ -1006,28 +1011,42 @@ (let ((out "") (pos 0) (count 0) (done false)) (begin (define - loop + gsub-loop (fn () (when (and (not done) (<= pos (len s))) - (let ((rest-str (if (= pos 0) s (substring s pos (len s))))) - (let ((idx (index-of rest-str pat))) - (cond - ((< idx 0) - (begin - (set! out (str out rest-str)) - (set! done true))) - ((and (>= max-n 0) (>= count max-n)) - (begin - (set! out (str out rest-str)) - (set! done true))) - (else - (let ((before (substring rest-str 0 idx))) - (begin - (set! out (str out before (if (= (type-of repl) "string") repl (str repl)))) - (set! pos (+ pos idx (len pat))) - (set! count (+ count 1)) - (loop)))))))))) - (loop) + (let ((r (lua-pat-find pat s pos))) + (cond + ((= r nil) + (begin + (set! out (str out (substring s pos (len s)))) + (set! done true))) + ((and (>= max-n 0) (>= count max-n)) + (begin + (set! out (str out (substring s pos (len s)))) + (set! done true))) + (else + (let ((start (first r)) (end (nth r 1))) + (let ((matched (substring s start end))) + (let ((replacement + (cond + ((= (type-of repl) "string") repl) + ((or (= (type-of repl) "function") (= (type-of repl) "lambda")) + (let ((rv (lua-call repl matched))) + (cond + ((or (= rv nil) (= rv false)) matched) + (else (str rv))))) + ((= (type-of repl) "dict") + (let ((v (get repl matched))) + (cond + ((= v nil) matched) + (else (str v))))) + (else (str repl))))) + (begin + (set! out (str out (substring s pos start) replacement)) + (set! pos (if (= end start) (+ end 1) end)) + (set! count (+ count 1)) + (gsub-loop))))))))))) + (gsub-loop) (list (quote lua-multi) out count)))))))) ;; Basic string.format: %s %d %f (%%.Nf ignored), %%. diff --git a/lib/lua/scoreboard.json b/lib/lua/scoreboard.json index 4f48fa7e..0b6bce3d 100644 --- a/lib/lua/scoreboard.json +++ b/lib/lua/scoreboard.json @@ -51,7 +51,7 @@ "name": "attrib.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: module 'C' not found\\\\\\\"\\", - "ms": 6112 + "ms": 6137 }, { "name": "big.lua", @@ -63,7 +63,7 @@ "name": "calls.lua", "status": "fail", "reason": "undefined symbol: fat\\", - "ms": 4800 + "ms": 4744 }, { "name": "checktable.lua", @@ -87,7 +87,7 @@ "name": "constructs.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to compare incompat", - "ms": 6618 + "ms": 6638 }, { "name": "db.lua", @@ -99,13 +99,13 @@ "name": "errors.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 3080 + "ms": 3109 }, { "name": "events.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 7192 + "ms": 7069 }, { "name": "files.lua", @@ -123,13 +123,13 @@ "name": "literals.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 1777 + "ms": 1866 }, { "name": "locals.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio", - "ms": 1644 + "ms": 1612 }, { "name": "main.lua", @@ -141,43 +141,43 @@ "name": "math.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 3999 + "ms": 3983 }, { "name": "nextvar.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 6948 + "ms": 6968 }, { "name": "pm.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 6326 + "ms": 5852 }, { "name": "sort.lua", "status": "timeout", "reason": "per-test timeout", - "ms": 8008 + "ms": 8007 }, { "name": "strings.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 3975 + "ms": 3925 }, { "name": "vararg.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 2152 + "ms": 2195 }, { "name": "verybig.lua", "status": "pass", "reason": "", - "ms": 562 + "ms": 930 } ] } \ No newline at end of file diff --git a/lib/lua/scoreboard.md b/lib/lua/scoreboard.md index c2093f26..b719d360 100644 --- a/lib/lua/scoreboard.md +++ b/lib/lua/scoreboard.md @@ -18,25 +18,25 @@ fail=12 timeout=3 skip=8 total=24 |---|---|---|---:| | all.lua | skip | driver uses dofile to chain other tests | 0 | | api.lua | skip | requires testC (C debug library) | 0 | -| attrib.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: module 'C' not found\\\"\ | 6112 | +| attrib.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: module 'C' not found\\\"\ | 6137 | | big.lua | timeout | per-test timeout | 8008 | -| calls.lua | fail | undefined symbol: fat\ | 4800 | +| calls.lua | fail | undefined symbol: fat\ | 4744 | | checktable.lua | skip | internal debug helpers | 0 | | closure.lua | timeout | per-test timeout | 8008 | | code.lua | skip | bytecode inspection via debug library | 0 | -| constructs.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to compare incompat | 6618 | +| constructs.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to compare incompat | 6638 | | db.lua | skip | debug library | 0 | -| errors.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3080 | -| events.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7192 | +| errors.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3109 | +| events.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7069 | | files.lua | skip | io library | 0 | | gc.lua | skip | collectgarbage / finalisers | 0 | -| literals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 1777 | -| locals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 1644 | +| literals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 1866 | +| locals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 1612 | | main.lua | skip | standalone interpreter driver | 0 | -| math.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3999 | -| nextvar.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6948 | -| pm.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6326 | -| sort.lua | timeout | per-test timeout | 8008 | -| strings.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3975 | -| vararg.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2152 | -| verybig.lua | pass | - | 562 | +| math.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3983 | +| nextvar.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6968 | +| pm.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 5852 | +| sort.lua | timeout | per-test timeout | 8007 | +| strings.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3925 | +| vararg.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2195 | +| verybig.lua | pass | - | 930 | diff --git a/lib/lua/test.sh b/lib/lua/test.sh index 8ef4ec8f..22922649 100755 --- a/lib/lua/test.sh +++ b/lib/lua/test.sh @@ -964,6 +964,20 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1881) (eval "(lua-eval-ast \"local function f() return 10, 20 end return (f()) + 1\")") +;; ── Lua patterns in match/gmatch/gsub ──────────────────────── +(epoch 1890) +(eval "(lua-eval-ast \"return string.match(\\\"hello123world\\\", \\\"%d+\\\")\")") +(epoch 1891) +(eval "(lua-eval-ast \"return string.match(\\\"name=bob\\\", \\\"%a+\\\")\")") +(epoch 1892) +(eval "(lua-eval-ast \"local c = 0 for w in string.gmatch(\\\"a b c d\\\", \\\"%a\\\") do c = c + 1 end return c\")") +(epoch 1893) +(eval "(lua-eval-ast \"local r, n = string.gsub(\\\"1 + 2 = 3\\\", \\\"%d\\\", \\\"X\\\") return r .. \\\":\\\" .. n\")") +(epoch 1894) +(eval "(lua-eval-ast \"if string.find(\\\"hello\\\", \\\"^hel\\\") then return 1 else return 0 end\")") +(epoch 1895) +(eval "(lua-eval-ast \"if string.find(\\\"hello\\\", \\\"^wor\\\") then return 0 else return 1 end\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1460,6 +1474,14 @@ check 1870 "a:add():add():add().x chain" '60' check 1880 "(f()) scalar coerce" '1' check 1881 "(f()) + 1 scalar" '11' +# ── Lua patterns in match/gmatch/gsub ──────────────────────── +check 1890 "match %d+" '"123"' +check 1891 "match %a+" '"name"' +check 1892 "gmatch %a count" '4' +check 1893 "gsub %d → X" '"X + X = X:3"' +check 1894 "find ^ anchor hit" '1' +check 1895 "find ^ anchor miss" '1' + 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 e09eb546..09af73fa 100644 --- a/plans/lua-on-sx.md +++ b/plans/lua-on-sx.md @@ -82,6 +82,7 @@ Each item: implement → tests → tick box → update progress log. _Newest first. Agent appends on every commit._ +- 2026-04-24: lua: scoreboard iteration — extended pattern engine to `string.match`/`gmatch`/`gsub`. `gsub` now supports string/function/table replacement modes. 381/381 green (+6 pattern tests). - 2026-04-24: lua: scoreboard iteration — **Lua pattern engine (minimal)** for `string.find`. Supports character classes (`%d`/`%a`/`%s`/`%w`/`%p`/`%l`/`%u`/`%c`/`%x` + complements), `.` any, `^`/`$` anchors, quantifiers `*`/`+`/`-`/`?`, literal chars, `%%`. Added `plain` arg pathway. match/gmatch/gsub still literal. Scoreboard unchanged (pattern-using tests still hit other issues downstream). - 2026-04-24: lua: scoreboard iteration — `package.cpath`/`config`/`loaders`/`searchers`/`searchpath` stubs. attrib.lua moves from #9 (checking `package.cpath` is a string) to "module 'C' not found" — test requires filesystem-based module loading, not tractable. Most remaining failures need Lua pattern matching (pm.lua/strings.lua), env tracking (locals.lua/events.lua), or filesystem (attrib.lua). - 2026-04-24: lua: scoreboard iteration — **parenthesized expressions truncate multi-return** (Lua spec: `(f())` forces single value even if `f` returns multi). Parser wraps `(expr)` in a new `lua-paren` AST node; transpile emits `(lua-first inner)`. Fixes `constructs.lua`@30 (`a,b,c = (f())` expects `a=1, b=nil, c=nil`) and `math.lua`@13. 375/375 green (+2 paren tests). Scoreboard: 8× asserts (was 10).