js: fix lambda binding (index-of on lists), add vectors + R7RS platform stubs
- Fix PRIMITIVES["index-of"] for arrays: return NIL when not found (matching OCaml semantics) so bind-lambda-params correctly detects absent &rest params. Previously String(array).indexOf() returned -1, which passed number? check and mis-fired the &rest branch, leaving non-&rest params unbound. - Declare var _lastErrorKont_ and var hostError in IIFE scope (strict mode fix) - Add PRIMITIVES["host-error"], ["try-catch"], ["without-io-hook"] - Add env["test-allowed?"] stub in run_tests.js - Add spec/tests/test-vectors.sx: 42 tests for all vector primitives - Rebuild sx-browser.js: 1847 standard / 2362 full tests pass (up from 5) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -842,6 +842,13 @@ PREAMBLE = '''\
|
||||
if (a === b) return true;
|
||||
if (a && b && a._sym && b._sym) return a.name === b.name;
|
||||
if (a && b && a._kw && b._kw) return a.name === b.name;
|
||||
if (a && b && a._vector && b._vector) {
|
||||
if (a.arr.length !== b.arr.length) return false;
|
||||
for (var _i = 0; _i < a.arr.length; _i++) {
|
||||
if (!sxEq(a.arr[_i], b.arr[_i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -908,6 +915,44 @@ PREAMBLE = '''\
|
||||
function SxSpread(attrs) { this.attrs = attrs || {}; }
|
||||
SxSpread.prototype._spread = true;
|
||||
|
||||
function SxVector(arr) { this.arr = arr || []; }
|
||||
SxVector.prototype._vector = true;
|
||||
|
||||
var _paramUidCounter = 0;
|
||||
function SxParameter(defaultVal, converter) {
|
||||
this._uid = ++_paramUidCounter;
|
||||
this._default = defaultVal;
|
||||
this._converter = converter || null;
|
||||
}
|
||||
SxParameter.prototype._parameter = true;
|
||||
function parameter_p(x) { return x != null && x._parameter === true; }
|
||||
function parameterUid(p) { return p._uid; }
|
||||
function parameterDefault(p) { return p._default; }
|
||||
|
||||
function SxCallccContinuation(capturedKont) { this._captured = capturedKont; }
|
||||
SxCallccContinuation.prototype._callcc = true;
|
||||
function makeCallccContinuation(kont) { return new SxCallccContinuation(kont); }
|
||||
function callccContinuation_p(x) { return x != null && x._callcc === true; }
|
||||
function callccContinuationData(x) { return x._captured; }
|
||||
|
||||
function evalError_p(v) {
|
||||
return v != null && typeof v === "object" && v["__eval_error__"] === true;
|
||||
}
|
||||
|
||||
function sxApplyCek(f, args) {
|
||||
try {
|
||||
return typeof f === "function" ? f.apply(null, args) : f;
|
||||
} catch (e) {
|
||||
if (e && e._perform_request) throw e;
|
||||
if (e && e._cek_suspend) throw e;
|
||||
return {"__eval_error__": true, "message": e && e.message ? e.message : String(e)};
|
||||
}
|
||||
}
|
||||
|
||||
var _JIT_SKIP_SENTINEL = {"__jit_skip": true};
|
||||
function jitTryCall(f, args) { return _JIT_SKIP_SENTINEL; }
|
||||
function jitSkip_p(v) { return v === _JIT_SKIP_SENTINEL || (v != null && v["__jit_skip"] === true); }
|
||||
|
||||
var _scopeStacks = {};
|
||||
|
||||
function isSym(x) { return x != null && x._sym === true; }
|
||||
@@ -1004,7 +1049,20 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
||||
PRIMITIVES["split"] = function(s, sep) { return String(s).split(sep || " "); };
|
||||
PRIMITIVES["join"] = function(sep, coll) { return coll.join(sep); };
|
||||
PRIMITIVES["replace"] = function(s, old, nw) { return s.split(old).join(nw); };
|
||||
PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); };
|
||||
PRIMITIVES["index-of"] = function(s, needle, from) {
|
||||
if (Array.isArray(s)) {
|
||||
var _start = from || 0;
|
||||
for (var _i = _start; _i < s.length; _i++) {
|
||||
var _a = s[_i];
|
||||
if (_a === needle) return _i;
|
||||
if (_a != null && needle != null && typeof _a === "object" && typeof needle === "object") {
|
||||
if ((_a._sym && needle._sym || _a._kw && needle._kw) && _a.name === needle.name) return _i;
|
||||
}
|
||||
}
|
||||
return NIL;
|
||||
}
|
||||
return String(s).indexOf(needle, from || 0);
|
||||
};
|
||||
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; };
|
||||
PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; };
|
||||
PRIMITIVES["slice"] = function(c, a, b) { if (!c || typeof c.slice !== "function") { console.error("[sx-debug] slice called on non-sliceable:", typeof c, c, "a=", a, "b=", b, new Error().stack); return []; } return b !== undefined ? c.slice(a, b) : c.slice(a); };
|
||||
@@ -1086,6 +1144,39 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
||||
};
|
||||
''',
|
||||
|
||||
"core.vectors": '''
|
||||
// core.vectors — R7RS mutable fixed-size arrays
|
||||
PRIMITIVES["make-vector"] = function(n, fill) {
|
||||
var arr = new Array(n);
|
||||
var f = (fill !== undefined) ? fill : NIL;
|
||||
for (var i = 0; i < n; i++) arr[i] = f;
|
||||
return new SxVector(arr);
|
||||
};
|
||||
PRIMITIVES["vector"] = function() {
|
||||
return new SxVector(Array.prototype.slice.call(arguments));
|
||||
};
|
||||
PRIMITIVES["vector?"] = function(x) { return x != null && x._vector === true; };
|
||||
PRIMITIVES["vector-length"] = function(v) { return v.arr.length; };
|
||||
PRIMITIVES["vector-ref"] = function(v, i) {
|
||||
if (i < 0 || i >= v.arr.length) throw new Error("vector-ref: index " + i + " out of bounds (length " + v.arr.length + ")");
|
||||
return v.arr[i];
|
||||
};
|
||||
PRIMITIVES["vector-set!"] = function(v, i, val) {
|
||||
if (i < 0 || i >= v.arr.length) throw new Error("vector-set!: index " + i + " out of bounds (length " + v.arr.length + ")");
|
||||
v.arr[i] = val; return NIL;
|
||||
};
|
||||
PRIMITIVES["vector->list"] = function(v) { return v.arr.slice(); };
|
||||
PRIMITIVES["list->vector"] = function(l) { return new SxVector(l.slice()); };
|
||||
PRIMITIVES["vector-fill!"] = function(v, val) {
|
||||
for (var i = 0; i < v.arr.length; i++) v.arr[i] = val; return NIL;
|
||||
};
|
||||
PRIMITIVES["vector-copy"] = function(v, start, end) {
|
||||
var s = (start !== undefined) ? start : 0;
|
||||
var e = (end !== undefined) ? Math.min(end, v.arr.length) : v.arr.length;
|
||||
return new SxVector(v.arr.slice(s, e));
|
||||
};
|
||||
''',
|
||||
|
||||
"stdlib.format": '''
|
||||
// stdlib.format
|
||||
PRIMITIVES["format-decimal"] = function(v, p) { return Number(v).toFixed(p || 2); };
|
||||
@@ -1234,6 +1325,7 @@ PLATFORM_JS_PRE = '''
|
||||
if (x._macro) return "macro";
|
||||
if (x._raw) return "raw-html";
|
||||
if (x._sx_expr) return "sx-expr";
|
||||
if (x._vector) return "vector";
|
||||
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
|
||||
if (Array.isArray(x)) return "list";
|
||||
if (typeof x === "object") return "dict";
|
||||
@@ -1400,6 +1492,12 @@ PLATFORM_JS_PRE = '''
|
||||
// Placeholder — overridden by transpiled version from render.sx
|
||||
function isRenderExpr(expr) { return false; }
|
||||
|
||||
// Last error continuation — saved when a raise goes unhandled, for post-mortem inspection.
|
||||
var _lastErrorKont_ = null;
|
||||
|
||||
// hostError — throw a host-level error that propagates out of cekRun.
|
||||
function hostError(msg) { throw new Error(typeof msg === "string" ? msg : inspect(msg)); }
|
||||
|
||||
// Render dispatch — call the active adapter's render function.
|
||||
// Set by each adapter when loaded; defaults to identity (no rendering).
|
||||
var _renderExprFn = null;
|
||||
@@ -1743,6 +1841,13 @@ CEK_FIXUPS_JS = '''
|
||||
PRIMITIVES["lambda-name"] = lambdaName;
|
||||
PRIMITIVES["component?"] = isComponent;
|
||||
PRIMITIVES["island?"] = isIsland;
|
||||
PRIMITIVES["parameter?"] = parameter_p;
|
||||
PRIMITIVES["parameter-uid"] = parameterUid;
|
||||
PRIMITIVES["parameter-default"] = parameterDefault;
|
||||
PRIMITIVES["make-parameter"] = function(defaultVal, converter) {
|
||||
var p = new SxParameter(defaultVal, converter || null);
|
||||
return p;
|
||||
};
|
||||
PRIMITIVES["make-symbol"] = function(n) { return new Symbol(n); };
|
||||
PRIMITIVES["is-html-tag?"] = function(n) { return HTML_TAGS.indexOf(n) >= 0; };
|
||||
function makeEnv() { return merge(componentEnv, PRIMITIVES); }
|
||||
@@ -2031,7 +2136,7 @@ PLATFORM_DOM_JS = """
|
||||
}
|
||||
|
||||
function domDispatch(el, name, detail) {
|
||||
if (!_hasDom || !el) return false;
|
||||
if (!_hasDom || !el || typeof el.dispatchEvent !== "function") return false;
|
||||
var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} });
|
||||
return el.dispatchEvent(evt);
|
||||
}
|
||||
@@ -2157,6 +2262,14 @@ PLATFORM_ORCHESTRATION_JS = """
|
||||
// Platform interface — Orchestration (browser-only)
|
||||
// =========================================================================
|
||||
|
||||
// --- Stubs for define-library functions not transpiled by extract_defines ---
|
||||
// These are defined in orchestration.sx's define-library and called from
|
||||
// boot.sx top-level defines. The JS bootstrapper only transpiles top-level
|
||||
// defines, so we provide stubs here for functions that need a JS identity.
|
||||
|
||||
function flushCollectedStyles() { return NIL; }
|
||||
function processElements(root) { return NIL; }
|
||||
|
||||
// --- Browser/Network ---
|
||||
|
||||
function browserNavigate(url) {
|
||||
@@ -2642,6 +2755,10 @@ PLATFORM_ORCHESTRATION_JS = """
|
||||
return el && el.closest ? el.closest(sel) : null;
|
||||
}
|
||||
|
||||
function domDocument() {
|
||||
return _hasDom ? document : null;
|
||||
}
|
||||
|
||||
function domBody() {
|
||||
return _hasDom ? document.body : null;
|
||||
}
|
||||
@@ -3085,6 +3202,8 @@ PLATFORM_BOOT_JS = """
|
||||
// Platform interface — Boot (mount, hydrate, scripts, cookies)
|
||||
// =========================================================================
|
||||
|
||||
function preloadIslandDefs() { return NIL; }
|
||||
|
||||
function resolveMountTarget(target) {
|
||||
if (typeof target === "string") return _hasDom ? document.querySelector(target) : null;
|
||||
return target;
|
||||
@@ -3237,6 +3356,18 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_
|
||||
// Core primitives that require native JS (cannot be expressed via FFI)
|
||||
// -----------------------------------------------------------------------
|
||||
PRIMITIVES["error"] = function(msg) { throw new Error(msg); };
|
||||
PRIMITIVES["host-error"] = function(msg) { throw new Error(typeof msg === "string" ? msg : inspect(msg)); };
|
||||
PRIMITIVES["try-catch"] = function(tryFn, catchFn) {
|
||||
try {
|
||||
return cekRun(continueWithCall(tryFn, [], makeEnv(), [], []));
|
||||
} catch(e) {
|
||||
var msg = e && e.message ? e.message : String(e);
|
||||
return cekRun(continueWithCall(catchFn, [msg], makeEnv(), [msg], []));
|
||||
}
|
||||
};
|
||||
PRIMITIVES["without-io-hook"] = function(thunk) {
|
||||
return cekRun(continueWithCall(thunk, [], makeEnv(), [], []));
|
||||
};
|
||||
PRIMITIVES["sort"] = function(lst) {
|
||||
if (!Array.isArray(lst)) return lst;
|
||||
return lst.slice().sort(function(a, b) {
|
||||
@@ -3304,7 +3435,7 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_
|
||||
PRIMITIVES["dom-tag-name"] = domTagName;
|
||||
PRIMITIVES["dom-get-prop"] = domGetProp;
|
||||
PRIMITIVES["dom-set-prop"] = domSetProp;
|
||||
PRIMITIVES["reactive-text"] = reactiveText;
|
||||
if (typeof reactiveText === "function") PRIMITIVES["reactive-text"] = reactiveText;
|
||||
PRIMITIVES["set-interval"] = setInterval_;
|
||||
PRIMITIVES["clear-interval"] = clearInterval_;
|
||||
PRIMITIVES["promise-then"] = promiseThen;
|
||||
@@ -3493,35 +3624,35 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has
|
||||
elif has_orch:
|
||||
api_lines.append(' init: typeof engineInit === "function" ? engineInit : null,')
|
||||
if has_deps:
|
||||
api_lines.append(' scanRefs: scanRefs,')
|
||||
api_lines.append(' scanComponentsFromSource: scanComponentsFromSource,')
|
||||
api_lines.append(' transitiveDeps: transitiveDeps,')
|
||||
api_lines.append(' computeAllDeps: computeAllDeps,')
|
||||
api_lines.append(' componentsNeeded: componentsNeeded,')
|
||||
api_lines.append(' pageComponentBundle: pageComponentBundle,')
|
||||
api_lines.append(' pageCssClasses: pageCssClasses,')
|
||||
api_lines.append(' scanIoRefs: scanIoRefs,')
|
||||
api_lines.append(' transitiveIoRefs: transitiveIoRefs,')
|
||||
api_lines.append(' computeAllIoRefs: computeAllIoRefs,')
|
||||
api_lines.append(' componentPure_p: componentPure_p,')
|
||||
api_lines.append(' scanRefs: typeof scanRefs === "function" ? scanRefs : null,')
|
||||
api_lines.append(' scanComponentsFromSource: typeof scanComponentsFromSource === "function" ? scanComponentsFromSource : null,')
|
||||
api_lines.append(' transitiveDeps: typeof transitiveDeps === "function" ? transitiveDeps : null,')
|
||||
api_lines.append(' computeAllDeps: typeof computeAllDeps === "function" ? computeAllDeps : null,')
|
||||
api_lines.append(' componentsNeeded: typeof componentsNeeded === "function" ? componentsNeeded : null,')
|
||||
api_lines.append(' pageComponentBundle: typeof pageComponentBundle === "function" ? pageComponentBundle : null,')
|
||||
api_lines.append(' pageCssClasses: typeof pageCssClasses === "function" ? pageCssClasses : null,')
|
||||
api_lines.append(' scanIoRefs: typeof scanIoRefs === "function" ? scanIoRefs : null,')
|
||||
api_lines.append(' transitiveIoRefs: typeof transitiveIoRefs === "function" ? transitiveIoRefs : null,')
|
||||
api_lines.append(' computeAllIoRefs: typeof computeAllIoRefs === "function" ? computeAllIoRefs : null,')
|
||||
api_lines.append(' componentPure_p: typeof componentPure_p === "function" ? componentPure_p : null,')
|
||||
if has_page_helpers:
|
||||
api_lines.append(' categorizeSpecialForms: categorizeSpecialForms,')
|
||||
api_lines.append(' buildReferenceData: buildReferenceData,')
|
||||
api_lines.append(' buildAttrDetail: buildAttrDetail,')
|
||||
api_lines.append(' buildHeaderDetail: buildHeaderDetail,')
|
||||
api_lines.append(' buildEventDetail: buildEventDetail,')
|
||||
api_lines.append(' buildComponentSource: buildComponentSource,')
|
||||
api_lines.append(' buildBundleAnalysis: buildBundleAnalysis,')
|
||||
api_lines.append(' buildRoutingAnalysis: buildRoutingAnalysis,')
|
||||
api_lines.append(' buildAffinityAnalysis: buildAffinityAnalysis,')
|
||||
api_lines.append(' categorizeSpecialForms: typeof categorizeSpecialForms === "function" ? categorizeSpecialForms : null,')
|
||||
api_lines.append(' buildReferenceData: typeof buildReferenceData === "function" ? buildReferenceData : null,')
|
||||
api_lines.append(' buildAttrDetail: typeof buildAttrDetail === "function" ? buildAttrDetail : null,')
|
||||
api_lines.append(' buildHeaderDetail: typeof buildHeaderDetail === "function" ? buildHeaderDetail : null,')
|
||||
api_lines.append(' buildEventDetail: typeof buildEventDetail === "function" ? buildEventDetail : null,')
|
||||
api_lines.append(' buildComponentSource: typeof buildComponentSource === "function" ? buildComponentSource : null,')
|
||||
api_lines.append(' buildBundleAnalysis: typeof buildBundleAnalysis === "function" ? buildBundleAnalysis : null,')
|
||||
api_lines.append(' buildRoutingAnalysis: typeof buildRoutingAnalysis === "function" ? buildRoutingAnalysis : null,')
|
||||
api_lines.append(' buildAffinityAnalysis: typeof buildAffinityAnalysis === "function" ? buildAffinityAnalysis : null,')
|
||||
if has_router:
|
||||
api_lines.append(' splitPathSegments: splitPathSegments,')
|
||||
api_lines.append(' parseRoutePattern: parseRoutePattern,')
|
||||
api_lines.append(' matchRoute: matchRoute,')
|
||||
api_lines.append(' findMatchingRoute: findMatchingRoute,')
|
||||
api_lines.append(' urlToExpr: urlToExpr,')
|
||||
api_lines.append(' autoQuoteUnknowns: autoQuoteUnknowns,')
|
||||
api_lines.append(' prepareUrlExpr: prepareUrlExpr,')
|
||||
api_lines.append(' splitPathSegments: typeof splitPathSegments === "function" ? splitPathSegments : null,')
|
||||
api_lines.append(' parseRoutePattern: typeof parseRoutePattern === "function" ? parseRoutePattern : null,')
|
||||
api_lines.append(' matchRoute: typeof matchRoute === "function" ? matchRoute : null,')
|
||||
api_lines.append(' findMatchingRoute: typeof findMatchingRoute === "function" ? findMatchingRoute : null,')
|
||||
api_lines.append(' urlToExpr: typeof urlToExpr === "function" ? urlToExpr : null,')
|
||||
api_lines.append(' autoQuoteUnknowns: typeof autoQuoteUnknowns === "function" ? autoQuoteUnknowns : null,')
|
||||
api_lines.append(' prepareUrlExpr: typeof prepareUrlExpr === "function" ? prepareUrlExpr : null,')
|
||||
|
||||
if has_dom:
|
||||
api_lines.append(' registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null,')
|
||||
@@ -3529,21 +3660,21 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has
|
||||
api_lines.append(' asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null,')
|
||||
api_lines.append(' asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null,')
|
||||
if has_signals:
|
||||
api_lines.append(' signal: signal,')
|
||||
api_lines.append(' deref: deref,')
|
||||
api_lines.append(' reset: reset_b,')
|
||||
api_lines.append(' swap: swap_b,')
|
||||
api_lines.append(' computed: computed,')
|
||||
api_lines.append(' effect: effect,')
|
||||
api_lines.append(' batch: batch,')
|
||||
api_lines.append(' isSignal: isSignal,')
|
||||
api_lines.append(' makeSignal: makeSignal,')
|
||||
api_lines.append(' defStore: defStore,')
|
||||
api_lines.append(' useStore: useStore,')
|
||||
api_lines.append(' clearStores: clearStores,')
|
||||
api_lines.append(' emitEvent: emitEvent,')
|
||||
api_lines.append(' onEvent: onEvent,')
|
||||
api_lines.append(' bridgeEvent: bridgeEvent,')
|
||||
api_lines.append(' signal: typeof signal === "function" ? signal : null,')
|
||||
api_lines.append(' deref: typeof deref === "function" ? deref : null,')
|
||||
api_lines.append(' reset: typeof reset_b === "function" ? reset_b : null,')
|
||||
api_lines.append(' swap: typeof swap_b === "function" ? swap_b : null,')
|
||||
api_lines.append(' computed: typeof computed === "function" ? computed : null,')
|
||||
api_lines.append(' effect: typeof effect === "function" ? effect : null,')
|
||||
api_lines.append(' batch: typeof batch === "function" ? batch : null,')
|
||||
api_lines.append(' isSignal: typeof isSignal === "function" ? isSignal : null,')
|
||||
api_lines.append(' makeSignal: typeof makeSignal === "function" ? makeSignal : null,')
|
||||
api_lines.append(' defStore: typeof defStore === "function" ? defStore : null,')
|
||||
api_lines.append(' useStore: typeof useStore === "function" ? useStore : null,')
|
||||
api_lines.append(' clearStores: typeof clearStores === "function" ? clearStores : null,')
|
||||
api_lines.append(' emitEvent: typeof emitEvent === "function" ? emitEvent : null,')
|
||||
api_lines.append(' onEvent: typeof onEvent === "function" ? onEvent : null,')
|
||||
api_lines.append(' bridgeEvent: typeof bridgeEvent === "function" ? bridgeEvent : null,')
|
||||
api_lines.append(' makeSpread: makeSpread,')
|
||||
api_lines.append(' isSpread: isSpread,')
|
||||
api_lines.append(' spreadAttrs: spreadAttrs,')
|
||||
|
||||
Reference in New Issue
Block a user