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:
@@ -990,11 +990,18 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
if (n === undefined || n === 0) return Math.round(x);
|
if (n === undefined || n === 0) return Math.round(x);
|
||||||
var f = Math.pow(10, n); return Math.round(x * f) / f;
|
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["min"] = Math.min;
|
||||||
PRIMITIVES["max"] = Math.max;
|
PRIMITIVES["max"] = Math.max;
|
||||||
PRIMITIVES["sqrt"] = Math.sqrt;
|
PRIMITIVES["sqrt"] = Math.sqrt;
|
||||||
PRIMITIVES["pow"] = Math.pow;
|
PRIMITIVES["pow"] = Math.pow;
|
||||||
PRIMITIVES["clamp"] = function(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); };
|
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": '''
|
"core.comparison": '''
|
||||||
@@ -1016,6 +1023,10 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
// core.predicates
|
// core.predicates
|
||||||
PRIMITIVES["nil?"] = isNil;
|
PRIMITIVES["nil?"] = isNil;
|
||||||
PRIMITIVES["number?"] = function(x) { return typeof x === "number"; };
|
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["string?"] = function(x) { return typeof x === "string"; };
|
||||||
PRIMITIVES["list?"] = Array.isArray;
|
PRIMITIVES["list?"] = Array.isArray;
|
||||||
PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };
|
PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };
|
||||||
|
|||||||
@@ -296,6 +296,10 @@ let read_blob () =
|
|||||||
(* consume trailing newline *)
|
(* consume trailing newline *)
|
||||||
(try ignore (input_line stdin) with End_of_file -> ());
|
(try ignore (input_line stdin) with End_of_file -> ());
|
||||||
data
|
data
|
||||||
|
| [List [Symbol "blob"; Integer n]] ->
|
||||||
|
let data = read_exact_bytes n in
|
||||||
|
(try ignore (input_line stdin) with End_of_file -> ());
|
||||||
|
data
|
||||||
| _ -> raise (Eval_error ("read_blob: expected (blob N), got: " ^ line))
|
| _ -> raise (Eval_error ("read_blob: expected (blob N), got: " ^ line))
|
||||||
|
|
||||||
(** Batch IO mode — collect requests during aser-slot, resolve after. *)
|
(** Batch IO mode — collect requests during aser-slot, resolve after. *)
|
||||||
@@ -357,6 +361,11 @@ let rec read_io_response () =
|
|||||||
| [List (Symbol "io-response" :: Number n :: values)]
|
| [List (Symbol "io-response" :: Number n :: values)]
|
||||||
when int_of_float n = !current_epoch ->
|
when int_of_float n = !current_epoch ->
|
||||||
(match values with [v] -> v | _ -> List values)
|
(match values with [v] -> v | _ -> List values)
|
||||||
|
| [List [Symbol "io-response"; Integer n; value]]
|
||||||
|
when n = !current_epoch -> value
|
||||||
|
| [List (Symbol "io-response" :: Integer n :: values)]
|
||||||
|
when n = !current_epoch ->
|
||||||
|
(match values with [v] -> v | _ -> List values)
|
||||||
(* Legacy untagged: (io-response value) — accept for backwards compat *)
|
(* Legacy untagged: (io-response value) — accept for backwards compat *)
|
||||||
| [List [Symbol "io-response"; value]] -> value
|
| [List [Symbol "io-response"; value]] -> value
|
||||||
| [List (Symbol "io-response" :: values)] ->
|
| [List (Symbol "io-response" :: values)] ->
|
||||||
@@ -396,6 +405,12 @@ let read_batched_io_response () =
|
|||||||
when int_of_float n = !current_epoch -> s
|
when int_of_float n = !current_epoch -> s
|
||||||
| [List [Symbol "io-response"; Number n; v]]
|
| [List [Symbol "io-response"; Number n; v]]
|
||||||
when int_of_float n = !current_epoch -> serialize_value v
|
when int_of_float n = !current_epoch -> serialize_value v
|
||||||
|
| [List [Symbol "io-response"; Integer n; String s]]
|
||||||
|
when n = !current_epoch -> s
|
||||||
|
| [List [Symbol "io-response"; Integer n; SxExpr s]]
|
||||||
|
when n = !current_epoch -> s
|
||||||
|
| [List [Symbol "io-response"; Integer n; v]]
|
||||||
|
when n = !current_epoch -> serialize_value v
|
||||||
(* Legacy untagged *)
|
(* Legacy untagged *)
|
||||||
| [List [Symbol "io-response"; String s]]
|
| [List [Symbol "io-response"; String s]]
|
||||||
| [List [Symbol "io-response"; SxExpr s]] -> s
|
| [List [Symbol "io-response"; SxExpr s]] -> s
|
||||||
@@ -959,6 +974,7 @@ let setup_io_bridges env =
|
|||||||
bind "sleep" (fun args -> io_request "sleep" args);
|
bind "sleep" (fun args -> io_request "sleep" args);
|
||||||
bind "set-response-status" (fun args -> match args with
|
bind "set-response-status" (fun args -> match args with
|
||||||
| [Number n] -> _pending_response_status := int_of_float n; Nil
|
| [Number n] -> _pending_response_status := int_of_float n; Nil
|
||||||
|
| [Integer n] -> _pending_response_status := n; Nil
|
||||||
| _ -> Nil);
|
| _ -> Nil);
|
||||||
bind "set-response-header" (fun args -> io_request "set-response-header" args)
|
bind "set-response-header" (fun args -> io_request "set-response-header" args)
|
||||||
|
|
||||||
@@ -4450,6 +4466,8 @@ let site_mode () =
|
|||||||
match exprs with
|
match exprs with
|
||||||
| [List [Symbol "epoch"; Number n]] ->
|
| [List [Symbol "epoch"; Number n]] ->
|
||||||
current_epoch := int_of_float n
|
current_epoch := int_of_float n
|
||||||
|
| [List [Symbol "epoch"; Integer n]] ->
|
||||||
|
current_epoch := n
|
||||||
(* render-page: full SSR pipeline — URL → complete HTML *)
|
(* render-page: full SSR pipeline — URL → complete HTML *)
|
||||||
| [List [Symbol "render-page"; String path]] ->
|
| [List [Symbol "render-page"; String path]] ->
|
||||||
(try match http_render_page env path [] with
|
(try match http_render_page env path [] with
|
||||||
@@ -4507,6 +4525,8 @@ let () =
|
|||||||
(* Epoch marker: (epoch N) — set current epoch, read next command *)
|
(* Epoch marker: (epoch N) — set current epoch, read next command *)
|
||||||
| [List [Symbol "epoch"; Number n]] ->
|
| [List [Symbol "epoch"; Number n]] ->
|
||||||
current_epoch := int_of_float n
|
current_epoch := int_of_float n
|
||||||
|
| [List [Symbol "epoch"; Integer n]] ->
|
||||||
|
current_epoch := n
|
||||||
| [cmd] -> dispatch env cmd
|
| [cmd] -> dispatch env cmd
|
||||||
| _ -> send_error ("Expected single command, got " ^ string_of_int (List.length exprs))
|
| _ -> send_error ("Expected single command, got " ^ string_of_int (List.length exprs))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
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 isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||||
@@ -387,11 +387,18 @@
|
|||||||
if (n === undefined || n === 0) return Math.round(x);
|
if (n === undefined || n === 0) return Math.round(x);
|
||||||
var f = Math.pow(10, n); return Math.round(x * f) / f;
|
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["min"] = Math.min;
|
||||||
PRIMITIVES["max"] = Math.max;
|
PRIMITIVES["max"] = Math.max;
|
||||||
PRIMITIVES["sqrt"] = Math.sqrt;
|
PRIMITIVES["sqrt"] = Math.sqrt;
|
||||||
PRIMITIVES["pow"] = Math.pow;
|
PRIMITIVES["pow"] = Math.pow;
|
||||||
PRIMITIVES["clamp"] = function(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); };
|
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
|
// core.comparison
|
||||||
@@ -410,6 +417,10 @@
|
|||||||
// core.predicates
|
// core.predicates
|
||||||
PRIMITIVES["nil?"] = isNil;
|
PRIMITIVES["nil?"] = isNil;
|
||||||
PRIMITIVES["number?"] = function(x) { return typeof x === "number"; };
|
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["string?"] = function(x) { return typeof x === "string"; };
|
||||||
PRIMITIVES["list?"] = Array.isArray;
|
PRIMITIVES["list?"] = Array.isArray;
|
||||||
PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };
|
PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };
|
||||||
|
|||||||
Reference in New Issue
Block a user