diff --git a/lib/js/parser.sx b/lib/js/parser.sx index d22f8214..481af105 100644 --- a/lib/js/parser.sx +++ b/lib/js/parser.sx @@ -620,6 +620,10 @@ (append! kvs {:value (jp-parse-assignment st) :computed-key key-expr :key ""})))) + ((and (= (get t :type) "punct") (= (get t :value) "...")) + (do + (jp-advance! st) + (append! kvs {:spread (jp-parse-assignment st)}))) (else (error (str "Unexpected in object: " (get t :type)))))))) (define diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 737d8e3f..cfcdf47d 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -3477,6 +3477,55 @@ (dict-set! obj sk val) val)))) +(define + js-obj-spread! + (fn + (target src) + (cond + ((or (= src nil) (js-undefined? src)) target) + ((dict? src) + (begin + (for-each + (fn + (k) + (if + (js-key-internal? k) + nil + (js-obj-set! target k (get src k)))) + (js-object-keys src)) + target)) + ((= (type-of src) "string") + (let + ((n (len src))) + (begin (js-obj-spread-string-loop! target src 0 n) target))) + ((list? src) + (let + ((n (len src))) + (begin (js-obj-spread-list-loop! target src 0 n) target))) + (else target)))) + +(define + js-obj-spread-string-loop! + (fn + (target s i n) + (cond + ((>= i n) nil) + (else + (begin + (js-obj-set! target (js-to-string i) (char-at s i)) + (js-obj-spread-string-loop! target s (+ i 1) n)))))) + +(define + js-obj-spread-list-loop! + (fn + (target arr i n) + (cond + ((>= i n) nil) + (else + (begin + (js-obj-set! target (js-to-string i) (nth arr i)) + (js-obj-spread-list-loop! target arr (+ i 1) n)))))) + (begin (define js-set-prop diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index d9ccfb3d..5eb29ec2 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -482,14 +482,21 @@ (map (fn (entry) - (list - (js-sym "js-obj-set!") - (js-sym "_obj") - (if - (contains? (keys entry) :computed-key) - (list (js-sym "js-to-string") (js-transpile (get entry :computed-key))) - (get entry :key)) - (js-transpile (get entry :value)))) + (cond + ((contains? (keys entry) :spread) + (list + (js-sym "js-obj-spread!") + (js-sym "_obj") + (js-transpile (get entry :spread)))) + (else + (list + (js-sym "js-obj-set!") + (js-sym "_obj") + (if + (contains? (keys entry) :computed-key) + (list (js-sym "js-to-string") (js-transpile (get entry :computed-key))) + (get entry :key)) + (js-transpile (get entry :value)))))) entries) (list (js-sym "_obj"))))))) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 7dc07ef4..f76da205 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. +- 2026-05-09 — **Object literal spread `{...src}` parses + executes.** Per ES spec, object literals can include `...expr` to copy own enumerable properties from a source. `jp-parse-object-entry` was rejecting the leading `...` punct. Added a parser branch that records the AST under `:spread`. `js-transpile-object` emits `(js-obj-spread! _obj )` for spread entries, alongside the existing `(js-obj-set! _obj k v)` for regular entries. New `js-obj-spread!` runtime helper: dict source copies own enumerable keys (skipping internal `__js_order__` / `__proto__`); string source copies each character at its numeric index; list source copies elements at their numeric index; null/undefined no-op. Result: language/expressions/array 5/30 → 13/30 (+8). Object 30/30 holds. conformance.sh: 148/148. + - 2026-05-09 — **`Object.getOwnPropertyNames` throws on null/undefined and includes `"length"` for strings/arrays.** Was returning `(list)` for non-list/non-dict inputs; per spec it ToObject's the argument and returns own keys including the implicit `"length"` property for strings/arrays. Added explicit branches: null/undefined → TypeError, string → `["0","1",…,"n-1","length"]` via `js-string-keys-loop` then append, list → indices + `"length"`, dict → existing ordered path. Result: built-ins/Object/getOwnPropertyNames 19/30 → 20/30. Object 30/30 holds. conformance.sh: 148/148. - 2026-05-09 — **`Object.values`/`entries` throw on null/undefined and walk strings.** Same shape as the previous `Object.keys` fix. Both methods returned `(list)` for non-dict input; per spec they ToObject the argument and yield the property values / `[k, v]` pairs. Added explicit branches: null/undefined → TypeError, string → walk character indices, dict → iterate own enumerable keys (skipping internal `__js_order__` / `__proto__`). Result: built-ins/Object/values 5/16 → 8/16, entries 5/17 → 9/17. Object 30/30 holds. conformance.sh: 148/148.