diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index c501f3f8..c8f8aec6 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -5089,21 +5089,364 @@ (str "/" (get rx "source") "/" (get rx "flags"))) (else js-undefined)))) +(define + js-list-find-index + (fn + (lst v i n) + (cond + ((>= i n) -1) + ((= (nth lst i) v) i) + (else (js-list-find-index lst v (+ i 1) n))))) + +(define + js-list-remove-at! + (fn + (lst i) + (let + ((n (len lst)) (kept (list))) + (begin + (js-list-remove-at-loop lst i n 0 kept) + kept)))) + +(define + js-list-remove-at-loop + (fn + (src skip n j out) + (cond + ((>= j n) nil) + ((= j skip) (js-list-remove-at-loop src skip n (+ j 1) out)) + (else + (begin + (append! out (nth src j)) + (js-list-remove-at-loop src skip n (+ j 1) out)))))) + +(define + js-map-ctor-fn + (fn + (&rest args) + (let + ((this (js-this))) + (cond + ((not (= (type-of this) "dict")) + (raise (js-new-call TypeError (js-args "Map must be constructed with new")))) + (else + (begin + (dict-set! this "__map_keys__" (list)) + (dict-set! this "__map_vals__" (list)) + (dict-set! this "size" 0) + (if + (and + (>= (len args) 1) + (not (js-undefined? (nth args 0))) + (not (= (nth args 0) nil))) + (js-map-init this (nth args 0)) + nil) + this)))))) + +(define + js-map-init + (fn + (m iter) + (let + ((entries (js-iterable-to-list iter))) + (for-each + (fn + (entry) + (cond + ((list? entry) + (js-map-do-set m (nth entry 0) (nth entry 1))) + (else nil))) + entries)))) + +(define + js-map-do-set + (fn + (m k v) + (let + ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (let + ((idx (js-list-find-index ks k 0 (len ks)))) + (cond + ((>= idx 0) (begin (set-nth! vs idx v) m)) + (else + (begin + (append! ks k) + (append! vs v) + (dict-set! m "size" (len ks)) + m))))))) + +(define + js-map-do-get + (fn + (m k) + (let + ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (let + ((idx (js-list-find-index ks k 0 (len ks)))) + (cond ((>= idx 0) (nth vs idx)) (else js-undefined)))))) + +(define + js-map-do-has + (fn + (m k) + (let + ((ks (get m "__map_keys__"))) + (>= (js-list-find-index ks k 0 (len ks)) 0)))) + +(define + js-map-do-delete + (fn + (m k) + (let + ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (let + ((idx (js-list-find-index ks k 0 (len ks)))) + (cond + ((< idx 0) false) + (else + (let + ((new-ks (js-list-remove-at! ks idx)) + (new-vs (js-list-remove-at! vs idx))) + (begin + (dict-set! m "__map_keys__" new-ks) + (dict-set! m "__map_vals__" new-vs) + (dict-set! m "size" (len new-ks)) + true)))))))) + +(define + js-map-do-clear + (fn + (m) + (begin + (dict-set! m "__map_keys__" (list)) + (dict-set! m "__map_vals__" (list)) + (dict-set! m "size" 0) + js-undefined))) + +(define + js-map-do-foreach + (fn + (m cb) + (let + ((ks (get m "__map_keys__")) (vs (get m "__map_vals__"))) + (begin + (js-map-foreach-loop ks vs cb 0 (len ks)) + js-undefined)))) + +(define + js-map-foreach-loop + (fn + (ks vs cb i n) + (cond + ((>= i n) nil) + (else + (begin + (js-call-with-this js-undefined cb (list (nth vs i) (nth ks i))) + (js-map-foreach-loop ks vs cb (+ i 1) n)))))) + +(define + Map + {:length 0 + :name "Map" + :__callable__ js-map-ctor-fn + :prototype + {:get (fn (k) (js-map-do-get (js-this) k)) + :set (fn (k v) (js-map-do-set (js-this) k v)) + :has (fn (k) (js-map-do-has (js-this) k)) + :delete (fn (k) (js-map-do-delete (js-this) k)) + :clear (fn () (js-map-do-clear (js-this))) + :forEach (fn (cb) (js-map-do-foreach (js-this) cb)) + :keys (fn () (let ((ks (get (js-this) "__map_keys__"))) (js-list-copy ks))) + :values (fn () (let ((vs (get (js-this) "__map_vals__"))) (js-list-copy vs))) + :entries + (fn () + (let + ((ks (get (js-this) "__map_keys__")) + (vs (get (js-this) "__map_vals__")) + (out (list))) + (begin + (js-map-entries-loop ks vs 0 (len ks) out) + out)))}}) + +(dict-set! (get Map "prototype") "constructor" Map) + +(define + js-list-copy + (fn + (src) + (let + ((out (list))) + (begin (for-each (fn (x) (append! out x)) src) out)))) + +(define + js-map-entries-loop + (fn + (ks vs i n out) + (cond + ((>= i n) nil) + (else + (begin + (append! out (list (nth ks i) (nth vs i))) + (js-map-entries-loop ks vs (+ i 1) n out)))))) + +(define + js-set-ctor-fn + (fn + (&rest args) + (let + ((this (js-this))) + (cond + ((not (= (type-of this) "dict")) + (raise (js-new-call TypeError (js-args "Set must be constructed with new")))) + (else + (begin + (dict-set! this "__set_items__" (list)) + (dict-set! this "size" 0) + (if + (and + (>= (len args) 1) + (not (js-undefined? (nth args 0))) + (not (= (nth args 0) nil))) + (js-set-init this (nth args 0)) + nil) + this)))))) + +(define + js-set-init + (fn + (s iter) + (let + ((items (js-iterable-to-list iter))) + (for-each (fn (x) (js-set-do-add s x)) items)))) + +(define + js-set-do-add + (fn + (s v) + (let + ((items (get s "__set_items__"))) + (let + ((idx (js-list-find-index items v 0 (len items)))) + (cond + ((>= idx 0) s) + (else + (begin + (append! items v) + (dict-set! s "size" (len items)) + s))))))) + +(define + js-set-do-has + (fn + (s v) + (let + ((items (get s "__set_items__"))) + (>= (js-list-find-index items v 0 (len items)) 0)))) + +(define + js-set-do-delete + (fn + (s v) + (let + ((items (get s "__set_items__"))) + (let + ((idx (js-list-find-index items v 0 (len items)))) + (cond + ((< idx 0) false) + (else + (let + ((new-items (js-list-remove-at! items idx))) + (begin + (dict-set! s "__set_items__" new-items) + (dict-set! s "size" (len new-items)) + true)))))))) + +(define + js-set-do-clear + (fn + (s) + (begin + (dict-set! s "__set_items__" (list)) + (dict-set! s "size" 0) + js-undefined))) + +(define + js-set-do-foreach + (fn + (s cb) + (let + ((items (get s "__set_items__"))) + (begin + (js-set-foreach-loop items cb 0 (len items)) + js-undefined)))) + +(define + js-set-foreach-loop + (fn + (items cb i n) + (cond + ((>= i n) nil) + (else + (begin + (js-call-with-this + js-undefined + cb + (list (nth items i) (nth items i))) + (js-set-foreach-loop items cb (+ i 1) n)))))) + +(define + Set + {:length 0 + :name "Set" + :__callable__ js-set-ctor-fn + :prototype + {:add (fn (v) (js-set-do-add (js-this) v)) + :has (fn (v) (js-set-do-has (js-this) v)) + :delete (fn (v) (js-set-do-delete (js-this) v)) + :clear (fn () (js-set-do-clear (js-this))) + :forEach (fn (cb) (js-set-do-foreach (js-this) cb)) + :keys (fn () (js-list-copy (get (js-this) "__set_items__"))) + :values (fn () (js-list-copy (get (js-this) "__set_items__"))) + :entries + (fn () + (let + ((items (get (js-this) "__set_items__")) (out (list))) + (begin + (js-set-entries-loop items 0 (len items) out) + out)))}}) + +(dict-set! (get Set "prototype") "constructor" Set) + +(define + js-set-entries-loop + (fn + (items i n out) + (cond + ((>= i n) nil) + (else + (begin + (append! out (list (nth items i) (nth items i))) + (js-set-entries-loop items (+ i 1) n out)))))) + (begin (dict-set! Object "__proto__" (get js-function-global "prototype")) (dict-set! Array "__proto__" (get js-function-global "prototype")) (dict-set! Number "__proto__" (get js-function-global "prototype")) (dict-set! String "__proto__" (get js-function-global "prototype")) (dict-set! Boolean "__proto__" (get js-function-global "prototype")) + (dict-set! Map "__proto__" (get js-function-global "prototype")) + (dict-set! Set "__proto__" (get js-function-global "prototype")) (dict-set! (get Array "prototype") "__proto__" (get Object "prototype")) (dict-set! (get Number "prototype") "__proto__" (get Object "prototype")) (dict-set! (get String "prototype") "__proto__" (get Object "prototype")) (dict-set! (get Boolean "prototype") "__proto__" (get Object "prototype")) + (dict-set! (get Map "prototype") "__proto__" (get Object "prototype")) + (dict-set! (get Set "prototype") "__proto__" (get Object "prototype")) (dict-set! (get js-function-global "prototype") "__proto__" (get Object "prototype")) (dict-set! (get Number "prototype") "__js_number_value__" 0) (dict-set! (get String "prototype") "__js_string_value__" "") (dict-set! (get Boolean "prototype") "__js_boolean_value__" false)) -(define js-global {:undefined js-undefined :JSON JSON :parseInt parseInt :Object Object :isNaN js-global-is-nan :Infinity inf :NaN 0 :String String :Boolean Boolean :Array Array :Math Math :parseFloat parseFloat :Number Number :console console :isFinite js-global-is-finite}) +(define js-global {:undefined js-undefined :JSON JSON :parseInt parseInt :Object Object :isNaN js-global-is-nan :Infinity inf :NaN 0 :String String :Boolean Boolean :Array Array :Math Math :parseFloat parseFloat :Number Number :console console :isFinite js-global-is-finite :Map Map :Set Set}) (set! js-global-this js-global) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index e9e076d6..92dd7d81 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-08 — **`Map` and `Set` constructors with full instance API.** Both were undefined globally — every test in those categories died at `new Map()` / `new Set()` with ReferenceError. Implemented as plain SX storage on the instance dict (`__map_keys__` + `__map_vals__` parallel lists for Map, `__set_items__` for Set) using SX `=` for key/value comparisons. Wired prototype methods: `.get`, `.set`, `.has`, `.delete`, `.clear`, `.forEach`, `.keys`, `.values`, `.entries` for Map; `.add`, `.has`, `.delete`, `.clear`, `.forEach`, `.keys`, `.values`, `.entries` for Set. `.size` is a real own property updated on every mutation (no getters). Constructors use the dict-with-`__callable__` pattern (like `Object`) so `Map.length`, `Map.name`, `Map.prototype` work as regular dict reads. Constructor accepts an iterable of `[k,v]` pairs (Map) or values (Set). Added `Map`/`Set` to `js-global` and to the prototype-chain post-init block. Result: built-ins/Map 1/30 → 18/30 (60%), built-ins/Set 0/30 → 15/30 (50%, rest mostly timeouts on iterator-protocol tests). conformance.sh: 148/148. + - 2026-05-08 — **`decodeURI` / `decodeURIComponent` actually decode (and throw URIError on malformed input); harness `decimalToHexString` helper added.** Both were `(fn (v) (js-to-string v))` — passthrough stubs. Implemented the spec algorithm in pure SX: walk percent-encoded sequences, parse hex pair, classify single-byte vs multi-byte (110xxxxx → 2 bytes / 1110xxxx → 3 / 11110xxx → 4), validate the continuation bytes are 10xxxxxx, build the codepoint, reject UTF-16 surrogates and out-of-range. `decodeURI` keeps reserved bytes (`;/?:@&=+$,#`) as literal `%XX`. Malformed sequences throw `URIError` via existing constructor. Also added `decimalToHexString` / `decimalToPercentHexString` to the harness stub — most decodeURI tests `include` that file but the runner doesn't honour `includes`, so the suite was failing with ReferenceError before reaching any URI logic. Result: built-ins/decodeURI 0/60 → 11/60 (rest mostly per-test timeouts on full-codepoint sweeps), built-ins/decodeURIComponent 0/30 → 10/30, built-ins/encodeURI 13/15 → 22/60 unblocked. conformance.sh: 148/148. - 2026-05-08 — **Object literals: computed keys `[expr]: val`, insertion-order tracking, integer-key-first ordering for `getOwnPropertyNames`.** Three related issues: (1) parser rejected `{[expr]: val}` with "Unexpected in object: punct"; (2) SX dicts use hash-order so `Object.getOwnPropertyNames` returned keys in non-insertion order; (3) `var list = {...}` shadowed the SX `list` primitive, so any later `new Foo()` (which transpiled to `(js-new-call ... (list ...))`) crashed with "Not callable: ". Fixes: parser `jp-parse-object-entry` now accepts `[]:` and stores `:computed-key`; `js-transpile-object` emits `js-make-obj` (initializes `__js_order__` list) + `js-obj-set!` (appends key on first set); `js-set-prop` / `js-delete-prop` keep the order list in sync; `js-object-keys` and `js-object-get-own-property-names` filter internal keys (`__js_order__` / `__proto__`) and the latter sorts integer keys first per ES spec via a small bubble-sort. Replaced `(list ...)` emissions for `js-new-call` args and array literals with `(js-args ...)` and `(js-make-list ...)` (closure-captured) — the latter remains mutable. Fixes 0/2 → 2/2 on `language/computed-property-names/basics`, +3 on built-ins/Array (Array.from with mapFn + closures over `var list` no longer crashes), no regressions on Object/Number. conformance.sh: 148/148.