VM global_env sync + isomorphic nav store primitives

Reverse hook syncs VM GLOBAL_SET mutations back to global_env so CEK reads
see JIT-written values. Isomorphic nav: store primitives, event-bridge,
client? predicate. Browser JS and bytecode rebuilt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 15:20:12 +00:00
parent 153f02c672
commit 90918fb2b1
10 changed files with 47 additions and 447 deletions

View File

@@ -223,6 +223,13 @@ let () =
if env == global_env then
Hashtbl.replace _vm_globals name v)
(* Reverse hook: sync VM GLOBAL_SET mutations back to global_env.
Without this, set! inside JIT-compiled functions writes to _vm_globals
but leaves global_env stale — CEK reads then see the old value. *)
let () =
Sx_types._vm_global_set_hook := Some (fun name v ->
Hashtbl.replace global_env.bindings (Sx_types.intern name) v)
(* ================================================================== *)
(* Core API *)
(* ================================================================== *)

File diff suppressed because one or more lines are too long

View File

@@ -50,7 +50,12 @@ let sx_call f args =
Thunk (l.l_body, local)
| Continuation (k, _) ->
k (match args with x :: _ -> x | [] -> Nil)
| _ -> raise (Eval_error ("Not callable: " ^ inspect f))
| _ ->
let nargs = List.length args in
let args_preview = if nargs = 0 then "" else
let s = String.concat ", " (List.map (fun a -> let s = inspect a in if String.length s > 40 then String.sub s 0 40 ^ ".." else s) args) in
" with args=[" ^ s ^ "]" in
raise (Eval_error ("Not callable: " ^ inspect f ^ args_preview))
(* Initialize forward ref so primitives can call SX functions *)
let () = Sx_primitives._sx_call_fn := sx_call

View File

@@ -185,8 +185,18 @@ let make_env () =
let env_extend parent =
{ bindings = Hashtbl.create 16; parent = Some parent }
(* Optional hook: called after every env_bind with (env, name, value).
Used by browser kernel to sync VM globals table. *)
let _env_bind_hook : (env -> string -> value -> unit) option ref = ref None
(* Optional hook: called after VM GLOBAL_SET writes to vm.globals.
Used by browser kernel to sync mutations back to global_env. *)
let _vm_global_set_hook : (string -> value -> unit) option ref = ref None
let env_bind env name v =
Hashtbl.replace env.bindings (intern name) v; Nil
Hashtbl.replace env.bindings (intern name) v;
(match !_env_bind_hook with Some f -> f env name v | None -> ());
Nil
(* Internal: scope-chain lookup with pre-interned ID *)
let rec env_has_id env id =

View File

