From fd32bcf547f57cf1a10a6e6438fb7d6c871f8ac3 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 25 Apr 2026 00:24:05 +0000 Subject: [PATCH] lua: string.format width/zero-pad/hex/octal/char/precision +6 tests --- lib/lua/runtime.sx | 133 +++++++++++++++++++++++++++++++++++----- lib/lua/scoreboard.json | 58 ++++++++---------- lib/lua/scoreboard.md | 35 +++++------ lib/lua/test.sh | 22 +++++++ plans/lua-on-sx.md | 1 + 5 files changed, 183 insertions(+), 66 deletions(-) diff --git a/lib/lua/runtime.sx b/lib/lua/runtime.sx index 118f9071..3c148e08 100644 --- a/lib/lua/runtime.sx +++ b/lib/lua/runtime.sx @@ -1145,6 +1145,39 @@ ((= (type-of n) "number") (str (floor n))) (else (str n))))) +(define + lua-fmt-pad + (fn (s width left-align zero-pad) + (let ((diff (- width (len s)))) + (cond + ((<= diff 0) s) + (else + (let ((pad (lua-string-rep (if (and zero-pad (not left-align)) "0" " ") diff))) + (if left-align (str s pad) (str pad s)))))))) + +(define + lua-fmt-int-base + (fn (n base upper) + (cond + ((= n 0) "0") + (else + (let ((sign "") (v n) (out "")) + (begin + (when (< v 0) (begin (set! sign "-") (set! v (- 0 v)))) + (set! v (floor v)) + (define + ib-loop + (fn () + (when (> v 0) + (let ((d (- v (* base (floor (/ v base)))))) + (let ((c (cond + ((< d 10) (char-at "0123456789" d)) + (upper (char-at "ABCDEFGHIJKLMNOP" (- d 10))) + (else (char-at "abcdefghijklmnop" (- d 10)))))) + (begin (set! out (str c out)) (set! v (floor (/ v base))) (ib-loop))))))) + (ib-loop) + (str sign out))))))) + (define lua-string-format (fn (&rest args) @@ -1158,24 +1191,90 @@ (let ((c (char-at fmt i))) (cond ((and (= c "%") (< (+ i 1) (len fmt))) - (let ((spec (char-at fmt (+ i 1)))) - (cond - ((= spec "%") - (begin (set! out (str out "%")) (set! i (+ i 2)) (loop))) - ((= spec "s") + (let ((j (+ i 1)) (left-align false) (zero-pad false) (width 0) (prec -1)) + (begin + (when (and (< j (len fmt)) (= (char-at fmt j) "-")) + (begin (set! left-align true) (set! j (+ j 1)))) + (when (and (< j (len fmt)) (= (char-at fmt j) "0")) + (begin (set! zero-pad true) (set! j (+ j 1)))) + (define + wd-loop + (fn () + (when (and (< j (len fmt)) (>= (char-at fmt j) "0") (<= (char-at fmt j) "9")) + (begin + (set! width (+ (* width 10) (- (char-code (char-at fmt j)) (char-code "0")))) + (set! j (+ j 1)) + (wd-loop))))) + (wd-loop) + (when (and (< j (len fmt)) (= (char-at fmt j) ".")) (begin - (set! out (str out (lua-concat-coerce (nth vals vi)))) - (set! vi (+ vi 1)) (set! i (+ i 2)) (loop))) - ((= spec "d") - (begin - (set! out (str out (lua-format-int (nth vals vi)))) - (set! vi (+ vi 1)) (set! i (+ i 2)) (loop))) - ((= spec "f") - (begin - (set! out (str out (str (nth vals vi)))) - (set! vi (+ vi 1)) (set! i (+ i 2)) (loop))) - (else - (begin (set! out (str out c)) (set! i (+ i 1)) (loop)))))) + (set! prec 0) + (set! j (+ j 1)) + (define + pr-loop + (fn () + (when (and (< j (len fmt)) (>= (char-at fmt j) "0") (<= (char-at fmt j) "9")) + (begin + (set! prec (+ (* prec 10) (- (char-code (char-at fmt j)) (char-code "0")))) + (set! j (+ j 1)) + (pr-loop))))) + (pr-loop))) + (when (< j (len fmt)) + (let ((spec (char-at fmt j))) + (cond + ((= spec "%") + (begin (set! out (str out "%")) (set! i (+ j 1)))) + ((= spec "s") + (let ((v (lua-concat-coerce (nth vals vi)))) + (let ((vt (if (and (>= prec 0) (< prec (len v))) (substring v 0 prec) v))) + (begin + (set! out (str out (lua-fmt-pad vt width left-align false))) + (set! vi (+ vi 1)) + (set! i (+ j 1)))))) + ((= spec "d") + (let ((v (lua-fmt-int-base (lua-to-number (nth vals vi)) 10 false))) + (begin + (set! out (str out (lua-fmt-pad v width left-align zero-pad))) + (set! vi (+ vi 1)) + (set! i (+ j 1))))) + ((= spec "x") + (let ((v (lua-fmt-int-base (lua-to-number (nth vals vi)) 16 false))) + (begin + (set! out (str out (lua-fmt-pad v width left-align zero-pad))) + (set! vi (+ vi 1)) + (set! i (+ j 1))))) + ((= spec "X") + (let ((v (lua-fmt-int-base (lua-to-number (nth vals vi)) 16 true))) + (begin + (set! out (str out (lua-fmt-pad v width left-align zero-pad))) + (set! vi (+ vi 1)) + (set! i (+ j 1))))) + ((= spec "o") + (let ((v (lua-fmt-int-base (lua-to-number (nth vals vi)) 8 false))) + (begin + (set! out (str out (lua-fmt-pad v width left-align zero-pad))) + (set! vi (+ vi 1)) + (set! i (+ j 1))))) + ((= spec "f") + (let ((v (str (lua-to-number (nth vals vi))))) + (begin + (set! out (str out (lua-fmt-pad v width left-align zero-pad))) + (set! vi (+ vi 1)) + (set! i (+ j 1))))) + ((= spec "c") + (let ((v (lua-char-one (lua-to-number (nth vals vi))))) + (begin + (set! out (str out v)) + (set! vi (+ vi 1)) + (set! i (+ j 1))))) + ((= spec "q") + (let ((v (str "\"" (lua-concat-coerce (nth vals vi)) "\""))) + (begin + (set! out (str out v)) + (set! vi (+ vi 1)) + (set! i (+ j 1))))) + (else (begin (set! out (str out c)) (set! i (+ i 1))))))) + (loop)))) (else (begin (set! out (str out c)) (set! i (+ i 1)) (loop)))))))) (loop) diff --git a/lib/lua/scoreboard.json b/lib/lua/scoreboard.json index ece8ffe3..ef127e69 100644 --- a/lib/lua/scoreboard.json +++ b/lib/lua/scoreboard.json @@ -1,8 +1,8 @@ { "totals": { "pass": 1, - "fail": 11, - "timeout": 4, + "fail": 7, + "timeout": 8, "skip": 8, "total": 24, "runnable": 16, @@ -10,11 +10,11 @@ }, "top_failure_modes": [ [ - "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - 7 + "timeout", + 8 ], [ - "timeout", + "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", 4 ], [ @@ -24,10 +24,6 @@ [ "undefined symbol: fat\\", 1 - ], - [ - "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to compare incompat", - 1 ] ], "results": [ @@ -47,7 +43,7 @@ "name": "attrib.lua", "status": "timeout", "reason": "per-test timeout", - "ms": 8008 + "ms": 8000 }, { "name": "big.lua", @@ -59,7 +55,7 @@ "name": "calls.lua", "status": "fail", "reason": "undefined symbol: fat\\", - "ms": 6184 + "ms": 6732 }, { "name": "checktable.lua", @@ -71,7 +67,7 @@ "name": "closure.lua", "status": "timeout", "reason": "per-test timeout", - "ms": 8001 + "ms": 8005 }, { "name": "code.lua", @@ -81,9 +77,9 @@ }, { "name": "constructs.lua", - "status": "fail", - "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to compare incompat", - "ms": 7870 + "status": "timeout", + "reason": "per-test timeout", + "ms": 8007 }, { "name": "db.lua", @@ -95,13 +91,13 @@ "name": "errors.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 3808 + "ms": 4579 }, { "name": "events.lua", - "status": "fail", - "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 7990 + "status": "timeout", + "reason": "per-test timeout", + "ms": 8007 }, { "name": "files.lua", @@ -119,13 +115,13 @@ "name": "literals.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 2085 + "ms": 2829 }, { "name": "locals.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio", - "ms": 1811 + "ms": 2598 }, { "name": "main.lua", @@ -137,19 +133,19 @@ "name": "math.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"lua: attempt to call non-functio", - "ms": 4436 + "ms": 6606 }, { "name": "nextvar.lua", - "status": "fail", - "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 7759 + "status": "timeout", + "reason": "per-test timeout", + "ms": 8008 }, { "name": "pm.lua", - "status": "fail", - "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 6473 + "status": "timeout", + "reason": "per-test timeout", + "ms": 8009 }, { "name": "sort.lua", @@ -161,19 +157,19 @@ "name": "strings.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 4366 + "ms": 6228 }, { "name": "vararg.lua", "status": "fail", "reason": "other: Unhandled exception: \\\"Unhandled exception: \\\\\\\"assertion failed!\\\\\\\"\\", - "ms": 2422 + "ms": 3087 }, { "name": "verybig.lua", "status": "pass", "reason": "", - "ms": 1077 + "ms": 1058 } ] } \ No newline at end of file diff --git a/lib/lua/scoreboard.md b/lib/lua/scoreboard.md index 060e4435..50da0d66 100644 --- a/lib/lua/scoreboard.md +++ b/lib/lua/scoreboard.md @@ -1,15 +1,14 @@ # Lua-on-SX conformance scoreboard **Pass rate:** 1/16 runnable (6.2%) -fail=11 timeout=4 skip=8 total=24 +fail=7 timeout=8 skip=8 total=24 ## Top failure modes -- **7x** other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ -- **4x** timeout +- **8x** timeout +- **4x** other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ - **2x** other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio - **1x** undefined symbol: fat\ -- **1x** other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to compare incompat ## Per-test results @@ -17,25 +16,25 @@ fail=11 timeout=4 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 | timeout | per-test timeout | 8008 | +| attrib.lua | timeout | per-test timeout | 8000 | | big.lua | timeout | per-test timeout | 8007 | -| calls.lua | fail | undefined symbol: fat\ | 6184 | +| calls.lua | fail | undefined symbol: fat\ | 6732 | | checktable.lua | skip | internal debug helpers | 0 | -| closure.lua | timeout | per-test timeout | 8001 | +| closure.lua | timeout | per-test timeout | 8005 | | code.lua | skip | bytecode inspection via debug library | 0 | -| constructs.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to compare incompat | 7870 | +| constructs.lua | timeout | per-test timeout | 8007 | | db.lua | skip | debug library | 0 | -| errors.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3808 | -| events.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7990 | +| errors.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4579 | +| events.lua | timeout | per-test timeout | 8007 | | files.lua | skip | io library | 0 | | gc.lua | skip | collectgarbage / finalisers | 0 | -| literals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2085 | -| locals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 1811 | +| literals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2829 | +| locals.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 2598 | | main.lua | skip | standalone interpreter driver | 0 | -| math.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 4436 | -| nextvar.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 7759 | -| pm.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6473 | +| math.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"lua: attempt to call non-functio | 6606 | +| nextvar.lua | timeout | per-test timeout | 8008 | +| pm.lua | timeout | per-test timeout | 8009 | | sort.lua | timeout | per-test timeout | 8007 | -| strings.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 4366 | -| vararg.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 2422 | -| verybig.lua | pass | - | 1077 | +| strings.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 6228 | +| vararg.lua | fail | other: Unhandled exception: \"Unhandled exception: \\\"assertion failed!\\\"\ | 3087 | +| verybig.lua | pass | - | 1058 | diff --git a/lib/lua/test.sh b/lib/lua/test.sh index bc846a73..ef794195 100755 --- a/lib/lua/test.sh +++ b/lib/lua/test.sh @@ -994,6 +994,20 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1912) (eval "(lua-eval-ast \"return string.match(\\\"abc\\\", \\\"[^a]+\\\")\")") +;; ── string.format width/precision/hex/octal/char ───────────── +(epoch 1920) +(eval "(lua-eval-ast \"return string.format('%5d', 42)\")") +(epoch 1921) +(eval "(lua-eval-ast \"return string.format('%05d', 42)\")") +(epoch 1922) +(eval "(lua-eval-ast \"return string.format('%x', 255)\")") +(epoch 1923) +(eval "(lua-eval-ast \"return string.format('%X', 255)\")") +(epoch 1924) +(eval "(lua-eval-ast \"return string.format('%c', 65)\")") +(epoch 1925) +(eval "(lua-eval-ast \"return string.format('%.3s', 'hello')\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1508,6 +1522,14 @@ check 1910 "[a-z]+ on hello123" '"hello"' check 1911 "[0-9]+ on hello123" '"123"' check 1912 "[^a]+ on abc" '"bc"' +# ── string.format width/precision/hex/octal/char ──────────── +check 1920 "%5d width" '" 42"' +check 1921 "%05d zero-pad" '"00042"' +check 1922 "%x hex" '"ff"' +check 1923 "%X HEX" '"FF"' +check 1924 "%c char" '"A"' +check 1925 "%.3s precision" '"hel"' + 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 4d933d56..719fa5c8 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-25: lua: scoreboard iteration — extended `string.format`. New `%x`/`%X`/`%o` (hex/octal), `%c` (codepoint→char via `lua-char-one`), `%q` (basic quote), width N (`%5d`), zero-pad (`%05d`), left-align (`%-5d`), `%.Ns` precision. Helpers `lua-fmt-pad` and `lua-fmt-int-base`. 393/393 green (+6 format tests). - 2026-04-25: lua: scoreboard iteration — `lua-eval-ast` now SKIPS the top-level guard when the parsed chunk has no top-level `return` (recursive AST walk via `lua-has-top-return?` that descends through control-flow but stops at function-body boundaries). Without the guard, top-level user defines leak to the SX top env, and `loadstring`-captured closures can find them. Verified: `function fat(x)...loadstring("return fat(...)")...end; x=fat(5)` works (was undefined). Most PUC-Rio tests still have top-level returns elsewhere, so they still need the guard. Scoreboard unchanged at 1/16 but unblocks future work. - 2026-04-25: lua: scoreboard iteration — math fns now error on bad/missing args (was silently returning 0). New `lua-math-num "name" x` validator wraps `abs`/`ceil`/`floor`/`sqrt`/`exp`/`sin`/`cos`/`tan`/`asin`/`acos`/`atan`/`atan2`/`pow`. errors.lua moves past assert #4 (`pcall(math.sin)` now returns false+err as expected). - 2026-04-24: lua: scoreboard iteration — **pattern character sets** `[...]` and `[^...]`. New `lua-pat-set-end`/`lua-pat-set-match` helpers handle ranges (`[a-z]`), classes inside sets (`[%d%a]`), negation (`[^abc]`), and `[]...]`/`[^]...]` (literal `]` as first char). Asserts 6→4, but timeouts 3→7 — many tests now reach loop-heavy code. 387/387 green (+3 charset tests).