diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index c0ea75b2..c501f3f8 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -4201,11 +4201,157 @@ (v) (let ((s (js-to-string v))) (js-uri-encode-loop s 0 "")))) -(define decodeURIComponent (fn (v) (js-to-string v))) +(define + decodeURIComponent + (fn (v) (let ((s (js-to-string v))) (js-uri-decode s 0 "" false)))) (define encodeURI (fn (v) (js-to-string v))) -(define decodeURI (fn (v) (js-to-string v))) +(define + decodeURI + (fn (v) (let ((s (js-to-string v))) (js-uri-decode s 0 "" true)))) + +(define + js-uri-malformed! + (fn () (raise (js-new-call URIError (js-args "URI malformed"))))) + +(define + js-uri-reserved-byte? + (fn + (b) + (or + (= b 35) + (= b 36) + (= b 38) + (= b 43) + (= b 44) + (= b 47) + (= b 58) + (= b 59) + (= b 61) + (= b 63) + (= b 64)))) + +(define + js-uri-hex-val + (fn + (c) + (let + ((cc (char-code c))) + (cond + ((and (>= cc 48) (<= cc 57)) (- cc 48)) + ((and (>= cc 65) (<= cc 70)) (- cc 55)) + ((and (>= cc 97) (<= cc 102)) (- cc 87)) + (else -1))))) + +(define + js-uri-hex-pair + (fn + (s i) + (cond + ((>= (+ i 1) (len s)) -1) + (else + (let + ((d1 (js-uri-hex-val (char-at s i))) + (d2 (js-uri-hex-val (char-at s (+ i 1))))) + (cond + ((or (= d1 -1) (= d2 -1)) -1) + (else (+ (* d1 16) d2)))))))) + +(define + js-uri-decode + (fn + (s i acc preserveReserved) + (cond + ((>= i (len s)) acc) + ((not (= (char-at s i) "%")) + (js-uri-decode + s + (+ i 1) + (str acc (char-at s i)) + preserveReserved)) + ((> (+ i 3) (len s)) (js-uri-malformed!)) + (else + (let + ((b (js-uri-hex-pair s (+ i 1)))) + (cond + ((= b -1) (js-uri-malformed!)) + ((< b 128) + (cond + ((and preserveReserved (js-uri-reserved-byte? b)) + (js-uri-decode + s + (+ i 3) + (str acc (char-at s i) (char-at s (+ i 1)) (char-at s (+ i 2))) + preserveReserved)) + (else + (js-uri-decode + s + (+ i 3) + (str acc (char-from-code b)) + preserveReserved)))) + (else (js-uri-decode-multi s i acc preserveReserved b)))))))) + +(define + js-uri-decode-multi + (fn + (s i acc preserveReserved b1) + (let + ((n + (cond + ((< b1 192) -1) + ((< b1 224) 2) + ((< b1 240) 3) + ((< b1 248) 4) + (else -1))) + (head-bits + (cond + ((< b1 192) 0) + ((< b1 224) (mod b1 32)) + ((< b1 240) (mod b1 16)) + ((< b1 248) (mod b1 8)) + (else 0)))) + (cond + ((= n -1) (js-uri-malformed!)) + (else + (js-uri-decode-multi-loop s i acc preserveReserved n 1 head-bits)))))) + +(define + js-uri-decode-multi-loop + (fn + (s i acc preserveReserved n k cp) + (cond + ((>= k n) + (cond + ((and (>= cp 55296) (<= cp 57343)) (js-uri-malformed!)) + ((> cp 1114111) (js-uri-malformed!)) + (else + (js-uri-decode + s + (+ i (* 3 n)) + (str acc (char-from-code cp)) + preserveReserved)))) + (else + (let + ((p (+ i (* 3 k)))) + (cond + ((>= (+ p 3) (+ (len s) 1)) (js-uri-malformed!)) + ((not (= (char-at s p) "%")) (js-uri-malformed!)) + (else + (let + ((b (js-uri-hex-pair s (+ p 1)))) + (cond + ((= b -1) (js-uri-malformed!)) + ((or (< b 128) (>= b 192)) (js-uri-malformed!)) + (else + (js-uri-decode-multi-loop + s + i + acc + preserveReserved + n + (+ k 1) + (+ (* cp 64) (mod b 64))))))))))))) (define js-uri-encode-loop diff --git a/lib/js/test262-runner.py b/lib/js/test262-runner.py index e4118da4..f2f6aa1e 100644 --- a/lib/js/test262-runner.py +++ b/lib/js/test262-runner.py @@ -146,6 +146,16 @@ var isConstructor = function (f) { // Best-effort: built-in functions and arrows aren't; declared `function` decls are. return false; }; +// decimalToHexString.js include — used by URI/escape tests. +var decimalToHexString = function (n) { + var hex = "0123456789ABCDEF"; + if (n < 0) { n = n + 65536; } + return hex[(n >> 12) & 15] + hex[(n >> 8) & 15] + hex[(n >> 4) & 15] + hex[n & 15]; +}; +var decimalToPercentHexString = function (n) { + var hex = "0123456789ABCDEF"; + return "%" + hex[(n >> 4) & 15] + hex[n & 15]; +}; // Trivial helper for tests that use Array.isArray-like functionality // (many tests reach for it via compareArray) """ diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index f13f0ced..e9e076d6 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 — **`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. - 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.