From a8d0dfb38a921867baf7d9788b6583aa9caccfeb Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 17:10:57 +0000 Subject: [PATCH] js-on-sx: bitwise ops & | ^ << >> (+ compound assigns) --- lib/js/runtime.sx | 61 +++++++++++++++++++++++++++++++++++++++++++++ lib/js/transpile.sx | 16 ++++++++++++ plans/js-on-sx.md | 2 ++ 3 files changed, 79 insertions(+) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index bae53331..511e6a8e 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -1667,6 +1667,67 @@ (shift (modulo (js-math-trunc (js-to-number r)) 32))) (floor (/ lu32 (js-math-pow 2 shift)))))) +(define + js-to-uint32 + (fn (n) (modulo (js-math-trunc (js-to-number n)) 4294967296))) + +(define + js-uint32-to-int32 + (fn (u) (if (>= u 2147483648) (- u 4294967296) u))) + +(define js-to-int32 (fn (n) (js-uint32-to-int32 (js-to-uint32 n)))) + +(define + js-bitwise-loop + (fn + (op au bu i acc bit) + (if + (>= i 32) + acc + (let + ((abit (modulo (floor (/ au bit)) 2)) + (bbit (modulo (floor (/ bu bit)) 2))) + (let + ((rbit + (cond + ((= op "and") (* abit bbit)) + ((= op "or") (if (or (= abit 1) (= bbit 1)) 1 0)) + ((= op "xor") (if (= abit bbit) 0 1)) + (else 0)))) + (js-bitwise-loop + op au bu (+ i 1) (+ acc (* rbit bit)) (* bit 2))))))) + +(define + js-bitwise-binop + (fn + (op a b) + (js-uint32-to-int32 + (js-bitwise-loop op (js-to-uint32 a) (js-to-uint32 b) 0 0 1)))) + +(define js-bitand (fn (a b) (js-bitwise-binop "and" a b))) + +(define js-bitor (fn (a b) (js-bitwise-binop "or" a b))) + +(define js-bitxor (fn (a b) (js-bitwise-binop "xor" a b))) + +(define + js-shl + (fn + (a b) + (let + ((au (js-to-uint32 a)) + (sh (modulo (js-math-trunc (js-to-number b)) 32))) + (js-uint32-to-int32 (modulo (* au (js-math-pow 2 sh)) 4294967296))))) + +(define + js-shr + (fn + (a b) + (let + ((ai (js-to-int32 a)) + (sh (modulo (js-math-trunc (js-to-number b)) 32))) + (if (= sh 0) ai (floor (/ ai (js-math-pow 2 sh))))))) + (define js-pow (fn (a b) (pow (js-to-number a) (js-to-number b)))) (define js-neg (fn (a) (* -1 (exact->inexact (js-to-number a))))) diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx index ce09fb0f..8503129a 100644 --- a/lib/js/transpile.sx +++ b/lib/js/transpile.sx @@ -303,6 +303,16 @@ (js-sym "js-unsigned-rshift") (js-transpile l) (js-transpile r))) + ((= op "<<") + (list (js-sym "js-shl") (js-transpile l) (js-transpile r))) + ((= op ">>") + (list (js-sym "js-shr") (js-transpile l) (js-transpile r))) + ((= op "&") + (list (js-sym "js-bitand") (js-transpile l) (js-transpile r))) + ((= op "|") + (list (js-sym "js-bitor") (js-transpile l) (js-transpile r))) + ((= op "^") + (list (js-sym "js-bitxor") (js-transpile l) (js-transpile r))) (else (error (str "js-transpile-binop: unsupported op: " op)))))) ;; ── Object literal ──────────────────────────────────────────────── @@ -674,6 +684,12 @@ (list (js-sym "js-undefined?") lhs-expr)) rhs-expr lhs-expr)) + ((= op "<<=") (list (js-sym "js-shl") lhs-expr rhs-expr)) + ((= op ">>=") (list (js-sym "js-shr") lhs-expr rhs-expr)) + ((= op ">>>=") (list (js-sym "js-unsigned-rshift") lhs-expr rhs-expr)) + ((= op "&=") (list (js-sym "js-bitand") lhs-expr rhs-expr)) + ((= op "|=") (list (js-sym "js-bitor") lhs-expr rhs-expr)) + ((= op "^=") (list (js-sym "js-bitxor") lhs-expr rhs-expr)) (else (error (str "js-compound-update: unsupported op: " op)))))) (define diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index cd5f0763..7c1d05f2 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 — **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. - 2026-05-08 — **`arguments` object inside JS functions; `Array.from` calls mapFn correctly.** Three related fixes: (1) Every JS function body now binds `arguments` to `(cons p1 (cons p2 ... __extra_args__))` — a list of all received args, declared and rest. (2) `Array.from(iter, mapFn)` now invokes mapFn through `js-call-with-this` with the index as second arg (was `(map-fn x)` direct, missing index and inheriting outer `this`). (3) Defaults the `thisArg` to `js-global-this` when caller didn't pass one (per non-strict ES). Now `function f() { return arguments[1]; } f(1, 2)` returns 2; `Array.from([1,2,3], (v, i) => v + i*100)` returns `[1, 102, 203]`. conformance.sh: 148/148.