js-on-sx: object + array destructuring
Parser: jp-parse-vardecl handles {a, b} obj pattern and [a, , c]
arr pattern (with hole support) in addition to plain idents.
Emits (js-vardecl-obj names rhs) and (js-vardecl-arr names rhs).
Transpile: js-vardecl-forms dispatches on tag. Destructures emit
(define __destruct__ rhs) then (define name (js-get-prop __destruct__
key-or-index)) for each pattern element. Array holes (nil) are skipped.
418/420 unit (+4), 148/148 slice unchanged.
This commit is contained in:
108
lib/js/parser.sx
108
lib/js/parser.sx
@@ -781,20 +781,102 @@
|
||||
jp-parse-vardecl
|
||||
(fn
|
||||
(st)
|
||||
(let
|
||||
((nm (get (jp-peek st) :value)))
|
||||
(do
|
||||
(if
|
||||
(= (get (jp-peek st) :type) "ident")
|
||||
(jp-advance! st)
|
||||
(error
|
||||
(str "Expected ident in var decl, got " (get (jp-peek st) :type))))
|
||||
(if
|
||||
(jp-at? st "op" "=")
|
||||
(do
|
||||
(cond
|
||||
((jp-at? st "punct" "{")
|
||||
(let
|
||||
((pattern (jp-parse-obj-pattern st)))
|
||||
(jp-expect! st "op" "=")
|
||||
(list (quote js-vardecl-obj) pattern (jp-parse-assignment st))))
|
||||
((jp-at? st "punct" "[")
|
||||
(let
|
||||
((pattern (jp-parse-arr-pattern st)))
|
||||
(jp-expect! st "op" "=")
|
||||
(list (quote js-vardecl-arr) pattern (jp-parse-assignment st))))
|
||||
(else
|
||||
(let
|
||||
((nm (get (jp-peek st) :value)))
|
||||
(if
|
||||
(= (get (jp-peek st) :type) "ident")
|
||||
(jp-advance! st)
|
||||
(list (quote js-vardecl) nm (jp-parse-assignment st)))
|
||||
(list (quote js-vardecl) nm (list (quote js-undef))))))))
|
||||
(error
|
||||
(str
|
||||
"Expected ident in var decl, got "
|
||||
(get (jp-peek st) :type))))
|
||||
(if
|
||||
(jp-at? st "op" "=")
|
||||
(begin
|
||||
(jp-advance! st)
|
||||
(list (quote js-vardecl) nm (jp-parse-assignment st)))
|
||||
(list (quote js-vardecl) nm (list (quote js-undef)))))))))
|
||||
|
||||
(define
|
||||
jp-parse-obj-pattern
|
||||
(fn
|
||||
(st)
|
||||
(jp-advance! st)
|
||||
(let
|
||||
((names (list)))
|
||||
(jp-parse-obj-pattern-loop st names)
|
||||
(jp-expect! st "punct" "}")
|
||||
names)))
|
||||
|
||||
(define
|
||||
jp-parse-obj-pattern-loop
|
||||
(fn
|
||||
(st names)
|
||||
(cond
|
||||
((jp-at? st "punct" "}") nil)
|
||||
(else
|
||||
(begin
|
||||
(let
|
||||
((nm (get (jp-peek st) :value)))
|
||||
(if
|
||||
(or
|
||||
(= (get (jp-peek st) :type) "ident")
|
||||
(= (get (jp-peek st) :type) "string"))
|
||||
(jp-advance! st)
|
||||
(error "expected ident in obj pattern"))
|
||||
(append! names nm))
|
||||
(cond
|
||||
((jp-at? st "punct" ",")
|
||||
(begin (jp-advance! st) (jp-parse-obj-pattern-loop st names)))
|
||||
(else nil)))))))
|
||||
|
||||
(define
|
||||
jp-parse-arr-pattern
|
||||
(fn
|
||||
(st)
|
||||
(jp-advance! st)
|
||||
(let
|
||||
((names (list)))
|
||||
(jp-parse-arr-pattern-loop st names)
|
||||
(jp-expect! st "punct" "]")
|
||||
names)))
|
||||
|
||||
(define
|
||||
jp-parse-arr-pattern-loop
|
||||
(fn
|
||||
(st names)
|
||||
(cond
|
||||
((jp-at? st "punct" "]") nil)
|
||||
((jp-at? st "punct" ",")
|
||||
(begin
|
||||
(append! names nil)
|
||||
(jp-advance! st)
|
||||
(jp-parse-arr-pattern-loop st names)))
|
||||
(else
|
||||
(begin
|
||||
(let
|
||||
((nm (get (jp-peek st) :value)))
|
||||
(if
|
||||
(= (get (jp-peek st) :type) "ident")
|
||||
(jp-advance! st)
|
||||
(error "expected ident in arr pattern"))
|
||||
(append! names nm))
|
||||
(cond
|
||||
((jp-at? st "punct" ",")
|
||||
(begin (jp-advance! st) (jp-parse-arr-pattern-loop st names)))
|
||||
(else nil)))))))
|
||||
|
||||
(define
|
||||
jp-parse-var-stmt
|
||||
|
||||
@@ -1071,6 +1071,16 @@ cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 2504)
|
||||
(eval "(js-eval \"var a=[...'abc']; a.length\")")
|
||||
|
||||
;; ── Phase 11.destruct: object and array destructuring ──────────
|
||||
(epoch 2600)
|
||||
(eval "(js-eval \"var {aa, bb} = {aa:1, bb:2}; aa+bb\")")
|
||||
(epoch 2601)
|
||||
(eval "(js-eval \"var [ax, bx, cx] = [1, 2, 3]; ax+bx+cx\")")
|
||||
(epoch 2602)
|
||||
(eval "(js-eval \"var [fst, , trd] = [1, 2, 3]; fst+trd\")")
|
||||
(epoch 2603)
|
||||
(eval "(js-eval \"var pt = {px: 100}; var {px} = pt; px + 1\")")
|
||||
|
||||
EPOCHS
|
||||
|
||||
|
||||
@@ -1648,6 +1658,12 @@ check 2502 "spread in call args" '6'
|
||||
check 2503 "spread Math.max" '5'
|
||||
check 2504 "spread string" '3'
|
||||
|
||||
# ── Phase 11.destruct ─────────────────────────────────────────
|
||||
check 2600 "obj destructure" '3'
|
||||
check 2601 "arr destructure" '6'
|
||||
check 2602 "arr destructure skip" '4'
|
||||
check 2603 "obj partial+add" '101'
|
||||
|
||||
TOTAL=$((PASS + FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "✓ $PASS/$TOTAL JS-on-SX tests passed"
|
||||
|
||||
@@ -845,12 +845,68 @@
|
||||
(else
|
||||
(let
|
||||
((d (first decls)))
|
||||
(cons
|
||||
(list
|
||||
(js-sym "define")
|
||||
(js-sym (nth d 1))
|
||||
(js-transpile (nth d 2)))
|
||||
(js-vardecl-forms (rest decls))))))))
|
||||
(cond
|
||||
((js-tag? d "js-vardecl")
|
||||
(cons
|
||||
(list
|
||||
(js-sym "define")
|
||||
(js-sym (nth d 1))
|
||||
(js-transpile (nth d 2)))
|
||||
(js-vardecl-forms (rest decls))))
|
||||
((js-tag? d "js-vardecl-obj")
|
||||
(let
|
||||
((names (nth d 1))
|
||||
(rhs (js-transpile (nth d 2)))
|
||||
(tmp-sym (js-sym "__destruct__")))
|
||||
(cons
|
||||
(list (js-sym "define") tmp-sym rhs)
|
||||
(js-vardecl-obj-forms
|
||||
names
|
||||
tmp-sym
|
||||
(js-vardecl-forms (rest decls))))))
|
||||
((js-tag? d "js-vardecl-arr")
|
||||
(let
|
||||
((names (nth d 1))
|
||||
(rhs (js-transpile (nth d 2)))
|
||||
(tmp-sym (js-sym "__destruct__")))
|
||||
(cons
|
||||
(list (js-sym "define") tmp-sym rhs)
|
||||
(js-vardecl-arr-forms
|
||||
names
|
||||
tmp-sym
|
||||
0
|
||||
(js-vardecl-forms (rest decls))))))
|
||||
(else (error "js-vardecl-forms: unexpected decl"))))))))
|
||||
|
||||
(define
|
||||
js-vardecl-obj-forms
|
||||
(fn
|
||||
(names tmp-sym tail)
|
||||
(cond
|
||||
((empty? names) tail)
|
||||
(else
|
||||
(cons
|
||||
(list
|
||||
(js-sym "define")
|
||||
(js-sym (first names))
|
||||
(list (js-sym "js-get-prop") tmp-sym (first names)))
|
||||
(js-vardecl-obj-forms (rest names) tmp-sym tail))))))
|
||||
|
||||
(define
|
||||
js-vardecl-arr-forms
|
||||
(fn
|
||||
(names tmp-sym i tail)
|
||||
(cond
|
||||
((empty? names) tail)
|
||||
((= (first names) nil)
|
||||
(js-vardecl-arr-forms (rest names) tmp-sym (+ i 1) tail))
|
||||
(else
|
||||
(cons
|
||||
(list
|
||||
(js-sym "define")
|
||||
(js-sym (first names))
|
||||
(list (js-sym "js-get-prop") tmp-sym i))
|
||||
(js-vardecl-arr-forms (rest names) tmp-sym (+ i 1) tail))))))
|
||||
|
||||
(define
|
||||
js-transpile-if-stmt
|
||||
|
||||
@@ -193,6 +193,8 @@ Append-only record of completed iterations. Loop writes one line per iteration:
|
||||
|
||||
- 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.
|
||||
|
||||
- 2026-04-23 — **Object + array destructuring in var/let/const decls.** Parser: `jp-parse-vardecl` now handles three shapes — plain `ident`, `{a, b, c}` object pattern, `[a, , c]` array pattern with hole support. `jp-parse-obj-pattern` / `jp-parse-arr-pattern` / their loops collect the names. AST: `(js-vardecl-obj (names...) rhs)` and `(js-vardecl-arr (names-with-holes-as-nil...) rhs)`. Transpile: `js-vardecl-forms` dispatches on the three tags. Destructures emit `(define __destruct__ rhs)` then `(define name (js-get-prop __destruct__ key-or-index))` for each pattern element (skips nil holes in array patterns). 4 new unit tests. 418/420 (414→+4). Conformance unchanged. Gotcha: running destructuring tests sequentially — if epoch N defines `a, b` globally and epoch N+1 uses the same names as different types, "Not callable: N" results. Top-level `var` transpiles to `(define name value)`; re-defining a name that's held a value in a prior call to eval carries over. The proper fix would be to use `let` block-scoping; workaround for tests is unique names.
|
||||
|
||||
## Phase 3-5 gotchas
|
||||
|
||||
Worth remembering for later phases:
|
||||
|
||||
Reference in New Issue
Block a user