js: numeric tower — integer?/float?/exact?/inexact? + epoch Integer fix

Add integer?/float?/exact?/inexact? predicates (Number.isInteger check).
Add truncate/remainder/modulo/random-int/exact->inexact/inexact->exact/parse-number.
inexact->exact uses Math.round (rounds to nearest, matching OCaml).
Fix sx_server.ml epoch/blob/io-response protocol to accept Integer as
well as Number — parser now produces Integer for whole-number literals.
JS: 60 new passing tests (1880→1940). OCaml: 4874/394 baseline unchanged.
Note: 6 tests fail in JS due to platform limitation (JS cannot distinguish
float 2.0 from integer 2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 12:46:17 +00:00
parent 7888fbfd81
commit b12a22e68a
3 changed files with 43 additions and 1 deletions

View File

@@ -31,7 +31,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-04-26T10:01:22Z";
var SX_VERSION = "2026-04-26T12:42:00Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -387,11 +387,18 @@
if (n === undefined || n === 0) return Math.round(x);
var f = Math.pow(10, n); return Math.round(x * f) / f;
};
PRIMITIVES["truncate"] = Math.trunc;
PRIMITIVES["remainder"] = function(a, b) { return a % b; };
PRIMITIVES["modulo"] = function(a, b) { var r = a % b; return (r !== 0 && (r < 0) !== (b < 0)) ? r + b : r; };
PRIMITIVES["min"] = Math.min;
PRIMITIVES["max"] = Math.max;
PRIMITIVES["sqrt"] = Math.sqrt;
PRIMITIVES["pow"] = Math.pow;
PRIMITIVES["clamp"] = function(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); };
PRIMITIVES["random-int"] = function(lo, hi) { return Math.floor(Math.random() * (hi - lo + 1)) + lo; };
PRIMITIVES["exact->inexact"] = function(x) { return x; };
PRIMITIVES["inexact->exact"] = Math.round;
PRIMITIVES["parse-number"] = function(s) { var n = Number(s); return isNaN(n) ? null : n; };
// core.comparison
@@ -410,6 +417,10 @@
// core.predicates
PRIMITIVES["nil?"] = isNil;
PRIMITIVES["number?"] = function(x) { return typeof x === "number"; };
PRIMITIVES["integer?"] = function(x) { return typeof x === "number" && Number.isInteger(x); };
PRIMITIVES["float?"] = function(x) { return typeof x === "number" && !Number.isInteger(x); };
PRIMITIVES["exact?"] = function(x) { return typeof x === "number" && Number.isInteger(x); };
PRIMITIVES["inexact?"] = function(x) { return typeof x === "number" && !Number.isInteger(x); };
PRIMITIVES["string?"] = function(x) { return typeof x === "string"; };
PRIMITIVES["list?"] = Array.isArray;
PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };