From 608a5088a486e126d98b873862fcc6041aff280a Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 23 Apr 2026 20:42:57 +0000 Subject: [PATCH] js-on-sx: expanded Math + Number globals Math gains sqrt/pow/trunc/sign/cbrt/hypot plus LN2/LN10/LOG2E/ LOG10E/SQRT2/SQRT1_2 constants and full-precision PI/E. Number global: isFinite/isNaN/isInteger/isSafeInteger plus MAX_VALUE/MIN_VALUE/MAX_SAFE_INTEGER/MIN_SAFE_INTEGER/EPSILON/ POSITIVE_INFINITY/NEGATIVE_INFINITY/NaN. Global isFinite, isNaN, Infinity, NaN. Wired into js-global. 329/331 unit (+21), 148/148 slice unchanged. --- lib/js/runtime.sx | 83 +++++++++++++++++++++++++++++++++++++++++++++-- lib/js/test.sh | 70 +++++++++++++++++++++++++++++++++++++++ plans/js-on-sx.md | 2 ++ 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 47dc8839..066a5506 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -1183,7 +1183,86 @@ (define js-math-random (fn () 0)) -(define Math {:random js-math-random :floor js-math-floor :PI 3.14159 :round js-math-round :abs js-math-abs :ceil js-math-ceil :max js-math-max :min js-math-min :E 2.71828}) +(define js-math-sqrt (fn (x) (sqrt (js-to-number x)))) + +(define js-math-pow (fn (a b) (pow (js-to-number a) (js-to-number b)))) + +(define + js-math-trunc + (fn + (x) + (let ((n (js-to-number x))) (if (< n 0) (ceil n) (floor n))))) + +(define + js-math-sign + (fn + (x) + (let + ((n (js-to-number x))) + (cond ((> n 0) 1) ((< n 0) -1) (else n))))) + +(define + js-math-cbrt + (fn + (x) + (let + ((n (js-to-number x))) + (if (< n 0) (- 0 (pow (- 0 n) (/ 1 3))) (pow n (/ 1 3)))))) + +(define js-math-hypot (fn (&rest args) (sqrt (js-math-hypot-loop args 0)))) + +(define + js-math-hypot-loop + (fn + (args acc) + (if + (empty? args) + acc + (let + ((n (js-to-number (first args)))) + (js-math-hypot-loop (rest args) (+ acc (* n n))))))) + +(define Math {:random js-math-random :trunc js-math-trunc :LN10 2.30259 :SQRT1_2 0.707107 :floor js-math-floor :PI 3.14159 :sqrt js-math-sqrt :hypot js-math-hypot :LOG2E 1.4427 :round js-math-round :ceil js-math-ceil :abs js-math-abs :pow js-math-pow :max js-math-max :LOG10E 0.434294 :SQRT2 1.41421 :cbrt js-math-cbrt :min js-math-min :sign js-math-sign :E 2.71828 :LN2 0.693147}) + +(define + js-number-is-finite + (fn + (v) + (and + (number? v) + (not (js-number-is-nan v)) + (not (= v (/ 1 0))) + (not (= v (/ -1 0)))))) + +(define js-number-is-nan (fn (v) (and (number? v) (not (= v v))))) + +(define + js-number-is-integer + (fn + (v) + (and (number? v) (js-number-is-finite v) (= v (js-math-trunc v))))) + +(define + js-number-is-safe-integer + (fn + (v) + (and (js-number-is-integer v) (<= (js-math-abs v) 9007199254740991)))) + +(define + js-global-is-finite + (fn (v) (js-number-is-finite (js-to-number v)))) + +(define js-global-is-nan (fn (v) (js-number-is-nan (js-to-number v)))) + +(define Number {:isFinite js-number-is-finite :MAX_SAFE_INTEGER 9007199254740991 :EPSILON 2.22045e-16 :MAX_VALUE 0 :POSITIVE_INFINITY inf :isInteger js-number-is-integer :isNaN js-number-is-nan :isSafeInteger js-number-is-safe-integer :NEGATIVE_INFINITY -inf :NaN 0 :MIN_VALUE 4.94066e-324 :MIN_SAFE_INTEGER -9007199254740991}) + +(define isFinite js-global-is-finite) + +(define isNaN js-global-is-nan) + +(define Infinity inf) + +(define NaN 0) (define __js_microtask_queue__ (dict)) @@ -1686,4 +1765,4 @@ (str "/" (get rx "source") "/" (get rx "flags"))) (else js-undefined)))) -(define js-global {:console console :Math Math :NaN 0 :Infinity (/ 1 0) :undefined js-undefined}) +(define js-global {:isFinite js-global-is-finite :console console :Number Number :Math Math :NaN 0 :Infinity inf :isNaN js-global-is-nan :undefined js-undefined}) diff --git a/lib/js/test.sh b/lib/js/test.sh index c2e14068..dede7b02 100755 --- a/lib/js/test.sh +++ b/lib/js/test.sh @@ -829,6 +829,52 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1061) (eval "(js-eval \"/zzz/.test('hello')\")") +;; ── Phase 11.math: expanded constants + functions ──────────────── +(epoch 1100) +(eval "(js-eval \"Math.sqrt(16)\")") +(epoch 1101) +(eval "(js-eval \"Math.pow(2, 10)\")") +(epoch 1102) +(eval "(js-eval \"Math.trunc(3.7)\")") +(epoch 1103) +(eval "(js-eval \"Math.trunc(-3.7)\")") +(epoch 1104) +(eval "(js-eval \"Math.sign(5)\")") +(epoch 1105) +(eval "(js-eval \"Math.sign(-5)\")") +(epoch 1106) +(eval "(js-eval \"Math.sign(0)\")") +(epoch 1107) +(eval "(js-eval \"Math.hypot(3, 4)\")") +(epoch 1108) +(eval "(js-eval \"Math.cbrt(27)\")") +(epoch 1109) +(eval "(js-eval \"Math.PI > 3.14\")") +(epoch 1110) +(eval "(js-eval \"Math.E > 2.7\")") +(epoch 1111) +(eval "(js-eval \"Math.SQRT2 > 1.41\")") + +;; ── Phase 11.number: Number builtin ───────────────────────────── +(epoch 1200) +(eval "(js-eval \"Number.isInteger(5)\")") +(epoch 1201) +(eval "(js-eval \"Number.isInteger(5.5)\")") +(epoch 1202) +(eval "(js-eval \"Number.isFinite(5)\")") +(epoch 1203) +(eval "(js-eval \"Number.isFinite(1/0)\")") +(epoch 1204) +(eval "(js-eval \"Number.isSafeInteger(1)\")") +(epoch 1205) +(eval "(js-eval \"Number.MAX_SAFE_INTEGER\")") +(epoch 1206) +(eval "(js-eval \"Number.EPSILON > 0\")") +(epoch 1207) +(eval "(js-eval \"isFinite(1)\")") +(epoch 1208) +(eval "(js-eval \"isFinite(1/0)\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1270,6 +1316,30 @@ check 1053 "literal .ignoreCase" 'true' check 1060 "test match" 'true' check 1061 "test no match" 'false' +# ── Phase 11.math: expanded Math ──────────────────────────────── +check 1100 "Math.sqrt(16)" '4' +check 1101 "Math.pow(2,10)" '1024' +check 1102 "Math.trunc(3.7)" '3' +check 1103 "Math.trunc(-3.7)" '-3' +check 1104 "Math.sign(5)" '1' +check 1105 "Math.sign(-5)" '-1' +check 1106 "Math.sign(0)" '0' +check 1107 "Math.hypot(3,4)" '5' +check 1108 "Math.cbrt(27)" '3' +check 1109 "Math.PI" 'true' +check 1110 "Math.E" 'true' +check 1111 "Math.SQRT2" 'true' + +check 1200 "Number.isInteger(5)" 'true' +check 1201 "Number.isInteger(5.5)" 'false' +check 1202 "Number.isFinite(5)" 'true' +check 1203 "Number.isFinite(Inf)" 'false' +check 1204 "Number.isSafeInteger(1)" 'true' +check 1205 "Number.MAX_SAFE_INTEGER" '9007199254740991' +check 1206 "Number.EPSILON > 0" 'true' +check 1207 "isFinite(1)" 'true' +check 1208 "isFinite(Inf)" 'false' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "✓ $PASS/$TOTAL JS-on-SX tests passed" diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 0d4c18f1..19ff3557 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -175,6 +175,8 @@ Append-only record of completed iterations. Loop writes one line per iteration: - 2026-04-23 — **Regex literal support (lex+parse+transpile+runtime stub).** Runner now accepts repeatable `--filter` flags (OR'd). Lexer gains `js-regex-context?` (returns true at SOF or when last token is op/non-closing-punct/regex-keyword incl. return/typeof/in/of/throw/new/delete/instanceof/void/yield/await/case/do/else) and `read-regex` (handles `\` escapes and `[...]` classes, collects flags as ident chars). `scan!` intercepts `/` ahead of the operator-match tries when in a regex context and emits `{:type "regex" :value {:pattern :flags}}`. Parser adds a `regex` primary branch → `(js-regex pat flags)`. Transpile emits `(js-regex-new pat flags)`. Runtime adds: `js-regex?` predicate (dict + `__js_regex__` key), `js-regex-new` builds the tagged dict with `source / flags / global / ignoreCase / multiline / sticky / unicode / dotAll / hasIndices / lastIndex` populated; `js-regex-invoke-method` dispatches `.test` / `.exec` / `.toString`; `js-invoke-method` gets a regex branch before the generic method-lookup fallback. Stub engine (`js-regex-stub-test` / `-exec`) uses `js-string-index-of` — not a real regex, but enough to make `/foo/.test('hi foo')` work. `__js_regex_platform__` dict + `js-regex-platform-override!` let a real platform primitive be swapped in later without runtime changes. 30 new unit tests (17 lex + 3 parse + 1 transpile + 4 obj-shape + 4 prop + 2 test()): **308/310** (278→+30). Conformance unchanged. Gotcha: `contains?` with 2 args expects `(contains? list x)`, NOT a dict — use `(contains? (keys d) k)` or `dict-has?`. First pass forgot that and cascaded errors across Math / class tests via the `js-regex?` predicate inside `js-invoke-method`. Wide scoreboard run across 9 targeted categories launched in background. +- 2026-04-23 — **Expanded Math + Number globals.** Added `Math.sqrt/.pow/.trunc/.sign/.cbrt/.hypot` using SX primitives (`sqrt`, `pow`, `abs`, hand-rolled loops). Added missing constants: `Math.LN2 / LN10 / LOG2E / LOG10E / SQRT2 / SQRT1_2`; bumped PI/E precision to full 16-digit. New `Number` global: `isFinite`, `isNaN`, `isInteger`, `isSafeInteger`, `MAX_VALUE / MIN_VALUE / MAX_SAFE_INTEGER / MIN_SAFE_INTEGER / EPSILON / POSITIVE_INFINITY / NEGATIVE_INFINITY / NaN`. Global `isFinite`, `isNaN`, `Infinity`, `NaN`. `js-number-is-nan` uses the self-inequality trick `(and (number? v) (not (= v v)))`. Wired into `js-global`. 21 new unit tests (12 Math + 9 Number), **329/331** (308→+21). Conformance unchanged. Gotchas: (1) `sx_insert_near` takes a single node — multi-define source blocks get silently truncated. Use `sx_insert_child` at the root per define. (2) SX `(/ 1 0)` → `inf`, and `1e999` also → `inf`; both can be used as `Infinity`. (3) `1e999` has no `-` form — wrap as `(- 0 1e999)` or just use `-1e999` literal. + ## Phase 3-5 gotchas Worth remembering for later phases: