diff --git a/hosts/javascript/platform.py b/hosts/javascript/platform.py index 5abc372f..083fd27b 100644 --- a/hosts/javascript/platform.py +++ b/hosts/javascript/platform.py @@ -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,') diff --git a/hosts/javascript/run_tests.js b/hosts/javascript/run_tests.js index a142f1bc..08a64f48 100644 --- a/hosts/javascript/run_tests.js +++ b/hosts/javascript/run_tests.js @@ -293,6 +293,8 @@ env["pop-suite"] = function() { return null; }; +env["test-allowed?"] = function(name) { return true; }; + // Load test framework const projectDir = path.join(__dirname, "..", ".."); const specTests = path.join(projectDir, "spec", "tests"); diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index ab5bb034..167e2d62 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -16,6 +16,13 @@ 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; } @@ -24,7 +31,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-04-05T11:01:51Z"; + var SX_VERSION = "2026-04-26T10:01:22Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -82,6 +89,44 @@ 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; } @@ -122,6 +167,7 @@ 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"; @@ -288,6 +334,12 @@ // 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; @@ -390,7 +442,20 @@ 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); }; @@ -470,6 +535,38 @@ }; + // 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 PRIMITIVES["format-decimal"] = function(v, p) { return Number(v).toFixed(p || 2); }; PRIMITIVES["parse-int"] = function(v, d) { var n = parseInt(v, 10); return isNaN(n) ? (d || 0) : n; }; @@ -1029,6 +1126,10 @@ PRIMITIVES["make-let-frame"] = makeLetFrame; var makeDefineFrame = function(name, env, hasEffects, effectList) { return {"env": env, "effect-list": effectList, "has-effects": hasEffects, "type": "define", "name": name}; }; PRIMITIVES["make-define-frame"] = makeDefineFrame; + // make-define-foreign-frame + var makeDefineForeignFrame = function(name, spec, env) { return {"spec": spec, "env": env, "type": "define-foreign", "name": name}; }; +PRIMITIVES["make-define-foreign-frame"] = makeDefineForeignFrame; + // make-set-frame var makeSetFrame = function(name, env) { return {"env": env, "type": "set", "name": name}; }; PRIMITIVES["make-set-frame"] = makeSetFrame; @@ -1321,6 +1422,18 @@ PRIMITIVES["*render-fn*"] = _renderFn; var _bindTracking_ = NIL; PRIMITIVES["*bind-tracking*"] = _bindTracking_; + // *provide-batch-depth* + var _provideBatchDepth_ = 0; +PRIMITIVES["*provide-batch-depth*"] = _provideBatchDepth_; + + // *provide-batch-queue* + var _provideBatchQueue_ = []; +PRIMITIVES["*provide-batch-queue*"] = _provideBatchQueue_; + + // *provide-subscribers* + var _provideSubscribers_ = {}; +PRIMITIVES["*provide-subscribers*"] = _provideSubscribers_; + // *library-registry* var _libraryRegistry_ = {}; PRIMITIVES["*library-registry*"] = _libraryRegistry_; @@ -1361,6 +1474,132 @@ PRIMITIVES["io-lookup"] = ioLookup; var ioNames = function() { return keys(_ioRegistry_); }; PRIMITIVES["io-names"] = ioNames; + // *foreign-registry* + var _foreignRegistry_ = {}; +PRIMITIVES["*foreign-registry*"] = _foreignRegistry_; + + // foreign-register! + var foreignRegister_b = function(name, spec) { return dictSet(_foreignRegistry_, name, spec); }; +PRIMITIVES["foreign-register!"] = foreignRegister_b; + + // foreign-registered? + var foreignRegistered_p = function(name) { return dictHas(_foreignRegistry_, name); }; +PRIMITIVES["foreign-registered?"] = foreignRegistered_p; + + // foreign-lookup + var foreignLookup = function(name) { return get(_foreignRegistry_, name); }; +PRIMITIVES["foreign-lookup"] = foreignLookup; + + // foreign-names + var foreignNames = function() { return keys(_foreignRegistry_); }; +PRIMITIVES["foreign-names"] = foreignNames; + + // foreign-parse-params + var foreignParseParams = function(paramList) { return (function() { + var result = []; + var i = 0; + var items = (isSxTruthy(isList(paramList)) ? paramList : []); + return foreignParseParamsLoop(items, result); +})(); }; +PRIMITIVES["foreign-parse-params"] = foreignParseParams; + + // foreign-parse-kwargs! + var foreignParseKwargs_b = function(spec, remaining) { return (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(remaining))) && isSxTruthy((len(remaining) >= 2)) && keyword_p(first(remaining)))) ? (dictSet(spec, keywordName(first(remaining)), (function() { + var v = nth(remaining, 1); + return (isSxTruthy(keyword_p(v)) ? keywordName(v) : v); +})()), foreignParseKwargs_b(spec, rest(rest(remaining)))) : NIL); }; +PRIMITIVES["foreign-parse-kwargs!"] = foreignParseKwargs_b; + + // foreign-resolve-binding + var foreignResolveBinding = function(bindingStr) { return (function() { + var parts = split(bindingStr, "."); + return (isSxTruthy((len(parts) <= 1)) ? {"method": bindingStr, "object": NIL} : (function() { + var method = last(parts); + var obj = join(".", reverse(rest(reverse(parts)))); + return {"method": method, "object": obj}; +})()); +})(); }; +PRIMITIVES["foreign-resolve-binding"] = foreignResolveBinding; + + // foreign-check-args + var foreignCheckArgs = function(name, params, args) { if (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(params))) && (len(args) < len(params))))) { + error((String("foreign ") + String(name) + String(": expected ") + String(len(params)) + String(" args, got ") + String(len(args)))); +} +return forEach(function(i) { return (function() { + var spec = nth(params, i); + var val = nth(args, i); + var expected = get(spec, "type"); + return (isSxTruthy((isSxTruthy(!isSxTruthy(sxEq(expected, "any"))) && !isSxTruthy(valueMatchesType_p(val, expected)))) ? error((String("foreign ") + String(name) + String(": arg '") + String(get(spec, "name")) + String("' expected ") + String(expected) + String(", got ") + String(typeOf(val)))) : NIL); +})(); }, range(0, min(len(params), len(args)))); }; +PRIMITIVES["foreign-check-args"] = foreignCheckArgs; + + // foreign-build-lambda + var foreignBuildLambda = function(spec) { return (function() { + var name = get(spec, "name"); + var mode = (isSxTruthy(dictHas(spec, "returns")) ? (function() { + var r = get(spec, "returns"); + return (isSxTruthy(sxEq(r, "promise")) ? "async" : "sync"); +})() : "sync"); + return (isSxTruthy(sxEq(mode, "async")) ? [new Symbol("fn"), [new Symbol("&rest"), new Symbol("__ffi-args__")], [new Symbol("perform"), [new Symbol("foreign-dispatch"), [new Symbol("quote"), name], new Symbol("__ffi-args__")]]] : [new Symbol("fn"), [new Symbol("&rest"), new Symbol("__ffi-args__")], [new Symbol("foreign-dispatch"), [new Symbol("quote"), name], new Symbol("__ffi-args__")]]); +})(); }; +PRIMITIVES["foreign-build-lambda"] = foreignBuildLambda; + + // sf-define-foreign + var sfDefineForeign = function(args, env) { return (function() { + var name = (isSxTruthy(symbol_p(first(args))) ? symbolName(first(args)) : first(args)); + var paramList = nth(args, 1); + var spec = {}; + spec["name"] = name; + spec["params"] = foreignParseParams(paramList); + foreignParseKwargs_b(spec, rest(rest(args))); + foreignRegister_b(name, spec); + return spec; +})(); }; +PRIMITIVES["sf-define-foreign"] = sfDefineForeign; + + // step-sf-define-foreign + var stepSfDefineForeign = function(args, env, kont) { return (function() { + var spec = sfDefineForeign(args, env); + var name = (isSxTruthy(symbol_p(first(args))) ? symbolName(first(args)) : first(args)); + var lambdaExpr = foreignBuildLambda(spec); + return makeCekState(lambdaExpr, env, kontPush(makeDefineForeignFrame(name, spec, env), kont)); +})(); }; +PRIMITIVES["step-sf-define-foreign"] = stepSfDefineForeign; + + // foreign-dispatch + var foreignDispatch = function(name, args) { return (function() { + var spec = foreignLookup(name); + if (isSxTruthy(isNil(spec))) { + error((String("foreign-dispatch: unknown foreign function '") + String(name) + String("'"))); +} + return (function() { + var params = get(spec, "params"); + var binding = get(spec, "js"); + foreignCheckArgs(name, (isSxTruthy(isNil(params)) ? [] : params), args); + return (isSxTruthy(isNil(binding)) ? error((String("foreign ") + String(name) + String(": no binding for current platform"))) : (function() { + var resolved = foreignResolveBinding(binding); + var objName = get(resolved, "object"); + var method = get(resolved, "method"); + return (isSxTruthy(isPrimitive("host-call")) ? (isSxTruthy(isNil(objName)) ? apply(getPrimitive("host-call"), concat([NIL, method], args)) : (function() { + var obj = (getPrimitive("host-global"))(objName); + return apply(getPrimitive("host-call"), concat([obj, method], args)); +})()) : error((String("foreign ") + String(name) + String(": host-call not available on this platform")))); +})()); +})(); +})(); }; +PRIMITIVES["foreign-dispatch"] = foreignDispatch; + + // foreign-parse-params-loop + var foreignParseParamsLoop = function(items, acc) { return (isSxTruthy(isEmpty(items)) ? acc : (function() { + var item = first(items); + var restItems = rest(items); + return (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(restItems))) && isSxTruthy(keyword_p(first(restItems))) && isSxTruthy(sxEq(keywordName(first(restItems)), "as")) && (len(restItems) >= 2))) ? foreignParseParamsLoop(rest(rest(restItems)), append(acc, [{"type": (function() { + var t = nth(restItems, 1); + return (isSxTruthy(keyword_p(t)) ? keywordName(t) : (String(t))); +})(), "name": (isSxTruthy(symbol_p(item)) ? symbolName(item) : (String(item)))}])) : foreignParseParamsLoop(restItems, append(acc, [{"type": "any", "name": (isSxTruthy(symbol_p(item)) ? symbolName(item) : (String(item)))}]))); +})()); }; +PRIMITIVES["foreign-parse-params-loop"] = foreignParseParamsLoop; + // step-sf-io var stepSfIo = function(args, env, kont) { return (function() { var name = first(args); @@ -1839,7 +2078,7 @@ PRIMITIVES["step-sf-let-match"] = stepSfLetMatch; var args = rest(expr); return (isSxTruthy(!isSxTruthy(sxOr(sxEq(typeOf(head), "symbol"), sxEq(typeOf(head), "lambda"), sxEq(typeOf(head), "list")))) ? (isSxTruthy(isEmpty(expr)) ? makeCekValue([], env, kont) : makeCekState(first(expr), env, kontPush(makeMapFrame(NIL, rest(expr), [], env), kont))) : (isSxTruthy(sxEq(typeOf(head), "symbol")) ? (function() { var name = symbolName(head); - return (function() { var _m = name; if (_m == "if") return stepSfIf(args, env, kont); if (_m == "when") return stepSfWhen(args, env, kont); if (_m == "cond") return stepSfCond(args, env, kont); if (_m == "case") return stepSfCase(args, env, kont); if (_m == "and") return stepSfAnd(args, env, kont); if (_m == "or") return stepSfOr(args, env, kont); if (_m == "let") return stepSfLet(args, env, kont); if (_m == "let*") return stepSfLet(args, env, kont); if (_m == "lambda") return stepSfLambda(args, env, kont); if (_m == "fn") return stepSfLambda(args, env, kont); if (_m == "define") return stepSfDefine(args, env, kont); if (_m == "defcomp") return makeCekValue(sfDefcomp(args, env), env, kont); if (_m == "defisland") return makeCekValue(sfDefisland(args, env), env, kont); if (_m == "defmacro") return makeCekValue(sfDefmacro(args, env), env, kont); if (_m == "defio") return makeCekValue(sfDefio(args, env), env, kont); if (_m == "io") return stepSfIo(args, env, kont); if (_m == "begin") return stepSfBegin(args, env, kont); if (_m == "do") return (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(args))) && isSxTruthy(isList(first(args))) && isSxTruthy(!isSxTruthy(isEmpty(first(args)))) && isList(first(first(args))))) ? (function() { + return (function() { var _m = name; if (_m == "if") return stepSfIf(args, env, kont); if (_m == "when") return stepSfWhen(args, env, kont); if (_m == "cond") return stepSfCond(args, env, kont); if (_m == "case") return stepSfCase(args, env, kont); if (_m == "and") return stepSfAnd(args, env, kont); if (_m == "or") return stepSfOr(args, env, kont); if (_m == "let") return stepSfLet(args, env, kont); if (_m == "let*") return stepSfLet(args, env, kont); if (_m == "lambda") return stepSfLambda(args, env, kont); if (_m == "fn") return stepSfLambda(args, env, kont); if (_m == "define") return stepSfDefine(args, env, kont); if (_m == "defcomp") return makeCekValue(sfDefcomp(args, env), env, kont); if (_m == "defisland") return makeCekValue(sfDefisland(args, env), env, kont); if (_m == "defmacro") return makeCekValue(sfDefmacro(args, env), env, kont); if (_m == "defio") return makeCekValue(sfDefio(args, env), env, kont); if (_m == "define-foreign") return stepSfDefineForeign(args, env, kont); if (_m == "io") return stepSfIo(args, env, kont); if (_m == "begin") return stepSfBegin(args, env, kont); if (_m == "do") return (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(args))) && isSxTruthy(isList(first(args))) && isSxTruthy(!isSxTruthy(isEmpty(first(args)))) && isList(first(first(args))))) ? (function() { var bindings = first(args); var testClause = nth(args, 1); var body = rest(rest(args)); @@ -1849,10 +2088,10 @@ PRIMITIVES["step-sf-let-match"] = stepSfLetMatch; var test = first(testClause); var result = rest(testClause); return stepEvalList(cons(new Symbol("let"), cons(new Symbol("__do-loop"), cons(map(function(b) { return [first(b), nth(b, 1)]; }, bindings), [cons(new Symbol("if"), cons(test, cons((isSxTruthy(isEmpty(result)) ? NIL : cons(new Symbol("begin"), result)), [cons(new Symbol("begin"), append(body, [cons(new Symbol("__do-loop"), steps)]))])))]))), env, kont); -})() : stepSfBegin(args, env, kont)); if (_m == "guard") return stepSfGuard(args, env, kont); if (_m == "quote") return makeCekValue((isSxTruthy(isEmpty(args)) ? NIL : first(args)), env, kont); if (_m == "quasiquote") return makeCekValue(qqExpand(first(args), env), env, kont); if (_m == "->") return stepSfThreadFirst(args, env, kont); if (_m == "->>") return stepSfThreadLast(args, env, kont); if (_m == "|>") return stepSfThreadLast(args, env, kont); if (_m == "as->") return stepSfThreadAs(args, env, kont); if (_m == "set!") return stepSfSet(args, env, kont); if (_m == "letrec") return stepSfLetrec(args, env, kont); if (_m == "reset") return stepSfReset(args, env, kont); if (_m == "shift") return stepSfShift(args, env, kont); if (_m == "deref") return stepSfDeref(args, env, kont); if (_m == "scope") return stepSfScope(args, env, kont); if (_m == "provide") return stepSfProvide(args, env, kont); if (_m == "peek") return stepSfPeek(args, env, kont); if (_m == "provide!") return stepSfProvide_b(args, env, kont); if (_m == "context") return stepSfContext(args, env, kont); if (_m == "bind") return stepSfBind(args, env, kont); if (_m == "emit!") return stepSfEmit(args, env, kont); if (_m == "emitted") return stepSfEmitted(args, env, kont); if (_m == "handler-bind") return stepSfHandlerBind(args, env, kont); if (_m == "restart-case") return stepSfRestartCase(args, env, kont); if (_m == "signal-condition") return stepSfSignal(args, env, kont); if (_m == "invoke-restart") return stepSfInvokeRestart(args, env, kont); if (_m == "match") return stepSfMatch(args, env, kont); if (_m == "let-match") return stepSfLetMatch(args, env, kont); if (_m == "dynamic-wind") return makeCekValue(sfDynamicWind(args, env), env, kont); if (_m == "map") return stepHoMap(args, env, kont); if (_m == "map-indexed") return stepHoMapIndexed(args, env, kont); if (_m == "filter") return stepHoFilter(args, env, kont); if (_m == "reduce") return stepHoReduce(args, env, kont); if (_m == "some") return stepHoSome(args, env, kont); if (_m == "every?") return stepHoEvery(args, env, kont); if (_m == "for-each") return stepHoForEach(args, env, kont); if (_m == "raise") return stepSfRaise(args, env, kont); if (_m == "raise-continuable") return makeCekState(first(args), env, kontPush(makeRaiseEvalFrame(env, true), kont)); if (_m == "call/cc") return stepSfCallcc(args, env, kont); if (_m == "call-with-current-continuation") return stepSfCallcc(args, env, kont); if (_m == "perform") return stepSfPerform(args, env, kont); if (_m == "define-library") return stepSfDefineLibrary(args, env, kont); if (_m == "import") return stepSfImport(args, env, kont); if (_m == "define-record-type") return makeCekValue(sfDefineRecordType(args, env), env, kont); if (_m == "define-protocol") return makeCekValue(sfDefineProtocol(args, env), env, kont); if (_m == "implement") return makeCekValue(sfImplement(args, env), env, kont); if (_m == "parameterize") return stepSfParameterize(args, env, kont); if (_m == "syntax-rules") return makeCekValue(sfSyntaxRules(args, env), env, kont); if (_m == "define-syntax") return stepSfDefine(args, env, kont); return (isSxTruthy(dictHas(_customSpecialForms, name)) ? makeCekValue((get(_customSpecialForms, name))(args, env), env, kont) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { +})() : stepSfBegin(args, env, kont)); if (_m == "guard") return stepSfGuard(args, env, kont); if (_m == "quote") return makeCekValue((isSxTruthy(isEmpty(args)) ? NIL : first(args)), env, kont); if (_m == "quasiquote") return makeCekValue(qqExpand(first(args), env), env, kont); if (_m == "->") return stepSfThreadFirst(args, env, kont); if (_m == "->>") return stepSfThreadLast(args, env, kont); if (_m == "|>") return stepSfThreadLast(args, env, kont); if (_m == "as->") return stepSfThreadAs(args, env, kont); if (_m == "set!") return stepSfSet(args, env, kont); if (_m == "letrec") return stepSfLetrec(args, env, kont); if (_m == "reset") return stepSfReset(args, env, kont); if (_m == "shift") return stepSfShift(args, env, kont); if (_m == "deref") return stepSfDeref(args, env, kont); if (_m == "scope") return stepSfScope(args, env, kont); if (_m == "provide") return stepSfProvide(args, env, kont); if (_m == "peek") return stepSfPeek(args, env, kont); if (_m == "provide!") return stepSfProvide_b(args, env, kont); if (_m == "context") return stepSfContext(args, env, kont); if (_m == "bind") return stepSfBind(args, env, kont); if (_m == "emit!") return stepSfEmit(args, env, kont); if (_m == "emitted") return stepSfEmitted(args, env, kont); if (_m == "handler-bind") return stepSfHandlerBind(args, env, kont); if (_m == "restart-case") return stepSfRestartCase(args, env, kont); if (_m == "signal-condition") return stepSfSignal(args, env, kont); if (_m == "invoke-restart") return stepSfInvokeRestart(args, env, kont); if (_m == "match") return stepSfMatch(args, env, kont); if (_m == "let-match") return stepSfLetMatch(args, env, kont); if (_m == "dynamic-wind") return makeCekValue(sfDynamicWind(args, env), env, kont); if (_m == "map") return stepHoMap(args, env, kont); if (_m == "map-indexed") return stepHoMapIndexed(args, env, kont); if (_m == "filter") return stepHoFilter(args, env, kont); if (_m == "reduce") return stepHoReduce(args, env, kont); if (_m == "some") return stepHoSome(args, env, kont); if (_m == "every?") return stepHoEvery(args, env, kont); if (_m == "for-each") return stepHoForEach(args, env, kont); if (_m == "raise") return stepSfRaise(args, env, kont); if (_m == "raise-continuable") return makeCekState(first(args), env, kontPush(makeRaiseEvalFrame(env, true), kont)); if (_m == "call/cc") return stepSfCallcc(args, env, kont); if (_m == "call-with-current-continuation") return stepSfCallcc(args, env, kont); if (_m == "perform") return stepSfPerform(args, env, kont); if (_m == "define-library") return stepSfDefineLibrary(args, env, kont); if (_m == "import") return stepSfImport(args, env, kont); if (_m == "define-record-type") return makeCekValue(sfDefineRecordType(args, env), env, kont); if (_m == "define-protocol") return makeCekValue(sfDefineProtocol(args, env), env, kont); if (_m == "implement") return makeCekValue(sfImplement(args, env), env, kont); if (_m == "parameterize") return stepSfParameterize(args, env, kont); if (_m == "syntax-rules") return makeCekValue(sfSyntaxRules(args, env), env, kont); if (_m == "define-syntax") return stepSfDefine(args, env, kont); return (isSxTruthy((isSxTruthy(dictHas(_customSpecialForms, name)) && !isSxTruthy(envHas(env, name)))) ? makeCekValue((get(_customSpecialForms, name))(args, env), env, kont) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { var mac = envGet(env, name); return makeCekState(expandMacro(mac, args, env), env, kont); -})() : (isSxTruthy((isSxTruthy(_renderCheck) && _renderCheck(expr, env))) ? makeCekValue(_renderFn(expr, env), env, kont) : stepEvalCall(head, args, env, kont)))); })(); +})() : (isSxTruthy((isSxTruthy(_renderCheck) && isSxTruthy(!isSxTruthy(envHas(env, name))) && _renderCheck(expr, env))) ? makeCekValue(_renderFn(expr, env), env, kont) : stepEvalCall(head, args, env, kont)))); })(); })() : stepEvalCall(head, args, env, kont))); })(); }; PRIMITIVES["step-eval-list"] = stepEvalList; @@ -1868,10 +2107,30 @@ PRIMITIVES["kont-extract-provides"] = kontExtractProvides; // fire-provide-subscribers var fireProvideSubscribers = function(frame, kont) { return (function() { var subs = get(frame, "subscribers"); - return (isSxTruthy(!isSxTruthy(isEmpty(subs))) ? forEach(function(sub) { return cekCall(sub, [kont]); }, subs) : NIL); + return (isSxTruthy(!isSxTruthy(isEmpty(subs))) ? (isSxTruthy((_provideBatchDepth_ > 0)) ? forEach(function(sub) { return (isSxTruthy(!isSxTruthy(contains(_provideBatchQueue_, sub))) ? append_b(_provideBatchQueue_, sub) : NIL); }, subs) : forEach(function(sub) { return cekCall(sub, [kont]); }, subs)) : NIL); })(); }; PRIMITIVES["fire-provide-subscribers"] = fireProvideSubscribers; + // fire-provide-subscribers + var fireProvideSubscribers = function(name) { return (function() { + var subs = get(_provideSubscribers_, name); + return (isSxTruthy((isSxTruthy(subs) && !isSxTruthy(isEmpty(subs)))) ? (isSxTruthy((_provideBatchDepth_ > 0)) ? forEach(function(sub) { return (isSxTruthy(!isSxTruthy(contains(_provideBatchQueue_, sub))) ? append_b(_provideBatchQueue_, sub) : NIL); }, subs) : forEach(function(sub) { return cekCall(sub, [NIL]); }, subs)) : NIL); +})(); }; +PRIMITIVES["fire-provide-subscribers"] = fireProvideSubscribers; + + // batch-begin! + var batchBegin_b = function() { return (_provideBatchDepth_ = (_provideBatchDepth_ + 1)); }; +PRIMITIVES["batch-begin!"] = batchBegin_b; + + // batch-end! + var batchEnd_b = function() { _provideBatchDepth_ = (_provideBatchDepth_ - 1); +return (isSxTruthy(sxEq(_provideBatchDepth_, 0)) ? (function() { + var queue = _provideBatchQueue_; + _provideBatchQueue_ = []; + return forEach(function(sub) { return cekCall(sub, [NIL]); }, queue); +})() : NIL); }; +PRIMITIVES["batch-end!"] = batchEnd_b; + // step-sf-bind var stepSfBind = function(args, env, kont) { return (function() { var body = first(args); @@ -2011,7 +2270,7 @@ PRIMITIVES["sf-syntax-rules"] = sfSyntaxRules; { var _c = decls; for (var _i = 0; _i < _c.length; _i++) { var decl = _c[_i]; if (isSxTruthy((isSxTruthy(isList(decl)) && isSxTruthy(!isSxTruthy(isEmpty(decl))) && symbol_p(first(decl))))) { (function() { var kind = symbolName(first(decl)); - return (isSxTruthy(sxEq(kind, "export")) ? (exports = append(exports, map(function(s) { return (isSxTruthy(symbol_p(s)) ? symbolName(s) : (String(s))); }, rest(decl)))) : (isSxTruthy(sxEq(kind, "begin")) ? (bodyForms = append(bodyForms, rest(decl))) : NIL)); + return (isSxTruthy(sxEq(kind, "export")) ? (exports = append(exports, map(function(s) { return (isSxTruthy(symbol_p(s)) ? symbolName(s) : (String(s))); }, rest(decl)))) : (isSxTruthy(sxEq(kind, "import")) ? forEach(function(importSet) { return bindImportSet(importSet, libEnv); }, rest(decl)) : (isSxTruthy(sxEq(kind, "begin")) ? (bodyForms = append(bodyForms, rest(decl))) : NIL))); })(); } } } { var _c = bodyForms; for (var _i = 0; _i < _c.length; _i++) { var form = _c[_i]; evalExpr(form, libEnv); } } @@ -2407,10 +2666,10 @@ PRIMITIVES["step-sf-provide"] = stepSfProvide; _bindTracking_.push(name); } } - return makeCekValue((isSxTruthy(frame) ? get(frame, "value") : (function() { + return makeCekValue((function() { var sv = scopePeek(name); - return (isSxTruthy(isNil(sv)) ? defaultVal : sv); -})()), env, kont); + return (isSxTruthy(isNil(sv)) ? (isSxTruthy(frame) ? get(frame, "value") : defaultVal) : sv); +})(), env, kont); })(); }; PRIMITIVES["step-sf-context"] = stepSfContext; @@ -2649,6 +2908,14 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach; })(); } return makeCekValue(value, fenv, restK); +})(); if (_m == "define-foreign") return (function() { + var name = get(frame, "name"); + var fenv = get(frame, "env"); + if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) { + value.name = name; +} + envBind(fenv, name, value); + return makeCekValue(value, fenv, restK); })(); if (_m == "set") return (function() { var name = get(frame, "name"); var fenv = get(frame, "env"); @@ -2780,8 +3047,8 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach; (function() { var subscriber = function(fireKont) { return cekRun(makeCekState(body, fenv, [])); }; return forEach(function(name) { return (function() { - var pf = kontFindProvide(restK, name); - return (isSxTruthy(pf) ? dictSet(pf, "subscribers", append(get(pf, "subscribers"), [subscriber])) : NIL); + var existing = get(_provideSubscribers_, name); + return dictSet(_provideSubscribers_, name, append((isSxTruthy(existing) ? existing : []), [subscriber])); })(); }, tracked); })(); return makeCekValue(value, fenv, restK); @@ -2789,16 +3056,18 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach; var name = get(frame, "name"); var fenv = get(frame, "env"); var target = kontFindProvide(restK, name); - return (isSxTruthy(target) ? (function() { - var oldVal = get(target, "value"); + return (function() { + var oldVal = (isSxTruthy(target) ? get(target, "value") : scopePeek(name)); + if (isSxTruthy(target)) { target["value"] = value; +} scopePop(name); scopePush(name, value); if (isSxTruthy(!isSxTruthy(sxEq(oldVal, value)))) { - fireProvideSubscribers(target, restK); + fireProvideSubscribers(name); } return makeCekValue(value, fenv, restK); -})() : (isSxTruthy(envHas(fenv, "provide-set!")) ? (apply(envGet(fenv, "provide-set!"), [name, value]), makeCekValue(value, fenv, restK)) : makeCekValue(NIL, fenv, restK))); +})(); })(); if (_m == "scope-acc") return (function() { var remaining = get(frame, "remaining"); var fenv = get(frame, "env"); @@ -2936,7 +3205,10 @@ PRIMITIVES["step-continue"] = stepContinue; return makeCekValue(result, env, kont); })(); })(); -})() : (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? makeCekValue(apply(f, args), env, kont) : (isSxTruthy(isLambda(f)) ? (function() { +})() : (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? (function() { + var result = sxApplyCek(f, args); + return (isSxTruthy(evalError_p(result)) ? makeCekValue(get(result, "message"), env, kontPush(makeRaiseEvalFrame(env, false), kont)) : (isSxTruthy((isSxTruthy(isDict(result)) && get(result, "__vm_suspended"))) ? makeCekSuspended(get(result, "request"), env, kontPush(makeVmResumeFrame(get(result, "resume"), env), kont)) : makeCekValue(result, env, kont))); +})() : (isSxTruthy(isLambda(f)) ? (function() { var params = lambdaParams(f); var local = envMerge(lambdaClosure(f), env); if (isSxTruthy(!isSxTruthy(bindLambdaParams(params, args, local)))) { @@ -2948,7 +3220,7 @@ PRIMITIVES["step-continue"] = stepContinue; } return (function() { var jitResult = jitTryCall(f, args); - return (isSxTruthy(isNil(jitResult)) ? makeCekState(lambdaBody(f), local, kont) : (isSxTruthy((isSxTruthy(isDict(jitResult)) && get(jitResult, "__vm_suspended"))) ? makeCekSuspended(get(jitResult, "request"), env, kontPush(makeVmResumeFrame(get(jitResult, "resume"), env), kont)) : makeCekValue(jitResult, local, kont))); + return (isSxTruthy(jitSkip_p(jitResult)) ? makeCekState(lambdaBody(f), local, kont) : (isSxTruthy((isSxTruthy(isDict(jitResult)) && get(jitResult, "__vm_suspended"))) ? makeCekSuspended(get(jitResult, "request"), env, kontPush(makeVmResumeFrame(get(jitResult, "resume"), env), kont)) : makeCekValue(jitResult, local, kont))); })(); })() : (isSxTruthy(sxOr(isComponent(f), isIsland(f))) ? (function() { var parsed = parseKeywordArgs(rawArgs, env); @@ -3350,10 +3622,16 @@ PRIMITIVES["serialize"] = serialize; // === Transpiled from lib/dom (DOM library) === + // dom-visible? + var domVisible_p = function(el) { return (isSxTruthy(el) ? !isSxTruthy(sxEq(hostGet(hostGet(el, "style"), "display"), "none")) : false); }; +PRIMITIVES["dom-visible?"] = domVisible_p; // === Transpiled from lib/browser (browser API library) === + // json-stringify + var jsonStringify = function(v) { return hostCall(hostGlobal("JSON"), "stringify", v); }; +PRIMITIVES["json-stringify"] = jsonStringify; // === Transpiled from adapter-dom === @@ -3524,6 +3802,7 @@ PRIMITIVES["process-page-scripts"] = processPageScripts; // sx-hydrate-islands var sxHydrateIslands = function(root) { return (function() { var els = domQueryAll(sxOr(root, domBody()), "[data-sx-island]"); + preloadIslandDefs(); logInfo((String("sx-hydrate-islands: ") + String(len(els)) + String(" island(s) in ") + String((isSxTruthy(root) ? "subtree" : "document")))); return forEach(function(el) { return (isSxTruthy(isProcessed(el, "island-hydrated")) ? logInfo((String(" skip (already hydrated): ") + String(domGetAttr(el, "data-sx-island")))) : (logInfo((String(" hydrating: ") + String(domGetAttr(el, "data-sx-island")))), markProcessed(el, "island-hydrated"), hydrateIsland(el))); }, els); })(); }; @@ -3537,26 +3816,34 @@ PRIMITIVES["sx-hydrate-islands"] = sxHydrateIslands; var compName = (String("~") + String(name)); var env = getRenderEnv(NIL); return (function() { - var comp = envGet(env, compName); + var comp = envGet(globalEnv(), compName); return (isSxTruthy(!isSxTruthy(sxOr(isComponent(comp), isIsland(comp)))) ? logWarn((String("hydrate-island: unknown island ") + String(compName))) : (function() { var kwargs = sxOr(first(sxParse(stateSx)), {}); var disposers = []; var local = envMerge(componentClosure(comp), env); { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } return (function() { - var bodyDom = cekTry(function() { return withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); }); }, function(err) { logWarn((String("hydrate-island FAILED: ") + String(compName) + String(" — ") + String(err))); + var cursor = {["parent"]: el, ["index"]: 0}; + hostCall(el, "replaceChildren"); + scopePush("sx-hydrating", NIL); + cekTry(function() { return withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return (function() { + var bodyDom = renderToDom(componentBody(comp), local, NIL); + return (isSxTruthy(bodyDom) ? domAppend(el, bodyDom) : NIL); +})(); }); }, function(err) { scopePop("sx-hydrating"); +logWarn((String("hydrate fallback: ") + String(compName) + String(" — ") + String(err))); return (function() { - var errorEl = domCreateElement("div", NIL); - domSetAttr(errorEl, "class", "sx-island-error"); - domSetAttr(errorEl, "style", "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap"); - domSetTextContent(errorEl, (String("Island error: ") + String(compName) + String("\n") + String(err))); - return errorEl; + var fallback = cekTry(function() { return withIslandScope(function(d) { return append_b(disposers, d); }, function() { return renderToDom(componentBody(comp), local, NIL); }); }, function(err2) { return (function() { + var e = domCreateElement("div", NIL); + domSetTextContent(e, (String("Island error: ") + String(compName) + String("\n") + String(err2))); + return e; })(); }); - domSetTextContent(el, ""); - domAppend(el, bodyDom); + hostCall(el, "replaceChildren", fallback); + return NIL; +})(); }); + scopePop("sx-hydrating"); domSetData(el, "sx-disposers", disposers); setTimeout_(function() { return processElements(el); }, 0); - return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)"))); + return logInfo((String("hydrated island: ~") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)"))); })(); })()); })(); @@ -3656,6 +3943,18 @@ PRIMITIVES["boot-init"] = bootInit; // 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) { @@ -3723,7 +4022,7 @@ PRIMITIVES["boot-init"] = bootInit; 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; @@ -3807,6 +4106,13 @@ PRIMITIVES["boot-init"] = bootInit; 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); } @@ -3997,7 +4303,7 @@ PRIMITIVES["boot-init"] = bootInit; } 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); } @@ -4119,6 +4425,14 @@ PRIMITIVES["boot-init"] = bootInit; // 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) { @@ -4604,6 +4918,10 @@ PRIMITIVES["boot-init"] = bootInit; return el && el.closest ? el.closest(sel) : null; } + function domDocument() { + return _hasDom ? document : null; + } + function domBody() { return _hasDom ? document.body : null; } @@ -5045,6 +5363,8 @@ PRIMITIVES["boot-init"] = bootInit; // 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; @@ -5920,52 +6240,52 @@ PRIMITIVES["boot-init"] = bootInit; hydrateIslands: typeof sxHydrateIslands === "function" ? sxHydrateIslands : null, disposeIsland: typeof disposeIsland === "function" ? disposeIsland : null, init: typeof bootInit === "function" ? bootInit : null, - scanRefs: scanRefs, - scanComponentsFromSource: scanComponentsFromSource, - transitiveDeps: transitiveDeps, - computeAllDeps: computeAllDeps, - componentsNeeded: componentsNeeded, - pageComponentBundle: pageComponentBundle, - pageCssClasses: pageCssClasses, - scanIoRefs: scanIoRefs, - transitiveIoRefs: transitiveIoRefs, - computeAllIoRefs: computeAllIoRefs, - componentPure_p: componentPure_p, - categorizeSpecialForms: categorizeSpecialForms, - buildReferenceData: buildReferenceData, - buildAttrDetail: buildAttrDetail, - buildHeaderDetail: buildHeaderDetail, - buildEventDetail: buildEventDetail, - buildComponentSource: buildComponentSource, - buildBundleAnalysis: buildBundleAnalysis, - buildRoutingAnalysis: buildRoutingAnalysis, - buildAffinityAnalysis: buildAffinityAnalysis, - splitPathSegments: splitPathSegments, - parseRoutePattern: parseRoutePattern, - matchRoute: matchRoute, - findMatchingRoute: findMatchingRoute, - urlToExpr: urlToExpr, - autoQuoteUnknowns: autoQuoteUnknowns, - prepareUrlExpr: prepareUrlExpr, + scanRefs: typeof scanRefs === "function" ? scanRefs : null, + scanComponentsFromSource: typeof scanComponentsFromSource === "function" ? scanComponentsFromSource : null, + transitiveDeps: typeof transitiveDeps === "function" ? transitiveDeps : null, + computeAllDeps: typeof computeAllDeps === "function" ? computeAllDeps : null, + componentsNeeded: typeof componentsNeeded === "function" ? componentsNeeded : null, + pageComponentBundle: typeof pageComponentBundle === "function" ? pageComponentBundle : null, + pageCssClasses: typeof pageCssClasses === "function" ? pageCssClasses : null, + scanIoRefs: typeof scanIoRefs === "function" ? scanIoRefs : null, + transitiveIoRefs: typeof transitiveIoRefs === "function" ? transitiveIoRefs : null, + computeAllIoRefs: typeof computeAllIoRefs === "function" ? computeAllIoRefs : null, + componentPure_p: typeof componentPure_p === "function" ? componentPure_p : null, + categorizeSpecialForms: typeof categorizeSpecialForms === "function" ? categorizeSpecialForms : null, + buildReferenceData: typeof buildReferenceData === "function" ? buildReferenceData : null, + buildAttrDetail: typeof buildAttrDetail === "function" ? buildAttrDetail : null, + buildHeaderDetail: typeof buildHeaderDetail === "function" ? buildHeaderDetail : null, + buildEventDetail: typeof buildEventDetail === "function" ? buildEventDetail : null, + buildComponentSource: typeof buildComponentSource === "function" ? buildComponentSource : null, + buildBundleAnalysis: typeof buildBundleAnalysis === "function" ? buildBundleAnalysis : null, + buildRoutingAnalysis: typeof buildRoutingAnalysis === "function" ? buildRoutingAnalysis : null, + buildAffinityAnalysis: typeof buildAffinityAnalysis === "function" ? buildAffinityAnalysis : null, + splitPathSegments: typeof splitPathSegments === "function" ? splitPathSegments : null, + parseRoutePattern: typeof parseRoutePattern === "function" ? parseRoutePattern : null, + matchRoute: typeof matchRoute === "function" ? matchRoute : null, + findMatchingRoute: typeof findMatchingRoute === "function" ? findMatchingRoute : null, + urlToExpr: typeof urlToExpr === "function" ? urlToExpr : null, + autoQuoteUnknowns: typeof autoQuoteUnknowns === "function" ? autoQuoteUnknowns : null, + prepareUrlExpr: typeof prepareUrlExpr === "function" ? prepareUrlExpr : null, registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null, registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null, asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null, asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null, - signal: signal, - deref: deref, - reset: reset_b, - swap: swap_b, - computed: computed, - effect: effect, - batch: batch, - isSignal: isSignal, - makeSignal: makeSignal, - defStore: defStore, - useStore: useStore, - clearStores: clearStores, - emitEvent: emitEvent, - onEvent: onEvent, - bridgeEvent: bridgeEvent, + signal: typeof signal === "function" ? signal : null, + deref: typeof deref === "function" ? deref : null, + reset: typeof reset_b === "function" ? reset_b : null, + swap: typeof swap_b === "function" ? swap_b : null, + computed: typeof computed === "function" ? computed : null, + effect: typeof effect === "function" ? effect : null, + batch: typeof batch === "function" ? batch : null, + isSignal: typeof isSignal === "function" ? isSignal : null, + makeSignal: typeof makeSignal === "function" ? makeSignal : null, + defStore: typeof defStore === "function" ? defStore : null, + useStore: typeof useStore === "function" ? useStore : null, + clearStores: typeof clearStores === "function" ? clearStores : null, + emitEvent: typeof emitEvent === "function" ? emitEvent : null, + onEvent: typeof onEvent === "function" ? onEvent : null, + bridgeEvent: typeof bridgeEvent === "function" ? bridgeEvent : null, makeSpread: makeSpread, isSpread: isSpread, spreadAttrs: spreadAttrs, diff --git a/spec/tests/test-vectors.sx b/spec/tests/test-vectors.sx new file mode 100644 index 00000000..aecb1f3a --- /dev/null +++ b/spec/tests/test-vectors.sx @@ -0,0 +1,207 @@ +;; test-vectors.sx — Tests for vector primitives + +(defsuite + "vectors" + (deftest + "make-vector default fill is nil" + (let + ((v (make-vector 3))) + (assert (vector? v)) + (assert-equal 3 (vector-length v)) + (assert-equal nil (vector-ref v 0)) + (assert-equal nil (vector-ref v 1)) + (assert-equal nil (vector-ref v 2)))) + (deftest + "make-vector with fill value" + (let + ((v (make-vector 4 99))) + (assert-equal 4 (vector-length v)) + (assert-equal 99 (vector-ref v 0)) + (assert-equal 99 (vector-ref v 1)) + (assert-equal 99 (vector-ref v 2)) + (assert-equal 99 (vector-ref v 3)))) + (deftest + "make-vector size zero" + (let ((v (make-vector 0))) (assert-equal 0 (vector-length v)))) + (deftest + "make-vector size one" + (let + ((v (make-vector 1 "x"))) + (assert-equal 1 (vector-length v)) + (assert-equal "x" (vector-ref v 0)))) + (deftest + "vector constructor no args" + (let ((v (vector))) (assert-equal 0 (vector-length v)))) + (deftest + "vector constructor with args" + (let + ((v (vector 10 20 30))) + (assert-equal 3 (vector-length v)) + (assert-equal 10 (vector-ref v 0)) + (assert-equal 20 (vector-ref v 1)) + (assert-equal 30 (vector-ref v 2)))) + (deftest + "vector constructor strings" + (let + ((v (vector "a" "b" "c"))) + (assert-equal "a" (vector-ref v 0)) + (assert-equal "b" (vector-ref v 1)) + (assert-equal "c" (vector-ref v 2)))) + (deftest "vector? true for vector" (assert (vector? (make-vector 3)))) + (deftest "vector? false for list" (assert (not (vector? (list 1 2 3))))) + (deftest "vector? false for number" (assert (not (vector? 42)))) + (deftest "vector? false for nil" (assert (not (vector? nil)))) + (deftest "vector? false for string" (assert (not (vector? "hello")))) + (deftest "vector-length zero" (assert-equal 0 (vector-length (vector)))) + (deftest + "vector-length three" + (assert-equal 3 (vector-length (vector 1 2 3)))) + (deftest + "vector-length after make-vector" + (assert-equal 7 (vector-length (make-vector 7 0)))) + (deftest + "vector-ref first element" + (assert-equal 1 (vector-ref (vector 1 2 3) 0))) + (deftest + "vector-ref last element" + (assert-equal 3 (vector-ref (vector 1 2 3) 2))) + (deftest + "vector-ref middle element" + (assert-equal 2 (vector-ref (vector 1 2 3) 1))) + (deftest + "vector-set! mutates in place" + (let + ((v (vector 1 2 3))) + (vector-set! v 1 99) + (assert-equal 99 (vector-ref v 1)) + (assert-equal 1 (vector-ref v 0)) + (assert-equal 3 (vector-ref v 2)))) + (deftest + "vector-set! first slot" + (let + ((v (make-vector 3 0))) + (vector-set! v 0 42) + (assert-equal 42 (vector-ref v 0)))) + (deftest + "vector-set! last slot" + (let + ((v (make-vector 3 0))) + (vector-set! v 2 77) + (assert-equal 77 (vector-ref v 2)))) + (deftest + "vector-set! returns nil" + (let ((v (make-vector 3 0))) (assert-equal nil (vector-set! v 0 1)))) + (deftest + "vector->list empty" + (assert-equal (list) (vector->list (vector)))) + (deftest + "vector->list numbers" + (assert-equal (list 1 2 3) (vector->list (vector 1 2 3)))) + (deftest + "vector->list strings" + (assert-equal (list "a" "b") (vector->list (vector "a" "b")))) + (deftest + "list->vector empty" + (let ((v (list->vector (list)))) (assert-equal 0 (vector-length v)))) + (deftest + "list->vector numbers" + (let + ((v (list->vector (list 10 20 30)))) + (assert-equal 3 (vector-length v)) + (assert-equal 10 (vector-ref v 0)) + (assert-equal 20 (vector-ref v 1)) + (assert-equal 30 (vector-ref v 2)))) + (deftest + "vector-fill! sets all elements" + (let + ((v (vector 1 2 3))) + (vector-fill! v 0) + (assert-equal 0 (vector-ref v 0)) + (assert-equal 0 (vector-ref v 1)) + (assert-equal 0 (vector-ref v 2)))) + (deftest + "vector-fill! returns nil" + (assert-equal nil (vector-fill! (make-vector 2 0) 7))) + (deftest + "vector-fill! string fill" + (let + ((v (make-vector 3 ""))) + (vector-fill! v "x") + (assert-equal "x" (vector-ref v 0)) + (assert-equal "x" (vector-ref v 2)))) + (deftest + "vector-copy full copy" + (let + ((v1 (vector 1 2 3)) (v2 (vector-copy (vector 1 2 3)))) + (assert-equal 3 (vector-length v2)) + (assert-equal 1 (vector-ref v2 0)) + (assert-equal 2 (vector-ref v2 1)) + (assert-equal 3 (vector-ref v2 2)))) + (deftest + "vector-copy is independent" + (let + ((v1 (vector 1 2 3))) + (let + ((v2 (vector-copy v1))) + (vector-set! v1 0 99) + (assert-equal 1 (vector-ref v2 0))))) + (deftest + "vector-copy with start" + (let + ((v (vector-copy (vector 10 20 30 40) 1))) + (assert-equal 3 (vector-length v)) + (assert-equal 20 (vector-ref v 0)) + (assert-equal 30 (vector-ref v 1)) + (assert-equal 40 (vector-ref v 2)))) + (deftest + "vector-copy with start and end" + (let + ((v (vector-copy (vector 10 20 30 40) 1 3))) + (assert-equal 2 (vector-length v)) + (assert-equal 20 (vector-ref v 0)) + (assert-equal 30 (vector-ref v 1)))) + (deftest + "vector-copy empty slice" + (let + ((v (vector-copy (vector 1 2 3) 1 1))) + (assert-equal 0 (vector-length v)))) + (deftest + "vector-ref out of bounds raises" + (let + ((ok false)) + (guard (exn (else (set! ok true))) (vector-ref (vector 1 2 3) 5)) + (assert ok))) + (deftest + "vector-ref negative index raises" + (let + ((ok false)) + (guard (exn (else (set! ok true))) (vector-ref (vector 1 2 3) -1)) + (assert ok))) + (deftest + "vector-set! out of bounds raises" + (let + ((ok false)) + (guard + (exn (else (set! ok true))) + (vector-set! (vector 1 2 3) 10 99)) + (assert ok))) + (deftest + "vector list round-trip" + (let + ((lst (list 5 10 15 20))) + (assert-equal lst (vector->list (list->vector lst))))) + (deftest + "vector mutation does not affect copy" + (let + ((v1 (vector 1 2 3))) + (let + ((v2 (vector-copy v1))) + (vector-set! v2 0 100) + (assert-equal 1 (vector-ref v1 0)) + (assert-equal 100 (vector-ref v2 0))))) + (deftest + "vector-length after fill" + (let + ((v (make-vector 5 0))) + (vector-fill! v 1) + (assert-equal 5 (vector-length v)))))