@@ -303,7 +303,11 @@ and run vm =
in find_env env
| None -> false
in
if not written then Hashtbl.replace vm.globals name (peek vm)
if not written then begin
let v = peek vm in
Hashtbl.replace vm.globals name v;
(match !Sx_types._vm_global_set_hook with Some f -> f name v | None -> ())
end
(* ---- Control flow ---- *)
| 32 (* OP_JUMP *) ->

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-26T16:13:53Z";
var SX_VERSION = "2026-03-28T12:33:20Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -3169,10 +3169,7 @@ PRIMITIVES["dom-body"] = domBody;
PRIMITIVES["dom-head"] = domHead;
// dom-create-element
var domCreateElement = function(tag) { var nsArg = Array.prototype.slice.call(arguments, 1); return (function() {
var ns = (isSxTruthy((isSxTruthy(nsArg) && !isSxTruthy(isEmpty(nsArg)))) ? first(nsArg) : NIL);
return (isSxTruthy(ns) ? hostCall(domDocument(), "createElementNS", ns, tag) : hostCall(domDocument(), "createElement", tag));
})(); };
var domCreateElement = function(tag, ns) { return (isSxTruthy(ns) ? hostCall(domDocument(), "createElementNS", ns, tag) : hostCall(domDocument(), "createElement", tag)); };
PRIMITIVES["dom-create-element"] = domCreateElement;
// create-text-node
@@ -3262,7 +3259,7 @@ PRIMITIVES["dom-replace-child"] = domReplaceChild;
PRIMITIVES["dom-clone"] = domClone;
// dom-query
var domQuery = function(rootOrSel) { var rest = Array.prototype.slice.call(arguments, 1); return (isSxTruthy(isEmpty(rest)) ? hostCall(domDocument(), "querySelector", rootOrSel) : hostCall(rootOrSel, "querySelector", first(rest))); };
var domQuery = function(rootOrSel, sel) { return (isSxTruthy(isNil(sel)) ? hostCall(domDocument(), "querySelector", rootOrSel) : hostCall(rootOrSel, "querySelector", sel)); };
PRIMITIVES["dom-query"] = domQuery;
// dom-query-all
@@ -3455,9 +3452,9 @@ PRIMITIVES["dom-parse-html"] = domParseHtml;
PRIMITIVES["dom-listen"] = domListen;
// dom-add-listener
var domAddListener = function(el, eventName, handler) { var opts = Array.prototype.slice.call(arguments, 3); return (function() {
var domAddListener = function(el, eventName, handler, opts) { return (function() {
var cb = hostCallback(handler);
(isSxTruthy((isSxTruthy(opts) && !isSxTruthy(isEmpty(opts)))) ? hostCall(el, "addEventListener", eventName, cb, first(opts)) : hostCall(el, "addEventListener", eventName, cb));
(isSxTruthy(opts) ? hostCall(el, "addEventListener", eventName, cb, opts) : hostCall(el, "addEventListener", eventName, cb));
return function() { return hostCall(el, "removeEventListener", eventName, cb); };
})(); };
PRIMITIVES["dom-add-listener"] = domAddListener;
@@ -3539,11 +3536,11 @@ PRIMITIVES["browser-same-origin?"] = browserSameOrigin;
PRIMITIVES["url-pathname"] = urlPathname;
// browser-push-state
var browserPushState = function(urlOrState) { var rest = Array.prototype.slice.call(arguments, 1); return (isSxTruthy(isEmpty(rest)) ? hostCall(hostGet(domWindow(), "history"), "pushState", NIL, "", urlOrState) : hostCall(hostGet(domWindow(), "history"), "pushState", urlOrState, first(rest), nth(rest, 1))); };
var browserPushState = function(urlOrState, title, url) { return (isSxTruthy(isNil(title)) ? hostCall(hostGet(domWindow(), "history"), "pushState", NIL, "", urlOrState) : hostCall(hostGet(domWindow(), "history"), "pushState", urlOrState, title, url)); };
PRIMITIVES["browser-push-state"] = browserPushState;
// browser-replace-state
var browserReplaceState = function(urlOrState) { var rest = Array.prototype.slice.call(arguments, 1); return (isSxTruthy(isEmpty(rest)) ? hostCall(hostGet(domWindow(), "history"), "replaceState", NIL, "", urlOrState) : hostCall(hostGet(domWindow(), "history"), "replaceState", urlOrState, first(rest), nth(rest, 1))); };
var browserReplaceState = function(urlOrState, title, url) { return (isSxTruthy(isNil(title)) ? hostCall(hostGet(domWindow(), "history"), "replaceState", NIL, "", urlOrState) : hostCall(hostGet(domWindow(), "history"), "replaceState", urlOrState, title, url)); };
PRIMITIVES["browser-replace-state"] = browserReplaceState;
// browser-reload
@@ -3643,7 +3640,7 @@ PRIMITIVES["log-info"] = logInfo;
PRIMITIVES["log-warn"] = logWarn;
// console-log
var consoleLog = function() { var args = Array.prototype.slice.call(arguments, 0); return hostCall(hostGlobal("console"), "log", join(" ", cons("[sx]", map(str, args)))); };
var consoleLog = function(msg) { return hostCall(hostGlobal("console"), "log", (String("[sx] ") + String(msg))); };
PRIMITIVES["console-log"] = consoleLog;
// now-ms
@@ -4772,7 +4769,7 @@ PRIMITIVES["init-css-tracking"] = initCssTracking;
})()) ? promiseResolve(NIL) : (function() {
var promptMsg = domGetAttr(el, "sx-prompt");
var promptVal = (isSxTruthy(promptMsg) ? browserPrompt(promptMsg) : NIL);
return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(!isSxTruthy(validateForRequest(el))) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams))));
return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(sxOr(isNil(verb), isNil(url), !isSxTruthy(validateForRequest(el)))) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams))));
})()));
})());
})(); };
@@ -4942,12 +4939,12 @@ PRIMITIVES["bind-triggers"] = bindTriggers;
var shouldFire = true;
if (isSxTruthy(get(mods, "changed"))) {
(function() {
var val = elementValue(el);
var val = domValue(el);
return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val));
})();
}
return (isSxTruthy((isSxTruthy(shouldFire) && !isSxTruthy((isSxTruthy((eventName == "click")) && eventModifierKey_p(e))))) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() {
var liveInfo = sxOr(getVerbInfo(el), verbInfo);
var liveInfo = getVerbInfo(el);
var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(liveInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay")));
var clientRouted = false;
if (isSxTruthy(isGetLink)) {
@@ -5537,7 +5534,7 @@ PRIMITIVES["sx-render-component"] = sxRenderComponent;
var text = domTextContent(s);
return (isSxTruthy(domHasAttr(s, "data-components")) ? processComponentScript(s, text) : (isSxTruthy(sxOr(isNil(text), isEmpty(trim(text)))) ? NIL : (isSxTruthy(domHasAttr(s, "data-init")) ? (function() {
var exprs = sxParse(text);
return forEach(function(expr) { return evalExpr(expr, envExtend({})); }, exprs);
return forEach(function(expr) { return cekEval(expr); }, exprs);
})() : (isSxTruthy(domHasAttr(s, "data-mount")) ? (function() {
var mountSel = domGetAttr(s, "data-mount");
var target = domQuery(mountSel);
@@ -6533,28 +6530,6 @@ PRIMITIVES["with-marsh-scope"] = withMarshScope;
})(); };
PRIMITIVES["dispose-marsh-scope"] = disposeMarshScope;
// *store-registry*
var _storeRegistry = {};
PRIMITIVES["*store-registry*"] = _storeRegistry;
// def-store
var defStore = function(name, initFn) { return (function() {
var registry = _storeRegistry;
if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) {
_storeRegistry = assoc(registry, name, cekCall(initFn, NIL));
}
return get(_storeRegistry, name);
})(); };
PRIMITIVES["def-store"] = defStore;
// use-store
var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); };
PRIMITIVES["use-store"] = useStore;
// clear-stores
var clearStores = function() { return (_storeRegistry = {}); };
PRIMITIVES["clear-stores"] = clearStores;
// emit-event
var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); };
PRIMITIVES["emit-event"] = emitEvent;
@@ -6583,400 +6558,6 @@ PRIMITIVES["bridge-event"] = bridgeEvent;
PRIMITIVES["resource"] = resource;
// === Transpiled from types (gradual type system) ===
// base-types
var baseTypes = ["number", "string", "boolean", "nil", "symbol", "keyword", "element", "any", "never", "list", "dict", "lambda", "component", "island", "macro", "signal"];
PRIMITIVES["base-types"] = baseTypes;
// type-any?
var typeAny_p = function(t) { return (t == "any"); };
PRIMITIVES["type-any?"] = typeAny_p;
// type-never?
var typeNever_p = function(t) { return (t == "never"); };
PRIMITIVES["type-never?"] = typeNever_p;
// type-nullable?
var typeNullable_p = function(t) { return (isSxTruthy((t == "any")) ? true : (isSxTruthy((t == "nil")) ? true : (isSxTruthy((isSxTruthy((typeOf(t) == "string")) && endsWith(t, "?"))) ? true : (isSxTruthy((isSxTruthy((typeOf(t) == "list")) && isSxTruthy(!isSxTruthy(isEmpty(t))) && (first(t) == "or"))) ? contains(rest(t), "nil") : false)))); };
PRIMITIVES["type-nullable?"] = typeNullable_p;
// nullable-base
var nullableBase = function(t) { return (isSxTruthy((isSxTruthy((typeOf(t) == "string")) && isSxTruthy(endsWith(t, "?")) && !isSxTruthy((t == "?")))) ? slice(t, 0, (stringLength(t) - 1)) : t); };
PRIMITIVES["nullable-base"] = nullableBase;
// subtype?
var subtype_p = function(a, b) { return (isSxTruthy(typeAny_p(b)) ? true : (isSxTruthy(typeNever_p(a)) ? true : (isSxTruthy(typeAny_p(a)) ? false : (isSxTruthy((a == b)) ? true : (isSxTruthy((a == "nil")) ? typeNullable_p(b) : (isSxTruthy((isSxTruthy((typeOf(b) == "string")) && endsWith(b, "?"))) ? (function() {
var base = nullableBase(b);
return sxOr((a == base), (a == "nil"));
})() : (isSxTruthy((isSxTruthy((typeOf(a) == "list")) && isSxTruthy(!isSxTruthy(isEmpty(a))) && (first(a) == "or"))) ? isEvery(function(member) { return subtype_p(member, b); }, rest(a)) : (isSxTruthy((isSxTruthy((typeOf(b) == "list")) && isSxTruthy(!isSxTruthy(isEmpty(b))) && (first(b) == "or"))) ? some(function(member) { return subtype_p(a, member); }, rest(b)) : (isSxTruthy((isSxTruthy((typeOf(a) == "list")) && isSxTruthy((typeOf(b) == "list")) && isSxTruthy((len(a) == 2)) && isSxTruthy((len(b) == 2)) && isSxTruthy((first(a) == "list-of")) && (first(b) == "list-of"))) ? subtype_p(nth(a, 1), nth(b, 1)) : (isSxTruthy((isSxTruthy((a == "list")) && isSxTruthy((typeOf(b) == "list")) && isSxTruthy((len(b) == 2)) && (first(b) == "list-of"))) ? typeAny_p(nth(b, 1)) : (isSxTruthy((isSxTruthy((typeOf(a) == "list")) && isSxTruthy((len(a) == 2)) && isSxTruthy((first(a) == "list-of")) && (b == "list"))) ? true : false))))))))))); };
PRIMITIVES["subtype?"] = subtype_p;
// type-union
var typeUnion = function(a, b) { return (isSxTruthy((a == b)) ? a : (isSxTruthy(typeAny_p(a)) ? "any" : (isSxTruthy(typeAny_p(b)) ? "any" : (isSxTruthy(typeNever_p(a)) ? b : (isSxTruthy(typeNever_p(b)) ? a : (isSxTruthy(subtype_p(a, b)) ? b : (isSxTruthy(subtype_p(b, a)) ? a : (isSxTruthy((a == "nil")) ? (isSxTruthy((isSxTruthy((typeOf(b) == "string")) && !isSxTruthy(endsWith(b, "?")))) ? (String(b) + String("?")) : ["or", a, b]) : (isSxTruthy((b == "nil")) ? (isSxTruthy((isSxTruthy((typeOf(a) == "string")) && !isSxTruthy(endsWith(a, "?")))) ? (String(a) + String("?")) : ["or", a, b]) : ["or", a, b]))))))))); };
PRIMITIVES["type-union"] = typeUnion;
// narrow-type
var narrowType = function(t, predicateName) { return (isSxTruthy((predicateName == "nil?")) ? ["nil", narrowExcludeNil(t)] : (isSxTruthy((predicateName == "string?")) ? ["string", narrowExclude(t, "string")] : (isSxTruthy((predicateName == "number?")) ? ["number", narrowExclude(t, "number")] : (isSxTruthy((predicateName == "list?")) ? ["list", narrowExclude(t, "list")] : (isSxTruthy((predicateName == "dict?")) ? ["dict", narrowExclude(t, "dict")] : (isSxTruthy((predicateName == "boolean?")) ? ["boolean", narrowExclude(t, "boolean")] : [t, t])))))); };
PRIMITIVES["narrow-type"] = narrowType;
// narrow-exclude-nil
var narrowExcludeNil = function(t) { return (isSxTruthy((t == "nil")) ? "never" : (isSxTruthy((t == "any")) ? "any" : (isSxTruthy((isSxTruthy((typeOf(t) == "string")) && endsWith(t, "?"))) ? nullableBase(t) : (isSxTruthy((isSxTruthy((typeOf(t) == "list")) && isSxTruthy(!isSxTruthy(isEmpty(t))) && (first(t) == "or"))) ? (function() {
var members = filter(function(m) { return !isSxTruthy((m == "nil")); }, rest(t));
return (isSxTruthy((len(members) == 1)) ? first(members) : (isSxTruthy(isEmpty(members)) ? "never" : cons("or", members)));
})() : t)))); };
PRIMITIVES["narrow-exclude-nil"] = narrowExcludeNil;
// narrow-exclude
var narrowExclude = function(t, excluded) { return (isSxTruthy((t == excluded)) ? "never" : (isSxTruthy((t == "any")) ? "any" : (isSxTruthy((isSxTruthy((typeOf(t) == "list")) && isSxTruthy(!isSxTruthy(isEmpty(t))) && (first(t) == "or"))) ? (function() {
var members = filter(function(m) { return !isSxTruthy((m == excluded)); }, rest(t));
return (isSxTruthy((len(members) == 1)) ? first(members) : (isSxTruthy(isEmpty(members)) ? "never" : cons("or", members)));
})() : t))); };
PRIMITIVES["narrow-exclude"] = narrowExclude;
// infer-type
var inferType = function(node, typeEnv, primTypes, typeRegistry) { return (function() {
var kind = typeOf(node);
return (isSxTruthy((kind == "number")) ? "number" : (isSxTruthy((kind == "string")) ? "string" : (isSxTruthy((kind == "boolean")) ? "boolean" : (isSxTruthy(isNil(node)) ? "nil" : (isSxTruthy((kind == "keyword")) ? "keyword" : (isSxTruthy((kind == "symbol")) ? (function() {
var name = symbolName(node);
return (isSxTruthy(dictHas(typeEnv, name)) ? get(typeEnv, name) : (isSxTruthy((name == "true")) ? "boolean" : (isSxTruthy((name == "false")) ? "boolean" : (isSxTruthy((name == "nil")) ? "nil" : (isSxTruthy(dictHas(primTypes, name)) ? get(primTypes, name) : "any")))));
})() : (isSxTruthy((kind == "dict")) ? "dict" : (isSxTruthy((kind == "list")) ? inferListType(node, typeEnv, primTypes, typeRegistry) : "any"))))))));
})(); };
PRIMITIVES["infer-type"] = inferType;
// infer-list-type
var inferListType = function(node, typeEnv, primTypes, typeRegistry) { return (isSxTruthy(isEmpty(node)) ? "list" : (function() {
var head = first(node);
var args = rest(node);
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? "any" : (function() {
var name = symbolName(head);
return (isSxTruthy((name == "if")) ? inferIfType(args, typeEnv, primTypes, typeRegistry) : (isSxTruthy((name == "when")) ? (isSxTruthy((len(args) >= 2)) ? typeUnion(inferType(last(args), typeEnv, primTypes, typeRegistry), "nil") : "nil") : (isSxTruthy(sxOr((name == "cond"), (name == "case"))) ? "any" : (isSxTruthy((name == "let")) ? inferLetType(args, typeEnv, primTypes, typeRegistry) : (isSxTruthy(sxOr((name == "do"), (name == "begin"))) ? (isSxTruthy(isEmpty(args)) ? "nil" : inferType(last(args), typeEnv, primTypes, typeRegistry)) : (isSxTruthy(sxOr((name == "lambda"), (name == "fn"))) ? "lambda" : (isSxTruthy((name == "and")) ? (isSxTruthy(isEmpty(args)) ? "boolean" : inferType(last(args), typeEnv, primTypes, typeRegistry)) : (isSxTruthy((name == "or")) ? (isSxTruthy(isEmpty(args)) ? "boolean" : reduce(typeUnion, "never", map(function(a) { return inferType(a, typeEnv, primTypes, typeRegistry); }, args))) : (isSxTruthy((name == "map")) ? (isSxTruthy((len(args) >= 2)) ? (function() {
var fnType = inferType(first(args), typeEnv, primTypes, typeRegistry);
return (isSxTruthy((isSxTruthy((typeOf(fnType) == "list")) && (first(fnType) == "->"))) ? ["list-of", last(fnType)] : "list");
})() : "list") : (isSxTruthy((name == "filter")) ? (isSxTruthy((len(args) >= 2)) ? inferType(nth(args, 1), typeEnv, primTypes, typeRegistry) : "list") : (isSxTruthy((name == "reduce")) ? "any" : (isSxTruthy((name == "list")) ? "list" : (isSxTruthy((name == "dict")) ? "dict" : (isSxTruthy((name == "quote")) ? "any" : (isSxTruthy((name == "str")) ? "string" : (isSxTruthy((name == "not")) ? "boolean" : (isSxTruthy((name == "get")) ? (isSxTruthy((isSxTruthy((len(args) >= 2)) && !isSxTruthy(isNil(typeRegistry)))) ? (function() {
var dictType = inferType(first(args), typeEnv, primTypes, typeRegistry);
var keyArg = nth(args, 1);
var keyName = (isSxTruthy((typeOf(keyArg) == "keyword")) ? keywordName(keyArg) : (isSxTruthy((typeOf(keyArg) == "string")) ? keyArg : NIL));
return (isSxTruthy((isSxTruthy(keyName) && isSxTruthy((typeOf(dictType) == "string")) && dictHas(typeRegistry, dictType))) ? (function() {
var resolved = resolveType(dictType, typeRegistry);
return (isSxTruthy((isSxTruthy((typeOf(resolved) == "dict")) && dictHas(resolved, keyName))) ? get(resolved, keyName) : "any");
})() : "any");
})() : "any") : (isSxTruthy(startsWith(name, "~")) ? "element" : (isSxTruthy(dictHas(primTypes, name)) ? get(primTypes, name) : "any")))))))))))))))))));
})());
})()); };
PRIMITIVES["infer-list-type"] = inferListType;
// infer-if-type
var inferIfType = function(args, typeEnv, primTypes, typeRegistry) { return (isSxTruthy((len(args) < 2)) ? "nil" : (function() {
var thenType = inferType(nth(args, 1), typeEnv, primTypes, typeRegistry);
return (isSxTruthy((len(args) >= 3)) ? typeUnion(thenType, inferType(nth(args, 2), typeEnv, primTypes, typeRegistry)) : typeUnion(thenType, "nil"));
})()); };
PRIMITIVES["infer-if-type"] = inferIfType;
// infer-let-type
var inferLetType = function(args, typeEnv, primTypes, typeRegistry) { return (isSxTruthy((len(args) < 2)) ? "nil" : (function() {
var bindings = first(args);
var body = last(args);
var extended = merge(typeEnv, {});
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var binding = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(binding) == "list")) && (len(binding) >= 2)))) {
(function() {
var name = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : (String(first(binding))));
var valType = inferType(nth(binding, 1), extended, primTypes, typeRegistry);
return dictSet(extended, name, valType);
})();
} } }
return inferType(body, extended, primTypes, typeRegistry);
})()); };
PRIMITIVES["infer-let-type"] = inferLetType;
// make-diagnostic
var makeDiagnostic = function(level, message, component, expr) { return {"level": level, "component": component, "expr": expr, "message": message}; };
PRIMITIVES["make-diagnostic"] = makeDiagnostic;
// check-primitive-call
var checkPrimitiveCall = function(name, args, typeEnv, primTypes, primParamTypes, compName, typeRegistry) { return (function() {
var diagnostics = [];
if (isSxTruthy((isSxTruthy(!isSxTruthy(isNil(primParamTypes))) && dictHas(primParamTypes, name)))) {
(function() {
var sig = get(primParamTypes, name);
var positional = get(sig, "positional");
var restType = get(sig, "rest-type");
return forEach(function(idx) { return (isSxTruthy((idx < len(args))) ? (isSxTruthy((idx < len(positional))) ? (function() {
var paramInfo = nth(positional, idx);
var argExpr = nth(args, idx);
return (function() {
var expectedType = nth(paramInfo, 1);
return (isSxTruthy(!isSxTruthy(isNil(expectedType))) ? (function() {
var actual = inferType(argExpr, typeEnv, primTypes, typeRegistry);
return (isSxTruthy((isSxTruthy(!isSxTruthy(typeAny_p(expectedType))) && isSxTruthy(!isSxTruthy(typeAny_p(actual))) && !isSxTruthy(subtypeResolved_p(actual, expectedType, typeRegistry)))) ? append_b(diagnostics, makeDiagnostic("error", (String("Argument ") + String((idx + 1)) + String(" of `") + String(name) + String("` expects ") + String(expectedType) + String(", got ") + String(actual)), compName, argExpr)) : NIL);
})() : NIL);
})();
})() : (isSxTruthy(!isSxTruthy(isNil(restType))) ? (function() {
var argExpr = nth(args, idx);
var actual = inferType(argExpr, typeEnv, primTypes, typeRegistry);
return (isSxTruthy((isSxTruthy(!isSxTruthy(typeAny_p(restType))) && isSxTruthy(!isSxTruthy(typeAny_p(actual))) && !isSxTruthy(subtypeResolved_p(actual, restType, typeRegistry)))) ? append_b(diagnostics, makeDiagnostic("error", (String("Argument ") + String((idx + 1)) + String(" of `") + String(name) + String("` expects ") + String(restType) + String(", got ") + String(actual)), compName, argExpr)) : NIL);
})() : NIL)) : NIL); }, range(0, len(args), 1));
})();
}
return diagnostics;
})(); };
PRIMITIVES["check-primitive-call"] = checkPrimitiveCall;
// check-component-call
var checkComponentCall = function(compName, comp, callArgs, typeEnv, primTypes, typeRegistry) { return (function() {
var diagnostics = [];
var paramTypes = componentParamTypes(comp);
var params = componentParams(comp);
if (isSxTruthy((isSxTruthy(!isSxTruthy(isNil(paramTypes))) && !isSxTruthy(isEmpty(keys(paramTypes)))))) {
(function() {
var i = 0;
var providedKeys = [];
{ var _c = range(0, len(callArgs), 1); for (var _i = 0; _i < _c.length; _i++) { var idx = _c[_i]; if (isSxTruthy((idx < len(callArgs)))) {
(function() {
var arg = nth(callArgs, idx);
return (isSxTruthy((typeOf(arg) == "keyword")) ? (function() {
var keyName = keywordName(arg);
providedKeys.push(keyName);
return (isSxTruthy(((idx + 1) < len(callArgs))) ? (function() {
var valExpr = nth(callArgs, (idx + 1));
return (isSxTruthy(dictHas(paramTypes, keyName)) ? (function() {
var expected = get(paramTypes, keyName);
var actual = inferType(valExpr, typeEnv, primTypes, typeRegistry);
return (isSxTruthy((isSxTruthy(!isSxTruthy(typeAny_p(expected))) && isSxTruthy(!isSxTruthy(typeAny_p(actual))) && !isSxTruthy(subtypeResolved_p(actual, expected, typeRegistry)))) ? append_b(diagnostics, makeDiagnostic("error", (String("Keyword :") + String(keyName) + String(" of ") + String(compName) + String(" expects ") + String(expected) + String(", got ") + String(actual)), compName, valExpr)) : NIL);
})() : NIL);
})() : NIL);
})() : NIL);
})();
} } }
{ var _c = params; for (var _i = 0; _i < _c.length; _i++) { var paramName = _c[_i]; if (isSxTruthy((isSxTruthy(dictHas(paramTypes, paramName)) && isSxTruthy(!isSxTruthy(contains(providedKeys, paramName))) && !isSxTruthy(typeNullable_p(get(paramTypes, paramName)))))) {
diagnostics.push(makeDiagnostic("warning", (String("Required param :") + String(paramName) + String(" of ") + String(compName) + String(" not provided")), compName, NIL));
} } }
return forEach(function(key) { return (isSxTruthy(!isSxTruthy(contains(params, key))) ? append_b(diagnostics, makeDiagnostic("warning", (String("Unknown keyword :") + String(key) + String(" passed to ") + String(compName)), compName, NIL)) : NIL); }, providedKeys);
})();
}
return diagnostics;
})(); };
PRIMITIVES["check-component-call"] = checkComponentCall;
// check-body-walk
var checkBodyWalk = function(node, compName, typeEnv, primTypes, primParamTypes, env, diagnostics, typeRegistry, effectAnnotations) { return (function() {
var kind = typeOf(node);
return (isSxTruthy((kind == "list")) ? (isSxTruthy(!isSxTruthy(isEmpty(node))) ? (function() {
var head = first(node);
var args = rest(node);
if (isSxTruthy((typeOf(head) == "symbol"))) {
(function() {
var name = symbolName(head);
if (isSxTruthy(startsWith(name, "~"))) {
(function() {
var compVal = envGet(env, name);
return (isSxTruthy((typeOf(compVal) == "component")) ? forEach(function(d) { return append_b(diagnostics, d); }, checkComponentCall(name, compVal, args, typeEnv, primTypes, typeRegistry)) : NIL);
})();
if (isSxTruthy(!isSxTruthy(isNil(effectAnnotations)))) {
(function() {
var callerEffects = getEffects(compName, effectAnnotations);
return forEach(function(d) { return append_b(diagnostics, d); }, checkEffectCall(name, callerEffects, effectAnnotations, compName));
})();
}
}
if (isSxTruthy((isSxTruthy(!isSxTruthy(startsWith(name, "~"))) && isSxTruthy(!isSxTruthy(isNil(primParamTypes))) && dictHas(primParamTypes, name)))) {
{ var _c = checkPrimitiveCall(name, args, typeEnv, primTypes, primParamTypes, compName, typeRegistry); for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; diagnostics.push(d); } }
}
if (isSxTruthy((isSxTruthy(!isSxTruthy(startsWith(name, "~"))) && !isSxTruthy(isNil(effectAnnotations))))) {
(function() {
var callerEffects = getEffects(compName, effectAnnotations);
return forEach(function(d) { return append_b(diagnostics, d); }, checkEffectCall(name, callerEffects, effectAnnotations, compName));
})();
}
if (isSxTruthy(sxOr((name == "let"), (name == "let*")))) {
if (isSxTruthy((len(args) >= 2))) {
(function() {
var bindings = first(args);
var bodyExprs = rest(args);
var extended = merge(typeEnv, {});
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var binding = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(binding) == "list")) && (len(binding) >= 2)))) {
(function() {
var bname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : (String(first(binding))));
var valType = inferType(nth(binding, 1), extended, primTypes, typeRegistry);
return dictSet(extended, bname, valType);
})();
} } }
return forEach(function(body) { return checkBodyWalk(body, compName, extended, primTypes, primParamTypes, env, diagnostics, typeRegistry, effectAnnotations); }, bodyExprs);
})();
}
}
return (isSxTruthy((name == "define")) ? (isSxTruthy((len(args) >= 2)) ? (function() {
var defName = (isSxTruthy((typeOf(first(args)) == "symbol")) ? symbolName(first(args)) : NIL);
var defVal = nth(args, 1);
if (isSxTruthy(defName)) {
typeEnv[defName] = inferType(defVal, typeEnv, primTypes, typeRegistry);
}
return checkBodyWalk(defVal, compName, typeEnv, primTypes, primParamTypes, env, diagnostics, typeRegistry, effectAnnotations);
})() : NIL) : NIL);
})();
}
return forEach(function(child) { return checkBodyWalk(child, compName, typeEnv, primTypes, primParamTypes, env, diagnostics, typeRegistry, effectAnnotations); }, args);
})() : NIL) : NIL);
})(); };
PRIMITIVES["check-body-walk"] = checkBodyWalk;
// check-component
var checkComponent = function(compName, env, primTypes, primParamTypes, typeRegistry, effectAnnotations) { return (function() {
var comp = envGet(env, compName);
var diagnostics = [];
if (isSxTruthy((typeOf(comp) == "component"))) {
(function() {
var body = componentBody(comp);
var params = componentParams(comp);
var paramTypes = componentParamTypes(comp);
var typeEnv = {};
{ var _c = params; for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; typeEnv[p] = (isSxTruthy((isSxTruthy(!isSxTruthy(isNil(paramTypes))) && dictHas(paramTypes, p))) ? get(paramTypes, p) : "any"); } }
if (isSxTruthy(componentHasChildren(comp))) {
typeEnv["children"] = ["list-of", "element"];
}
return checkBodyWalk(body, compName, typeEnv, primTypes, primParamTypes, env, diagnostics, typeRegistry, effectAnnotations);
})();
}
return diagnostics;
})(); };
PRIMITIVES["check-component"] = checkComponent;
// check-all
var checkAll = function(env, primTypes, primParamTypes, typeRegistry, effectAnnotations) { return (function() {
var allDiagnostics = [];
{ var _c = keys(env); for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() {
var val = envGet(env, name);
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(d) { return append_b(allDiagnostics, d); }, checkComponent(name, env, primTypes, primParamTypes, typeRegistry, effectAnnotations)) : NIL);
})(); } }
return allDiagnostics;
})(); };
PRIMITIVES["check-all"] = checkAll;
// build-type-registry
var buildTypeRegistry = function(primDeclarations, ioDeclarations) { return (function() {
var registry = {};
{ var _c = primDeclarations; for (var _i = 0; _i < _c.length; _i++) { var decl = _c[_i]; (function() {
var name = get(decl, "name");
var returns = get(decl, "returns");
return (isSxTruthy((isSxTruthy(!isSxTruthy(isNil(name))) && !isSxTruthy(isNil(returns)))) ? dictSet(registry, name, returns) : NIL);
})(); } }
{ var _c = ioDeclarations; for (var _i = 0; _i < _c.length; _i++) { var decl = _c[_i]; (function() {
var name = get(decl, "name");
var returns = get(decl, "returns");
return (isSxTruthy((isSxTruthy(!isSxTruthy(isNil(name))) && !isSxTruthy(isNil(returns)))) ? dictSet(registry, name, returns) : NIL);
})(); } }
return registry;
})(); };
PRIMITIVES["build-type-registry"] = buildTypeRegistry;
// type-def-name
var typeDefName = function(td) { return get(td, "name"); };
PRIMITIVES["type-def-name"] = typeDefName;
// type-def-params
var typeDefParams = function(td) { return get(td, "params"); };
PRIMITIVES["type-def-params"] = typeDefParams;
// type-def-body
var typeDefBody = function(td) { return get(td, "body"); };
PRIMITIVES["type-def-body"] = typeDefBody;
// resolve-type
var resolveType = function(t, registry) { return (isSxTruthy(isNil(registry)) ? t : (isSxTruthy((typeOf(t) == "string")) ? (isSxTruthy(dictHas(registry, t)) ? (function() {
var td = get(registry, t);
return (function() {
var params = typeDefParams(td);
var body = typeDefBody(td);
return (isSxTruthy(isEmpty(params)) ? resolveType(body, registry) : t);
})();
})() : t) : (isSxTruthy((typeOf(t) == "list")) ? (isSxTruthy(isEmpty(t)) ? t : (function() {
var head = first(t);
return (isSxTruthy(sxOr((head == "or"), (head == "list-of"), (head == "->"), (head == "dict-of"))) ? cons(head, map(function(m) { return resolveType(m, registry); }, rest(t))) : (isSxTruthy((isSxTruthy((typeOf(head) == "string")) && dictHas(registry, head))) ? (function() {
var td = get(registry, head);
var params = typeDefParams(td);
var body = typeDefBody(td);
var args = rest(t);
return (isSxTruthy((len(params) == len(args))) ? resolveType(substituteTypeVars(body, params, args), registry) : t);
})() : t));
})()) : (isSxTruthy((typeOf(t) == "dict")) ? mapDict(function(k, v) { return resolveType(v, registry); }, t) : t)))); };
PRIMITIVES["resolve-type"] = resolveType;
// substitute-type-vars
var substituteTypeVars = function(body, params, args) { return (function() {
var subst = {};
{ var _c = range(0, len(params), 1); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; subst[nth(params, i)] = nth(args, i); } }
return substituteInType(body, subst);
})(); };
PRIMITIVES["substitute-type-vars"] = substituteTypeVars;
// substitute-in-type
var substituteInType = function(t, subst) { return (isSxTruthy((typeOf(t) == "string")) ? (isSxTruthy(dictHas(subst, t)) ? get(subst, t) : t) : (isSxTruthy((typeOf(t) == "list")) ? map(function(m) { return substituteInType(m, subst); }, t) : (isSxTruthy((typeOf(t) == "dict")) ? mapDict(function(k, v) { return substituteInType(v, subst); }, t) : t))); };
PRIMITIVES["substitute-in-type"] = substituteInType;
// subtype-resolved?
var subtypeResolved_p = function(a, b, registry) { return (isSxTruthy(isNil(registry)) ? subtype_p(a, b) : (function() {
var ra = resolveType(a, registry);
var rb = resolveType(b, registry);
return (isSxTruthy((isSxTruthy((typeOf(ra) == "dict")) && (typeOf(rb) == "dict"))) ? isEvery(function(key) { return (isSxTruthy(dictHas(ra, key)) && subtypeResolved_p(get(ra, key), get(rb, key), registry)); }, keys(rb)) : subtype_p(ra, rb));
})()); };
PRIMITIVES["subtype-resolved?"] = subtypeResolved_p;
// get-effects
var getEffects = function(name, effectAnnotations) { return (isSxTruthy(isNil(effectAnnotations)) ? NIL : (isSxTruthy(dictHas(effectAnnotations, name)) ? get(effectAnnotations, name) : NIL)); };
PRIMITIVES["get-effects"] = getEffects;
// effects-subset?
var effectsSubset_p = function(calleeEffects, callerEffects) { return (isSxTruthy(isNil(callerEffects)) ? true : (isSxTruthy(isNil(calleeEffects)) ? true : isEvery(function(e) { return contains(callerEffects, e); }, calleeEffects))); };
PRIMITIVES["effects-subset?"] = effectsSubset_p;
// check-effect-call
var checkEffectCall = function(calleeName, callerEffects, effectAnnotations, compName) { return (function() {
var diagnostics = [];
var calleeEffects = getEffects(calleeName, effectAnnotations);
if (isSxTruthy((isSxTruthy(!isSxTruthy(isNil(callerEffects))) && isSxTruthy(!isSxTruthy(isNil(calleeEffects))) && !isSxTruthy(effectsSubset_p(calleeEffects, callerEffects))))) {
diagnostics.push(makeDiagnostic("error", (String("`") + String(calleeName) + String("` has effects ") + String(join(", ", calleeEffects)) + String(" but `") + String(compName) + String("` only allows ") + String((isSxTruthy(isEmpty(callerEffects)) ? "[pure]" : join(", ", callerEffects)))), compName, NIL));
}
return diagnostics;
})(); };
PRIMITIVES["check-effect-call"] = checkEffectCall;
// build-effect-annotations
var buildEffectAnnotations = function(ioDeclarations) { return (function() {
var annotations = {};
{ var _c = ioDeclarations; for (var _i = 0; _i < _c.length; _i++) { var decl = _c[_i]; (function() {
var name = get(decl, "name");
return (isSxTruthy(!isSxTruthy(isNil(name))) ? dictSet(annotations, name, ["io"]) : NIL);
})(); } }
return annotations;
})(); };
PRIMITIVES["build-effect-annotations"] = buildEffectAnnotations;
// check-component-effects
var checkComponentEffects = function(compName, env, effectAnnotations) { return (function() {
var comp = envGet(env, compName);
var diagnostics = [];
if (isSxTruthy((typeOf(comp) == "component"))) {
(function() {
var body = componentBody(comp);
return checkBodyWalk(body, compName, {}, {}, NIL, env, diagnostics, NIL, effectAnnotations);
})();
}
return diagnostics;
})(); };
PRIMITIVES["check-component-effects"] = checkComponentEffects;
// check-all-effects
var checkAllEffects = function(env, effectAnnotations) { return (function() {
var allDiagnostics = [];
{ var _c = keys(env); for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() {
var val = envGet(env, name);
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(d) { return append_b(allDiagnostics, d); }, checkComponentEffects(name, env, effectAnnotations)) : NIL);
})(); } }
return allDiagnostics;
})(); };
PRIMITIVES["check-all-effects"] = checkAllEffects;
// =========================================================================
// Post-transpilation fixups
// =========================================================================

