From 0d99b5dfe889cdcec6f84f064a50c7cf50e68f6c Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 18:16:32 +0000 Subject: [PATCH] js-on-sx: object computed keys + insertion-order tracking --- lib/js/parser.sx | 10 +++ lib/js/runtime.sx | 186 ++++++++++++++++++++++++++++++++++++++++---- lib/js/transpile.sx | 13 ++-- plans/js-on-sx.md | 2 + 4 files changed, 192 insertions(+), 19 deletions(-) diff --git a/lib/js/parser.sx b/lib/js/parser.sx index 4e88f2d4..73cfb2a4 100644 --- a/lib/js/parser.sx +++ b/lib/js/parser.sx @@ -584,6 +584,16 @@ (jp-advance! st) (jp-expect! st "punct" ":") (append! kvs {:value (jp-parse-assignment st) :key (get t :value)}))) + ((and (= (get t :type) "punct") (= (get t :value) "[")) + (do + (jp-advance! st) + (let + ((key-expr (jp-parse-assignment st))) + (jp-expect! st "punct" "]") + (jp-expect! st "punct" ":") + (append! + kvs + {:value (jp-parse-assignment st) :computed-key key-expr :key ""})))) (else (error (str "Unexpected in object: " (get t :type)))))))) (define diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 511e6a8e..c0ea75b2 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -1293,6 +1293,16 @@ (define js-args (fn (&rest args) args)) +(define + js-make-list + (fn + (&rest args) + (let + ((r (list))) + (begin + (for-each (fn (x) (append! r x)) args) + r)))) + (define js-trim (fn (s) (js-trim-left (js-trim-right s)))) (define @@ -2993,6 +3003,46 @@ (define dict-has? (fn (d k) (contains? (keys d) k))) +(define + js-make-obj + (fn () (let ((d (dict))) (begin (dict-set! d "__js_order__" (list)) d)))) + +(define + js-obj-order-add! + (fn + (obj k) + (cond + ((not (dict? obj)) nil) + ((not (contains? (keys obj) "__js_order__")) nil) + (else + (let + ((order (get obj "__js_order__"))) + (if (contains? order k) nil (append! order k))))))) + +(define + js-obj-order-remove! + (fn + (obj k) + (cond + ((not (dict? obj)) nil) + ((not (contains? (keys obj) "__js_order__")) nil) + (else + (dict-set! + obj + "__js_order__" + (filter (fn (x) (not (= x k))) (get obj "__js_order__"))))))) + +(define + js-obj-set! + (fn + (obj key val) + (let + ((sk (js-to-string key))) + (begin + (if (not (contains? (keys obj) sk)) (js-obj-order-add! obj sk) nil) + (dict-set! obj sk val) + val)))) + (begin (define js-set-prop @@ -3001,7 +3051,12 @@ (cond ((js-undefined? obj) (error "js-set-prop: cannot set on undefined")) ((= (type-of obj) "dict") - (do (dict-set! obj (js-to-string key) val) val)) + (let + ((sk (js-to-string key))) + (begin + (if (not (contains? (keys obj) sk)) (js-obj-order-add! obj sk) nil) + (dict-set! obj sk val) + val))) ((= (type-of obj) "list") (do (js-list-set! obj key val) val)) (else val)))) (define @@ -3428,16 +3483,35 @@ (append! acc (char-at s i)) (js-string-to-list s (+ i 1) acc))))) +(define + js-key-internal? + (fn + (k) + (or (= k "__js_order__") (= k "__proto__")))) + (define js-object-keys (fn (o) (cond ((dict? o) - (let - ((result (list))) - (for-each (fn (k) (append! result k)) (keys o)) - result)) + (cond + ((contains? (keys o) "__js_order__") + (let + ((result (list))) + (begin + (for-each + (fn (k) (if (js-key-internal? k) nil (append! result k))) + (get o "__js_order__")) + result))) + (else + (let + ((result (list))) + (begin + (for-each + (fn (k) (if (js-key-internal? k) nil (append! result k))) + (keys o)) + result))))) (else (list))))) (define @@ -3563,9 +3637,85 @@ (cond ((list? o) (let ((r (list))) (begin (js-list-keys-loop o 0 r) r))) - ((dict? o) (js-object-keys o)) + ((dict? o) (js-own-property-names-ordered o)) (else (list))))) +(define + js-own-property-names-ordered + (fn + (o) + (let + ((all (js-object-keys o)) + (ints (list)) + (rest (list))) + (begin + (for-each + (fn + (k) + (if (js-int-key? k) (append! ints k) (append! rest k))) + all) + (append (js-sort-int-keys ints) rest))))) + +(define + js-int-key? + (fn + (k) + (cond + ((not (= (type-of k) "string")) false) + ((= (len k) 0) false) + (else (js-int-key-loop? k 0 (len k)))))) + +(define + js-int-key-loop? + (fn + (s i n) + (cond + ((>= i n) true) + ((let ((c (char-code-at s i))) (and (>= c 48) (<= c 57))) + (js-int-key-loop? s (+ i 1) n)) + (else false)))) + +(define + js-sort-int-keys + (fn + (lst) + (let + ((nums (map js-string-to-number lst))) + (begin + (js-sort-numbers! nums) + (map (fn (n) (str (js-num-to-int n))) nums))))) + +(define + js-sort-numbers! + (fn + (lst) + (let ((n (len lst))) + (js-bubble-sort! lst 0 n)))) + +(define + js-bubble-sort! + (fn + (lst i n) + (cond + ((>= i n) nil) + (else + (begin (js-bubble-sort-inner! lst 0 (- n i 1)) (js-bubble-sort! lst (+ i 1) n)))))) + +(define + js-bubble-sort-inner! + (fn + (lst j stop) + (cond + ((>= j stop) nil) + ((> (nth lst j) (nth lst (+ j 1))) + (let + ((a (nth lst j)) (b (nth lst (+ j 1)))) + (begin + (set-nth! lst j b) + (set-nth! lst (+ j 1) a) + (js-bubble-sort-inner! lst (+ j 1) stop)))) + (else (js-bubble-sort-inner! lst (+ j 1) stop))))) + (define js-object-get-own-property-descriptor (fn @@ -3656,7 +3806,12 @@ (obj key) (cond ((dict? obj) - (begin (dict-delete! obj (js-to-string key)) true)) + (let + ((sk (js-to-string key))) + (begin + (js-obj-order-remove! obj sk) + (dict-delete! obj sk) + true))) (else true)))) (define @@ -4135,14 +4290,17 @@ (for-each (fn (k) - (let - ((val (get v k))) + (if + (js-key-internal? k) + nil (let - ((vs (js-json-stringify-value val))) - (if - (not (js-undefined? vs)) - (append! parts (str (js-json-escape-string k) ":" vs)))))) - (keys v)) + ((val (get v k))) + (let + ((vs (js-json-stringify-value val))) + (if + (not (js-undefined? vs)) + (append! parts (str (js-json-escape-string k) ":" vs))))))) + (js-object-keys v)) (str "{" (join "," parts) "}"))) (else "null")))) diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index 8503129a..ba45d162 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -391,7 +391,7 @@ (list (js-sym "js-new-call") (js-transpile callee) - (cons (js-sym "list") (map js-transpile args))))) + (cons (js-sym "js-args") (map js-transpile args))))) (define js-transpile-array @@ -409,7 +409,7 @@ (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))))) + (cons (js-sym "js-make-list") (map js-transpile elts))))) (define js-has-spread? @@ -449,7 +449,7 @@ (entries) (list (js-sym "let") - (list (list (js-sym "_obj") (list (js-sym "dict")))) + (list (list (js-sym "_obj") (list (js-sym "js-make-obj")))) (cons (js-sym "begin") (append @@ -457,9 +457,12 @@ (fn (entry) (list - (js-sym "dict-set!") + (js-sym "js-obj-set!") (js-sym "_obj") - (get entry :key) + (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 7c1d05f2..f13f0ced 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 — **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. + - 2026-05-08 — **Bitwise ops `& | ^ << >>` (+ compound assigns) now transpile and evaluate.** Previously the transpiler raised `unsupported op: &/>>/<<` for any source using them, and the punctuator suite (0/11) plus a wider scatter of Number/expression tests bombed on first reference. Added pure-SX runtime helpers: `js-to-uint32` / `js-to-int32` / `js-uint32-to-int32` for ToUint32/ToInt32 coercion; `js-bitwise-loop` that walks all 32 bit positions emitting `and`/`or`/`xor` (no native bit primitive available); `js-bitand` / `js-bitor` / `js-bitxor` and `js-shl` / `js-shr` (shr uses `floor(ai / 2^sh)` which is correct for signed values). Wired `<<`, `>>`, `&`, `|`, `^` into `js-transpile-binop`, and the corresponding `<<=`, `>>=`, `>>>=`, `&=`, `|=`, `^=` into `js-compound-update`. Lexer + parser already produced the tokens with correct precedence. language/punctuators: 0/11 → 1/11 (the remaining 10 are negative tests for `\u`-escaped punctuator rejection). Also unblocks the 8x `&`, 2x `>>`, 1x `<<` "unsupported op" failures from the prior broad sweep. conformance.sh: 148/148. - 2026-05-08 — **`Function(arg1, arg2, ..., body)` constructor compiles + evaluates JS source.** Was unconditionally throwing `"TypeError: Function constructor not supported"`. Now `js-function-ctor` joins the param strings with commas, wraps the body in `(function(){})`, and runs it through `js-eval`. Side helpers (`js-fn-args-to-strs`, `js-fn-take-init`, `js-fn-take-last`, `js-fn-join-commas`) keep the implementation self-contained and use existing primitives. Now `Function('a', 'b', 'return a + b')(3,4) === 7`. built-ins/Function: 0/14 → 4/14. conformance.sh: 148/148.