js-on-sx: spread ... in array literals and call args
Parser: jp-array-loop and jp-call-args-loop detect punct "..." and emit (js-spread inner). Transpile: when any element is spread, build array/args via js-array-spread-build with (list "js-value" v) and (list "js-spread" xs) tags. Runtime: js-array-spread-build walks items, appending values or splicing spreads via js-iterable-to-list (handles list/string/dict). Works in arrays, call args, variadic fns (Math.max(...arr)), and string spread ([...'abc']). 414/416 unit (+5), 148/148 slice unchanged.
This commit is contained in:
@@ -486,11 +486,18 @@
|
|||||||
(cond
|
(cond
|
||||||
((jp-at? st "punct" "]") nil)
|
((jp-at? st "punct" "]") nil)
|
||||||
(else
|
(else
|
||||||
(do
|
(begin
|
||||||
(append! elems (jp-parse-assignment st))
|
(cond
|
||||||
|
((jp-at? st "punct" "...")
|
||||||
|
(begin
|
||||||
|
(jp-advance! st)
|
||||||
|
(append!
|
||||||
|
elems
|
||||||
|
(list (quote js-spread) (jp-parse-assignment st)))))
|
||||||
|
(else (append! elems (jp-parse-assignment st))))
|
||||||
(cond
|
(cond
|
||||||
((jp-at? st "punct" ",")
|
((jp-at? st "punct" ",")
|
||||||
(do (jp-advance! st) (jp-array-loop st elems)))
|
(begin (jp-advance! st) (jp-array-loop st elems)))
|
||||||
(else nil)))))))
|
(else nil)))))))
|
||||||
|
|
||||||
;; ── Entry point ─────────────────────────────────────────────────
|
;; ── Entry point ─────────────────────────────────────────────────
|
||||||
@@ -600,11 +607,18 @@
|
|||||||
(cond
|
(cond
|
||||||
((jp-at? st "punct" ")") nil)
|
((jp-at? st "punct" ")") nil)
|
||||||
(else
|
(else
|
||||||
(do
|
(begin
|
||||||
(append! args (jp-parse-assignment st))
|
(cond
|
||||||
|
((jp-at? st "punct" "...")
|
||||||
|
(begin
|
||||||
|
(jp-advance! st)
|
||||||
|
(append!
|
||||||
|
args
|
||||||
|
(list (quote js-spread) (jp-parse-assignment st)))))
|
||||||
|
(else (append! args (jp-parse-assignment st))))
|
||||||
(cond
|
(cond
|
||||||
((jp-at? st "punct" ",")
|
((jp-at? st "punct" ",")
|
||||||
(do (jp-advance! st) (jp-call-args-loop st args)))
|
(begin (jp-advance! st) (jp-call-args-loop st args)))
|
||||||
(else nil)))))))
|
(else nil)))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
|
|||||||
@@ -1750,6 +1750,26 @@
|
|||||||
|
|
||||||
(define Object {:entries js-object-entries :values js-object-values :freeze js-object-freeze :assign js-object-assign :keys js-object-keys})
|
(define Object {:entries js-object-entries :values js-object-values :freeze js-object-freeze :assign js-object-assign :keys js-object-keys})
|
||||||
|
|
||||||
|
(define
|
||||||
|
js-array-spread-build
|
||||||
|
(fn
|
||||||
|
(&rest items)
|
||||||
|
(let
|
||||||
|
((result (list)))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(item)
|
||||||
|
(let
|
||||||
|
((kind (nth item 0)))
|
||||||
|
(cond
|
||||||
|
((= kind "js-spread")
|
||||||
|
(for-each
|
||||||
|
(fn (x) (append! result x))
|
||||||
|
(js-iterable-to-list (nth item 1))))
|
||||||
|
(else (append! result (nth item 1))))))
|
||||||
|
items)
|
||||||
|
result)))
|
||||||
|
|
||||||
(define js-array-is-array (fn (v) (list? v)))
|
(define js-array-is-array (fn (v) (list? v)))
|
||||||
|
|
||||||
(define js-array-of (fn (&rest args) args))
|
(define js-array-of (fn (&rest args) args))
|
||||||
|
|||||||
@@ -1059,6 +1059,18 @@ cat > "$TMPFILE" << 'EPOCHS'
|
|||||||
(epoch 2401)
|
(epoch 2401)
|
||||||
(eval "(js-eval \"'abcd'.slice('1', '3')\")")
|
(eval "(js-eval \"'abcd'.slice('1', '3')\")")
|
||||||
|
|
||||||
|
;; ── Phase 11.spread: ... in arrays and calls ───────────────────
|
||||||
|
(epoch 2500)
|
||||||
|
(eval "(js-eval \"var a=[1,2]; var b=[...a,3,4]; b.length\")")
|
||||||
|
(epoch 2501)
|
||||||
|
(eval "(js-eval \"var a=[1,2]; var b=[0,...a,3]; b.join(',')\")")
|
||||||
|
(epoch 2502)
|
||||||
|
(eval "(js-eval \"function f(a,b,c){ return a+b+c; } var args=[1,2,3]; f(...args)\")")
|
||||||
|
(epoch 2503)
|
||||||
|
(eval "(js-eval \"Math.max(...[1,5,3])\")")
|
||||||
|
(epoch 2504)
|
||||||
|
(eval "(js-eval \"var a=[...'abc']; a.length\")")
|
||||||
|
|
||||||
EPOCHS
|
EPOCHS
|
||||||
|
|
||||||
|
|
||||||
@@ -1629,6 +1641,13 @@ check 2307 "Array.from w/ map" '"2,4,6"'
|
|||||||
check 2400 "charAt coerces string" '"c"'
|
check 2400 "charAt coerces string" '"c"'
|
||||||
check 2401 "slice coerces strings" '"bc"'
|
check 2401 "slice coerces strings" '"bc"'
|
||||||
|
|
||||||
|
# ── Phase 11.spread ───────────────────────────────────────────
|
||||||
|
check 2500 "spread in array length" '4'
|
||||||
|
check 2501 "spread middle of array" '"0,1,2,3"'
|
||||||
|
check 2502 "spread in call args" '6'
|
||||||
|
check 2503 "spread Math.max" '5'
|
||||||
|
check 2504 "spread string" '3'
|
||||||
|
|
||||||
TOTAL=$((PASS + FAIL))
|
TOTAL=$((PASS + FAIL))
|
||||||
if [ $FAIL -eq 0 ]; then
|
if [ $FAIL -eq 0 ]; then
|
||||||
echo "✓ $PASS/$TOTAL JS-on-SX tests passed"
|
echo "✓ $PASS/$TOTAL JS-on-SX tests passed"
|
||||||
|
|||||||
@@ -254,15 +254,15 @@
|
|||||||
(fn
|
(fn
|
||||||
(callee args)
|
(callee args)
|
||||||
(cond
|
(cond
|
||||||
((and (list? callee) (js-tag? callee "js-member"))
|
((and (js-tag? callee "js-member") (not (js-has-spread? args)))
|
||||||
(let
|
(let
|
||||||
((recv (js-transpile (nth callee 1))) (key (nth callee 2)))
|
((recv (js-transpile (nth callee 1))) (key (nth callee 2)))
|
||||||
(list
|
(list
|
||||||
(js-sym "js-invoke-method")
|
(js-sym "js-invoke-method")
|
||||||
recv
|
recv
|
||||||
key
|
key
|
||||||
(cons (js-sym "list") (map js-transpile args)))))
|
(js-transpile-args args))))
|
||||||
((and (list? callee) (js-tag? callee "js-index"))
|
((and (js-tag? callee "js-index") (not (js-has-spread? args)))
|
||||||
(let
|
(let
|
||||||
((recv (js-transpile (nth callee 1)))
|
((recv (js-transpile (nth callee 1)))
|
||||||
(key (js-transpile (nth callee 2))))
|
(key (js-transpile (nth callee 2))))
|
||||||
@@ -270,12 +270,29 @@
|
|||||||
(js-sym "js-invoke-method-dyn")
|
(js-sym "js-invoke-method-dyn")
|
||||||
recv
|
recv
|
||||||
key
|
key
|
||||||
(cons (js-sym "list") (map js-transpile args)))))
|
(js-transpile-args args))))
|
||||||
|
((js-tag? callee "js-member")
|
||||||
|
(let
|
||||||
|
((recv (js-transpile (nth callee 1))) (key (nth callee 2)))
|
||||||
|
(list
|
||||||
|
(js-sym "js-invoke-method")
|
||||||
|
recv
|
||||||
|
key
|
||||||
|
(js-transpile-args args))))
|
||||||
|
((js-tag? callee "js-index")
|
||||||
|
(let
|
||||||
|
((recv (js-transpile (nth callee 1)))
|
||||||
|
(key (js-transpile (nth callee 2))))
|
||||||
|
(list
|
||||||
|
(js-sym "js-invoke-method-dyn")
|
||||||
|
recv
|
||||||
|
key
|
||||||
|
(js-transpile-args args))))
|
||||||
(else
|
(else
|
||||||
(list
|
(list
|
||||||
(js-sym "js-call-plain")
|
(js-sym "js-call-plain")
|
||||||
(js-transpile callee)
|
(js-transpile callee)
|
||||||
(cons (js-sym "list") (map js-transpile args)))))))
|
(js-transpile-args args))))))
|
||||||
|
|
||||||
;; ── Array literal ─────────────────────────────────────────────────
|
;; ── Array literal ─────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -295,10 +312,58 @@
|
|||||||
;; order and allows computed values.
|
;; order and allows computed values.
|
||||||
(define
|
(define
|
||||||
js-transpile-array
|
js-transpile-array
|
||||||
(fn (elts) (cons (js-sym "list") (map js-transpile elts))))
|
(fn
|
||||||
|
(elts)
|
||||||
|
(if
|
||||||
|
(js-has-spread? elts)
|
||||||
|
(cons
|
||||||
|
(js-sym "js-array-spread-build")
|
||||||
|
(map
|
||||||
|
(fn
|
||||||
|
(e)
|
||||||
|
(if
|
||||||
|
(js-tag? e "js-spread")
|
||||||
|
(list (js-sym "list") "js-spread" (js-transpile (nth e 1)))
|
||||||
|
(list (js-sym "list") "js-value" (js-transpile e))))
|
||||||
|
elts))
|
||||||
|
(cons (js-sym "list") (map js-transpile elts)))))
|
||||||
|
|
||||||
;; ── Conditional ───────────────────────────────────────────────────
|
;; ── Conditional ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
js-has-spread?
|
||||||
|
(fn
|
||||||
|
(lst)
|
||||||
|
(cond
|
||||||
|
((empty? lst) false)
|
||||||
|
((js-tag? (first lst) "js-spread") true)
|
||||||
|
(else (js-has-spread? (rest lst))))))
|
||||||
|
|
||||||
|
;; ── Arrow function ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
js-transpile-args
|
||||||
|
(fn
|
||||||
|
(args)
|
||||||
|
(if
|
||||||
|
(js-has-spread? args)
|
||||||
|
(cons
|
||||||
|
(js-sym "js-array-spread-build")
|
||||||
|
(map
|
||||||
|
(fn
|
||||||
|
(e)
|
||||||
|
(if
|
||||||
|
(js-tag? e "js-spread")
|
||||||
|
(list (js-sym "list") "js-spread" (js-transpile (nth e 1)))
|
||||||
|
(list (js-sym "list") "js-value" (js-transpile e))))
|
||||||
|
args))
|
||||||
|
(cons (js-sym "list") (map js-transpile args)))))
|
||||||
|
|
||||||
|
;; ── Assignment ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; `a = b` on an ident → (set! a b).
|
||||||
|
;; `a += b` on an ident → (set! a (js-add a b)).
|
||||||
|
;; `obj.k = v` / `obj[k] = v` → (js-set-prop obj "k" v).
|
||||||
(define
|
(define
|
||||||
js-transpile-object
|
js-transpile-object
|
||||||
(fn
|
(fn
|
||||||
@@ -320,8 +385,6 @@
|
|||||||
entries)
|
entries)
|
||||||
(list (js-sym "_obj")))))))
|
(list (js-sym "_obj")))))))
|
||||||
|
|
||||||
;; ── Arrow function ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
js-transpile-cond
|
js-transpile-cond
|
||||||
(fn
|
(fn
|
||||||
@@ -332,11 +395,6 @@
|
|||||||
(js-transpile t)
|
(js-transpile t)
|
||||||
(js-transpile f))))
|
(js-transpile f))))
|
||||||
|
|
||||||
;; ── Assignment ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; `a = b` on an ident → (set! a b).
|
|
||||||
;; `a += b` on an ident → (set! a (js-add a b)).
|
|
||||||
;; `obj.k = v` / `obj[k] = v` → (js-set-prop obj "k" v).
|
|
||||||
(define
|
(define
|
||||||
js-transpile-arrow
|
js-transpile-arrow
|
||||||
(fn
|
(fn
|
||||||
@@ -367,6 +425,9 @@
|
|||||||
(append inits (list (js-transpile body))))))))
|
(append inits (list (js-transpile body))))))))
|
||||||
(list (js-sym "fn") param-syms body-tr))))
|
(list (js-sym "fn") param-syms body-tr))))
|
||||||
|
|
||||||
|
;; ── End-to-end entry points ───────────────────────────────────────
|
||||||
|
|
||||||
|
;; Transpile + eval a single JS expression string.
|
||||||
(define
|
(define
|
||||||
js-transpile-tpl
|
js-transpile-tpl
|
||||||
(fn
|
(fn
|
||||||
@@ -378,6 +439,8 @@
|
|||||||
(else
|
(else
|
||||||
(cons (js-sym "js-template-concat") (js-transpile-tpl-parts parts))))))
|
(cons (js-sym "js-template-concat") (js-transpile-tpl-parts parts))))))
|
||||||
|
|
||||||
|
;; Transpile a JS expression string to SX source text (for inspection
|
||||||
|
;; in tests). Useful for asserting the exact emitted tree.
|
||||||
(define
|
(define
|
||||||
js-transpile-tpl-parts
|
js-transpile-tpl-parts
|
||||||
(fn
|
(fn
|
||||||
@@ -389,9 +452,6 @@
|
|||||||
(js-transpile (first parts))
|
(js-transpile (first parts))
|
||||||
(js-transpile-tpl-parts (rest parts))))))
|
(js-transpile-tpl-parts (rest parts))))))
|
||||||
|
|
||||||
;; ── End-to-end entry points ───────────────────────────────────────
|
|
||||||
|
|
||||||
;; Transpile + eval a single JS expression string.
|
|
||||||
(define
|
(define
|
||||||
js-transpile-assign
|
js-transpile-assign
|
||||||
(fn
|
(fn
|
||||||
@@ -429,8 +489,6 @@
|
|||||||
(js-transpile rhs))))
|
(js-transpile rhs))))
|
||||||
(else (error "js-transpile-assign: unsupported target")))))
|
(else (error "js-transpile-assign: unsupported target")))))
|
||||||
|
|
||||||
;; Transpile a JS expression string to SX source text (for inspection
|
|
||||||
;; in tests). Useful for asserting the exact emitted tree.
|
|
||||||
(define
|
(define
|
||||||
js-compound-update
|
js-compound-update
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
@@ -191,6 +191,8 @@ Append-only record of completed iterations. Loop writes one line per iteration:
|
|||||||
|
|
||||||
- 2026-04-23 — **Array.prototype flat/fill; indexOf start arg; for..of/for..in.** Array: `flat(depth=1)` uses `js-list-flat-loop` (recursive flatten), `fill(value, start?, end?)` mutates in-place then returns self via `js-list-fill-loop`. Fixed `indexOf` to honor the `fromIndex` second argument. Parser: `jp-parse-for-stmt` now does a 2-token lookahead — if it sees `(var? ident (of|in) expr)` it emits `(js-for-of-in kind ident iter body)`, else falls back to classic `for(;;)`. Transpile: `js-transpile-for-of-in` wraps body in `(call/cc (fn (__break__) (let ((__js_items__ <normalized-source>)) (for-each (fn (ident) (call/cc (fn (__continue__) body))) items))))`. For `of` it normalizes via `js-iterable-to-list` (list → self, string → char list, dict → values). For `in` it iterates over `js-object-keys`. `break` / `continue` already route to the call/cc bindings. 8 new tests (flat, fill variants, indexOf with start, for-of array/string, for-in dict), **399/401** (391→+8). Conformance unchanged. Gotcha: **SX `cond` clauses evaluate only the last expr of a body. `(cond ((test) (set! a 1) (set! b 2)) …)` silently drops the first set!.** Must wrap multi-stmt clause bodies in `(begin …)`. First pass on the for-stmt rewrite had multi-expr cond clauses that silently did nothing — broke all existing for-loop tests, not just the new ones.
|
- 2026-04-23 — **Array.prototype flat/fill; indexOf start arg; for..of/for..in.** Array: `flat(depth=1)` uses `js-list-flat-loop` (recursive flatten), `fill(value, start?, end?)` mutates in-place then returns self via `js-list-fill-loop`. Fixed `indexOf` to honor the `fromIndex` second argument. Parser: `jp-parse-for-stmt` now does a 2-token lookahead — if it sees `(var? ident (of|in) expr)` it emits `(js-for-of-in kind ident iter body)`, else falls back to classic `for(;;)`. Transpile: `js-transpile-for-of-in` wraps body in `(call/cc (fn (__break__) (let ((__js_items__ <normalized-source>)) (for-each (fn (ident) (call/cc (fn (__continue__) body))) items))))`. For `of` it normalizes via `js-iterable-to-list` (list → self, string → char list, dict → values). For `in` it iterates over `js-object-keys`. `break` / `continue` already route to the call/cc bindings. 8 new tests (flat, fill variants, indexOf with start, for-of array/string, for-in dict), **399/401** (391→+8). Conformance unchanged. Gotcha: **SX `cond` clauses evaluate only the last expr of a body. `(cond ((test) (set! a 1) (set! b 2)) …)` silently drops the first set!.** Must wrap multi-stmt clause bodies in `(begin …)`. First pass on the for-stmt rewrite had multi-expr cond clauses that silently did nothing — broke all existing for-loop tests, not just the new ones.
|
||||||
|
|
||||||
|
- 2026-04-23 — **String.replace/search/match + Array.from; js-num-to-int coerces strings; spread operator `...`.** Strings: `replace(regex|str, repl)`, `search(regex|str)`, `match(regex|str)` all handle both regex and plain-string args. Regex path walks via `js-string-index-of` with case-adjusted hay/needle. Array.from(iterable, mapFn?) via `js-iterable-to-list`. `js-num-to-int` now routes through `js-to-number` so `'abcd'.charAt('2')` and `.slice('1','3')` coerce properly. **Spread `...` in array literals and call args.** Parser: `jp-array-loop` and `jp-call-args-loop` detect `punct "..."` and emit `(js-spread inner)` entries. Transpile: if any element has a spread, the array/args list is built via `(js-array-spread-build (list "js-value" v) (list "js-spread" xs) ...)`. Runtime `js-array-spread-build` walks items, appending values directly and splicing spread via `js-iterable-to-list`. Works in call args (including variadic `Math.max(...arr)`) and in array literals (prefix, middle, and string-spread `[...'abc']`). Gotcha: early pass used `(js-sym ":js-spread")` thinking it'd make a keyword — `js-sym` makes a SYMBOL which becomes an env lookup (and fails as undefined). Use plain STRING `"js-spread"` as the discriminator. 10 new tests (replace/search/match both arg types, Array.from, coercion, 5 spread variants), **414/416** (399→+15). Conformance unchanged.
|
||||||
|
|
||||||
## Phase 3-5 gotchas
|
## Phase 3-5 gotchas
|
||||||
|
|
||||||
Worth remembering for later phases:
|
Worth remembering for later phases:
|
||||||
|
|||||||
Reference in New Issue
Block a user