View File

@@ -504,7 +504,7 @@
"[data-sx-signal]"
"for-each"
(code :arity 1
:bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 33 94 0 16 1 1 3 0 52 2 0 2 17 2 16 2 1 5 0 52 4 0 2 33 67 0 16 1 1 5 0 16 2 52 6 0 3 17 3 16 1 16 2 1 8 0 52 7 0 2 52 6 0 2 17 4 20 9 0 16 4 48 1 17 5 20 10 0 20 11 0 16 3 48 1 16 5 48 2 5 20 12 0 16 0 1 1 0 49 2 32 1 0 2 32 1 0 2 50)
:bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 33 93 0 16 1 1 3 0 52 2 0 2 17 2 16 2 1 5 0 52 4 0 2 33 66 0 16 1 1 5 0 16 2 52 6 0 3 17 3 16 1 16 2 1 8 0 52 7 0 2 52 6 0 2 17 4 20 9 0 16 4 48 1 17 5 20 10 0 16 3 52 11 0 1 16 5 48 2 5 20 12 0 16 0 1 1 0 49 2 32 1 0 2 32 1 0 2 50)
:constants (
"dom-get-attr"
"data-sx-signal"

File diff suppressed because one or more lines are too long

View File

@@ -1792,7 +1792,7 @@
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
}
(globalThis))
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-6f7dfa09",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-6b156118",[2,3,5]],["std_exit-10fb8830",[2]],["start-80fdb768",0]],"generated":(b=>{var
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-59456384",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-ec413294",[2,3,5]],["std_exit-10fb8830",[2]],["start-80fdb768",0]],"generated":(b=>{var
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new

View File

@@ -176,10 +176,7 @@ test.describe('Isomorphic SSR', () => {
expect(brokenLinks).toEqual([]);
});
test.fixme('navigation preserves header island state', async ({ page }) => {
// BUG: client? primitive works from K.eval but not during render-to-dom hydration.
// Needs JIT/env investigation — the pre-existing JIT "Not callable: nil" bug
// prevents primitives registered after boot from being called during component eval.
test('navigation preserves header island state', async ({ page }) => {
await page.goto(BASE_URL + '/sx/', { waitUntil: 'networkidle' });
// Wait for header island to hydrate
@@ -200,13 +197,9 @@ test.describe('Isomorphic SSR', () => {
await geoLink.click();
await page.waitForTimeout(2000);
// Colour should be preserved (island not disposed)
// Colour should be preserved (def-store keeps signals across re-hydration)
const colourAfter = await reactive.evaluate(el => el.style.color);
expect(colourAfter).toBe(colourBefore);
// Copyright path should update
const copyright = page.locator('[data-sx-lake="copyright"]');
await expect(copyright).toContainText('geography');
});
});