Fix JIT closure isolation, SX wire format, server diagnostics

Root cause: _env_bind_hook mirrored ALL env_bind calls (including
lambda parameter bindings) to the shared VM globals table. Factory
functions like make-page-fn that return closures capturing different
values for the same param names (default-name, prefix, suffix) would
have the last call's values overwrite all previous closures' captured
state in globals. OP_GLOBAL_GET reads globals first, so all closures
returned the last factory call's values.

Fix: only sync root-env bindings (parent=None) to VM globals. Lambda
parameter bindings stay in their local env, found via vm_closure_env
fallback in OP_GLOBAL_GET.

Also in this commit:
- OP_CLOSURE propagates parent vm_closure_env to child closures
- Remove JIT globals injection (closure vars found via env chain)
- sx_server.ml: SX-Request header → returns text/sx (aser only)
- sx_server.ml: diagnostic endpoint GET /sx/_debug/{env,eval,route}
- sx_server.ml: page helper stubs for deep page rendering
- sx_server.ml: skip client-libs/ dir (browser-only definitions)
- adapter-html.sx: unknown components → HTML comment (not error)
- sx-platform.js: .sxbc fallback loader for bytecode modules
- Delete sx_http.ml (standalone HTTP server, unused)
- Delete stale .sxbc.json files (arity=0 bug, replaced by .sxbc)
- 7 new closure isolation tests in test-closure-isolation.sx
- mcp_tree.ml: emit arity + upvalue-count in .sxbc.json output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 17:28:47 +00:00
parent 408eca1cb0
commit 80931e4972
43 changed files with 1528 additions and 1268 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-28T22:04:02Z";
var SX_VERSION = "2026-03-30T12:33:49Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -1104,6 +1104,63 @@ PRIMITIVES["make-ho-setup-frame"] = makeHoSetupFrame;
var makeCompTraceFrame = function(name, file) { return {"env": file, "type": "comp-trace", "name": name}; };
PRIMITIVES["make-comp-trace-frame"] = makeCompTraceFrame;
// kont-collect-comp-trace
var kontCollectCompTrace = function(kont) { return (isSxTruthy(isEmpty(kont)) ? [] : (function() {
var frame = first(kont);
return (isSxTruthy((frameType(frame) == "comp-trace")) ? cons({"file": get(frame, "file"), "name": get(frame, "name")}, kontCollectCompTrace(rest(kont))) : kontCollectCompTrace(rest(kont)));
})()); };
PRIMITIVES["kont-collect-comp-trace"] = kontCollectCompTrace;
// make-handler-frame
var makeHandlerFrame = function(handlers, remaining, env) { return {"env": env, "type": "handler", "f": handlers, "remaining": remaining}; };
PRIMITIVES["make-handler-frame"] = makeHandlerFrame;
// make-restart-frame
var makeRestartFrame = function(restarts, remaining, env) { return {"env": env, "type": "restart", "f": restarts, "remaining": remaining}; };
PRIMITIVES["make-restart-frame"] = makeRestartFrame;
// make-signal-return-frame
var makeSignalReturnFrame = function(env, savedKont) { return {"env": env, "type": "signal-return", "f": savedKont}; };
PRIMITIVES["make-signal-return-frame"] = makeSignalReturnFrame;
// find-matching-handler
var findMatchingHandler = function(handlers, condition) { return (isSxTruthy(isEmpty(handlers)) ? NIL : (function() {
var pair = first(handlers);
return (function() {
var pred = first(pair);
var handlerFn = nth(pair, 1);
return (isSxTruthy(cekCall(pred, [condition])) ? handlerFn : findMatchingHandler(rest(handlers), condition));
})();
})()); };
PRIMITIVES["find-matching-handler"] = findMatchingHandler;
// kont-find-handler
var kontFindHandler = function(kont, condition) { return (isSxTruthy(isEmpty(kont)) ? NIL : (function() {
var frame = first(kont);
return (isSxTruthy((frameType(frame) == "handler")) ? (function() {
var match = findMatchingHandler(get(frame, "f"), condition);
return (isSxTruthy(isNil(match)) ? kontFindHandler(rest(kont), condition) : match);
})() : kontFindHandler(rest(kont), condition));
})()); };
PRIMITIVES["kont-find-handler"] = kontFindHandler;
// find-named-restart
var findNamedRestart = function(restarts, name) { return (isSxTruthy(isEmpty(restarts)) ? NIL : (function() {
var entry = first(restarts);
return (isSxTruthy((first(entry) == name)) ? entry : findNamedRestart(rest(restarts), name));
})()); };
PRIMITIVES["find-named-restart"] = findNamedRestart;
// kont-find-restart
var kontFindRestart = function(kont, name) { return (isSxTruthy(isEmpty(kont)) ? NIL : (function() {
var frame = first(kont);
return (isSxTruthy((frameType(frame) == "restart")) ? (function() {
var match = findNamedRestart(get(frame, "f"), name);
return (isSxTruthy(isNil(match)) ? kontFindRestart(rest(kont), name) : [match, frame, rest(kont)]);
})() : kontFindRestart(rest(kont), name));
})()); };
PRIMITIVES["kont-find-restart"] = kontFindRestart;
// frame-type
var frameType = function(f) { return get(f, "type"); };
PRIMITIVES["frame-type"] = frameType;
@@ -1200,7 +1257,7 @@ PRIMITIVES["*prim-param-types*"] = _primParamTypes_;
PRIMITIVES["set-prim-param-types!"] = setPrimParamTypes_b;
// value-matches-type?
var valueMatchesType_p = function(val, expectedType) { return (isSxTruthy((expectedType == "any")) ? true : (isSxTruthy((expectedType == "number")) ? isNumber(val) : (isSxTruthy((expectedType == "string")) ? isString(val) : (isSxTruthy((expectedType == "boolean")) ? boolean_p(val) : (isSxTruthy((expectedType == "nil")) ? isNil(val) : (isSxTruthy((expectedType == "list")) ? isList(val) : (isSxTruthy((expectedType == "dict")) ? isDict(val) : (isSxTruthy((expectedType == "lambda")) ? isLambda(val) : (isSxTruthy((expectedType == "symbol")) ? (typeOf(val) == "symbol") : (isSxTruthy((expectedType == "keyword")) ? (typeOf(val) == "keyword") : (isSxTruthy((isSxTruthy(isString(expectedType)) && endsWith(expectedType, "?"))) ? sxOr(isNil(val), valueMatchesType_p(val, slice(expectedType, 0, (stringLength(expectedType) - 1)))) : true))))))))))); };
var valueMatchesType_p = function(val, expectedType) { return match(expectedType, ["any", true], ["number", isNumber(val)], ["string", isString(val)], ["boolean", boolean_p(val)], ["nil", isNil(val)], ["list", isList(val)], ["dict", isDict(val)], ["lambda", isLambda(val)], ["symbol", (typeOf(val) == "symbol")], ["keyword", (typeOf(val) == "keyword")], _((isSxTruthy((isSxTruthy(isString(expectedType)) && endsWith(expectedType, "?"))) ? sxOr(isNil(val), valueMatchesType_p(val, slice(expectedType, 0, (stringLength(expectedType) - 1)))) : true))); };
PRIMITIVES["value-matches-type?"] = valueMatchesType_p;
// strict-check-args
@@ -1334,6 +1391,9 @@ PRIMITIVES["sf-lambda"] = sfLambda;
effectAnns[symbolName(nameSym)] = effectList;
return envBind(env, "*effect-annotations*", effectAnns);
})();
}
if (isSxTruthy(envHas(env, "*current-file*"))) {
componentSetFile_b(comp, envGet(env, "*current-file*"));
}
envBind(env, symbolName(nameSym), comp);
return comp;
@@ -1388,6 +1448,9 @@ PRIMITIVES["parse-comp-params"] = parseCompParams;
var hasChildren = nth(parsed, 1);
return (function() {
var island = makeIsland(compName, params, hasChildren, body, env);
if (isSxTruthy(envHas(env, "*current-file*"))) {
componentSetFile_b(island, envGet(env, "*current-file*"));
}
envBind(env, symbolName(nameSym), island);
return island;
})();
@@ -1555,14 +1618,96 @@ PRIMITIVES["step-eval"] = stepEval;
var args = rest(expr);
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? (isSxTruthy(isEmpty(expr)) ? makeCekValue([], env, kont) : makeCekState(first(expr), env, kontPush(makeMapFrame(NIL, rest(expr), [], env), kont))) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
var name = symbolName(head);
return (isSxTruthy((name == "if")) ? stepSfIf(args, env, kont) : (isSxTruthy((name == "when")) ? stepSfWhen(args, env, kont) : (isSxTruthy((name == "cond")) ? stepSfCond(args, env, kont) : (isSxTruthy((name == "case")) ? stepSfCase(args, env, kont) : (isSxTruthy((name == "and")) ? stepSfAnd(args, env, kont) : (isSxTruthy((name == "or")) ? stepSfOr(args, env, kont) : (isSxTruthy((name == "let")) ? stepSfLet(args, env, kont) : (isSxTruthy((name == "let*")) ? stepSfLet(args, env, kont) : (isSxTruthy((name == "lambda")) ? stepSfLambda(args, env, kont) : (isSxTruthy((name == "fn")) ? stepSfLambda(args, env, kont) : (isSxTruthy((name == "define")) ? stepSfDefine(args, env, kont) : (isSxTruthy((name == "defcomp")) ? makeCekValue(sfDefcomp(args, env), env, kont) : (isSxTruthy((name == "defisland")) ? makeCekValue(sfDefisland(args, env), env, kont) : (isSxTruthy((name == "defmacro")) ? makeCekValue(sfDefmacro(args, env), env, kont) : (isSxTruthy((name == "begin")) ? stepSfBegin(args, env, kont) : (isSxTruthy((name == "do")) ? stepSfBegin(args, env, kont) : (isSxTruthy((name == "quote")) ? makeCekValue((isSxTruthy(isEmpty(args)) ? NIL : first(args)), env, kont) : (isSxTruthy((name == "quasiquote")) ? makeCekValue(qqExpand(first(args), env), env, kont) : (isSxTruthy((name == "->")) ? stepSfThreadFirst(args, env, kont) : (isSxTruthy((name == "set!")) ? stepSfSet(args, env, kont) : (isSxTruthy((name == "letrec")) ? stepSfLetrec(args, env, kont) : (isSxTruthy((name == "reset")) ? stepSfReset(args, env, kont) : (isSxTruthy((name == "shift")) ? stepSfShift(args, env, kont) : (isSxTruthy((name == "deref")) ? stepSfDeref(args, env, kont) : (isSxTruthy((name == "scope")) ? stepSfScope(args, env, kont) : (isSxTruthy((name == "provide")) ? stepSfProvide(args, env, kont) : (isSxTruthy((name == "context")) ? stepSfContext(args, env, kont) : (isSxTruthy((name == "emit!")) ? stepSfEmit(args, env, kont) : (isSxTruthy((name == "emitted")) ? stepSfEmitted(args, env, kont) : (isSxTruthy((name == "dynamic-wind")) ? makeCekValue(sfDynamicWind(args, env), env, kont) : (isSxTruthy((name == "map")) ? stepHoMap(args, env, kont) : (isSxTruthy((name == "map-indexed")) ? stepHoMapIndexed(args, env, kont) : (isSxTruthy((name == "filter")) ? stepHoFilter(args, env, kont) : (isSxTruthy((name == "reduce")) ? stepHoReduce(args, env, kont) : (isSxTruthy((name == "some")) ? stepHoSome(args, env, kont) : (isSxTruthy((name == "every?")) ? stepHoEvery(args, env, kont) : (isSxTruthy((name == "for-each")) ? stepHoForEach(args, env, kont) : (isSxTruthy(dictHas(_customSpecialForms, name)) ? makeCekValue((get(_customSpecialForms, name))(args, env), env, kont) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
return match(name, ["if", stepSfIf(args, env, kont)], ["when", stepSfWhen(args, env, kont)], ["cond", stepSfCond(args, env, kont)], ["case", stepSfCase(args, env, kont)], ["and", stepSfAnd(args, env, kont)], ["or", stepSfOr(args, env, kont)], ["let", stepSfLet(args, env, kont)], ["let*", stepSfLet(args, env, kont)], ["lambda", stepSfLambda(args, env, kont)], ["fn", stepSfLambda(args, env, kont)], ["define", stepSfDefine(args, env, kont)], ["defcomp", makeCekValue(sfDefcomp(args, env), env, kont)], ["defisland", makeCekValue(sfDefisland(args, env), env, kont)], ["defmacro", makeCekValue(sfDefmacro(args, env), env, kont)], ["begin", stepSfBegin(args, env, kont)], ["do", stepSfBegin(args, env, kont)], ["quote", makeCekValue((isSxTruthy(isEmpty(args)) ? NIL : first(args)), env, kont)], ["quasiquote", makeCekValue(qqExpand(first(args), env), env, kont)], ["->", stepSfThreadFirst(args, env, kont)], ["set!", stepSfSet(args, env, kont)], ["letrec", stepSfLetrec(args, env, kont)], ["reset", stepSfReset(args, env, kont)], ["shift", stepSfShift(args, env, kont)], ["deref", stepSfDeref(args, env, kont)], ["scope", stepSfScope(args, env, kont)], ["provide", stepSfProvide(args, env, kont)], ["context", stepSfContext(args, env, kont)], ["emit!", stepSfEmit(args, env, kont)], ["emitted", stepSfEmitted(args, env, kont)], ["handler-bind", stepSfHandlerBind(args, env, kont)], ["restart-case", stepSfRestartCase(args, env, kont)], ["signal-condition", stepSfSignal(args, env, kont)], ["invoke-restart", stepSfInvokeRestart(args, env, kont)], ["match", stepSfMatch(args, env, kont)], ["dynamic-wind", makeCekValue(sfDynamicWind(args, env), env, kont)], ["map", stepHoMap(args, env, kont)], ["map-indexed", stepHoMapIndexed(args, env, kont)], ["filter", stepHoFilter(args, env, kont)], ["reduce", stepHoReduce(args, env, kont)], ["some", stepHoSome(args, env, kont)], ["every?", stepHoEvery(args, env, kont)], ["for-each", stepHoForEach(args, env, kont)], _((isSxTruthy(dictHas(_customSpecialForms, 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) && _renderCheck(expr, env))) ? makeCekValue(_renderFn(expr, env), env, kont) : stepEvalCall(head, args, env, kont))))));
})() : stepEvalCall(head, args, env, kont)));
})(); };
PRIMITIVES["step-eval-list"] = stepEvalList;
// match-find-clause
var matchFindClause = function(val, clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() {
var clause = first(clauses);
var pattern = first(clause);
var body = nth(clause, 1);
var local = envExtend(env);
return (isSxTruthy(matchPattern(pattern, val, local)) ? [local, body] : matchFindClause(val, rest(clauses), env));
})()); };
PRIMITIVES["match-find-clause"] = matchFindClause;
// match-pattern
var matchPattern = function(pattern, value, env) { return (isSxTruthy((pattern == new Symbol("_"))) ? true : (isSxTruthy((isSxTruthy(isList(pattern)) && isSxTruthy((len(pattern) == 2)) && (first(pattern) == new Symbol("?")))) ? (function() {
var pred = trampoline(evalExpr(nth(pattern, 1), env));
return cekCall(pred, [value]);
})() : (isSxTruthy((isSxTruthy(isList(pattern)) && isSxTruthy(!isSxTruthy(isEmpty(pattern))) && (first(pattern) == new Symbol("quote")))) ? (value == nth(pattern, 1)) : (isSxTruthy(symbol_p(pattern)) ? (envBind(env, symbolName(pattern), value), true) : (isSxTruthy((isSxTruthy(isList(pattern)) && isList(value))) ? (isSxTruthy(!isSxTruthy((len(pattern) == len(value)))) ? false : (function() {
var pairs = zip(pattern, value);
return isEvery(function(pair) { return matchPattern(first(pair), nth(pair, 1), env); }, pairs);
})()) : (pattern == value)))))); };
PRIMITIVES["match-pattern"] = matchPattern;
// step-sf-match
var stepSfMatch = function(args, env, kont) { return (function() {
var val = trampoline(evalExpr(first(args), env));
var clauses = rest(args);
return (function() {
var result = matchFindClause(val, clauses, env);
return (isSxTruthy(isNil(result)) ? error((String("match: no clause matched ") + String(inspect(val)))) : makeCekState(nth(result, 1), first(result), kont));
})();
})(); };
PRIMITIVES["step-sf-match"] = stepSfMatch;
// step-sf-handler-bind
var stepSfHandlerBind = function(args, env, kont) { return (function() {
var handlerSpecs = first(args);
var body = rest(args);
var handlers = map(function(spec) { return [trampoline(evalExpr(first(spec), env)), trampoline(evalExpr(nth(spec, 1), env))]; }, handlerSpecs);
return (isSxTruthy(isEmpty(body)) ? makeCekValue(NIL, env, kont) : makeCekState(first(body), env, kontPush(makeHandlerFrame(handlers, rest(body), env), kont)));
})(); };
PRIMITIVES["step-sf-handler-bind"] = stepSfHandlerBind;
// step-sf-restart-case
var stepSfRestartCase = function(args, env, kont) { return (function() {
var body = first(args);
var restartSpecs = rest(args);
var restarts = map(function(spec) { return [(isSxTruthy(symbol_p(first(spec))) ? symbolName(first(spec)) : first(spec)), nth(spec, 1), nth(spec, 2)]; }, restartSpecs);
return makeCekState(body, env, kontPush(makeRestartFrame(restarts, [], env), kont));
})(); };
PRIMITIVES["step-sf-restart-case"] = stepSfRestartCase;
// step-sf-signal
var stepSfSignal = function(args, env, kont) { return (function() {
var condition = trampoline(evalExpr(first(args), env));
var handlerFn = kontFindHandler(kont, condition);
return (isSxTruthy(isNil(handlerFn)) ? error((String("Unhandled condition: ") + String(inspect(condition)))) : continueWithCall(handlerFn, [condition], env, [condition], kontPush(makeSignalReturnFrame(env, kont), kont)));
})(); };
PRIMITIVES["step-sf-signal"] = stepSfSignal;
// step-sf-invoke-restart
var stepSfInvokeRestart = function(args, env, kont) { return (function() {
var restartName = (function() {
var rn = (isSxTruthy(symbol_p(first(args))) ? symbolName(first(args)) : trampoline(evalExpr(first(args), env)));
return (isSxTruthy(symbol_p(rn)) ? symbolName(rn) : rn);
})();
var restartArg = (isSxTruthy((len(args) >= 2)) ? trampoline(evalExpr(nth(args, 1), env)) : NIL);
var found = kontFindRestart(kont, restartName);
return (isSxTruthy(isNil(found)) ? error((String("No restart named: ") + String(inspect(restartName)))) : (function() {
var entry = first(found);
var restartFrame = nth(found, 1);
var restKont = nth(found, 2);
return (function() {
var params = nth(entry, 1);
var body = nth(entry, 2);
var restartEnv = envExtend(get(restartFrame, "env"));
if (isSxTruthy(!isSxTruthy(isEmpty(params)))) {
envBind(restartEnv, first(params), restartArg);
}
return makeCekState(body, restartEnv, restKont);
})();
})());
})(); };
PRIMITIVES["step-sf-invoke-restart"] = stepSfInvokeRestart;
// step-sf-if
var stepSfIf = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeIfFrame(nth(args, 1), (isSxTruthy((len(args) > 2)) ? nth(args, 2) : NIL), env), kont)); };
PRIMITIVES["step-sf-if"] = stepSfIf;
@@ -1786,29 +1931,29 @@ PRIMITIVES["ho-swap-args"] = hoSwapArgs;
var ordered = hoSwapArgs(hoType, evaled);
return (function() {
var f = first(ordered);
return (isSxTruthy((hoType == "map")) ? (function() {
return match(hoType, ["map", (function() {
var coll = nth(ordered, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeMapFrame(f, rest(coll), [], env), kont)));
})() : (isSxTruthy((hoType == "map-indexed")) ? (function() {
})()], ["map-indexed", (function() {
var coll = nth(ordered, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [0, first(coll)], env, [], kontPush(makeMapIndexedFrame(f, rest(coll), [], env), kont)));
})() : (isSxTruthy((hoType == "filter")) ? (function() {
})()], ["filter", (function() {
var coll = nth(ordered, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeFilterFrame(f, rest(coll), [], first(coll), env), kont)));
})() : (isSxTruthy((hoType == "reduce")) ? (function() {
})()], ["reduce", (function() {
var init = nth(ordered, 1);
var coll = nth(ordered, 2);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(init, env, kont) : continueWithCall(f, [init, first(coll)], env, [], kontPush(makeReduceFrame(f, rest(coll), env), kont)));
})() : (isSxTruthy((hoType == "some")) ? (function() {
})()], ["some", (function() {
var coll = nth(ordered, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(false, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeSomeFrame(f, rest(coll), env), kont)));
})() : (isSxTruthy((hoType == "every")) ? (function() {
})()], ["every", (function() {
var coll = nth(ordered, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(true, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeEveryFrame(f, rest(coll), env), kont)));
})() : (isSxTruthy((hoType == "for-each")) ? (function() {
})()], ["for-each", (function() {
var coll = nth(ordered, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(NIL, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeForEachFrame(f, rest(coll), env), kont)));
})() : error((String("Unknown HO type: ") + String(hoType))))))))));
})()], _(error((String("Unknown HO type: ") + String(hoType)))));
})();
})(); };
PRIMITIVES["ho-setup-dispatch"] = hoSetupDispatch;
@@ -1850,15 +1995,15 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
var frame = kontTop(kont);
var restK = kontPop(kont);
var ft = frameType(frame);
return (isSxTruthy((ft == "if")) ? (isSxTruthy((isSxTruthy(value) && !isSxTruthy(isNil(value)))) ? makeCekState(get(frame, "then"), get(frame, "env"), restK) : (isSxTruthy(isNil(get(frame, "else"))) ? makeCekValue(NIL, env, restK) : makeCekState(get(frame, "else"), get(frame, "env"), restK))) : (isSxTruthy((ft == "when")) ? (isSxTruthy((isSxTruthy(value) && !isSxTruthy(isNil(value)))) ? (function() {
return match(ft, ["if", (isSxTruthy((isSxTruthy(value) && !isSxTruthy(isNil(value)))) ? makeCekState(get(frame, "then"), get(frame, "env"), restK) : (isSxTruthy(isNil(get(frame, "else"))) ? makeCekValue(NIL, env, restK) : makeCekState(get(frame, "else"), get(frame, "env"), restK)))], ["when", (isSxTruthy((isSxTruthy(value) && !isSxTruthy(isNil(value)))) ? (function() {
var body = get(frame, "body");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(body)) ? makeCekValue(NIL, fenv, restK) : (isSxTruthy((len(body) == 1)) ? makeCekState(first(body), fenv, restK) : makeCekState(first(body), fenv, kontPush(makeBeginFrame(rest(body), fenv), restK))));
})() : makeCekValue(NIL, env, restK)) : (isSxTruthy((ft == "begin")) ? (function() {
})() : makeCekValue(NIL, env, restK))], ["begin", (function() {
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : (isSxTruthy((len(remaining) == 1)) ? makeCekState(first(remaining), fenv, restK) : makeCekState(first(remaining), fenv, kontPush(makeBeginFrame(rest(remaining), fenv), restK))));
})() : (isSxTruthy((ft == "let")) ? (function() {
})()], ["let", (function() {
var name = get(frame, "name");
var remaining = get(frame, "remaining");
var body = get(frame, "body");
@@ -1869,7 +2014,7 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
var vname = (isSxTruthy((typeOf(first(nextBinding)) == "symbol")) ? symbolName(first(nextBinding)) : first(nextBinding));
return makeCekState(nth(nextBinding, 1), local, kontPush(makeLetFrame(vname, rest(remaining), body, local), restK));
})());
})() : (isSxTruthy((ft == "define")) ? (function() {
})()], ["define", (function() {
var name = get(frame, "name");
var fenv = get(frame, "env");
var hasEffects = get(frame, "has-effects");
@@ -1880,25 +2025,25 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
envBind(fenv, name, value);
if (isSxTruthy(hasEffects)) {
(function() {
var effectNames = (isSxTruthy((typeOf(effectList) == "list")) ? map(function(e) { return (isSxTruthy((typeOf(e) == "symbol")) ? symbolName(e) : (String(e))); }, effectList) : [(String(effectList))]);
var effectNames = map(function(e) { return (isSxTruthy((typeOf(e) == "symbol")) ? symbolName(e) : e); }, effectList);
var effectAnns = (isSxTruthy(envHas(fenv, "*effect-annotations*")) ? envGet(fenv, "*effect-annotations*") : {});
effectAnns[name] = effectNames;
return envBind(fenv, "*effect-annotations*", effectAnns);
})();
}
return makeCekValue(value, fenv, restK);
})() : (isSxTruthy((ft == "set")) ? (function() {
})()], ["set", (function() {
var name = get(frame, "name");
var fenv = get(frame, "env");
envSet(fenv, name, value);
return makeCekValue(value, env, restK);
})() : (isSxTruthy((ft == "and")) ? (isSxTruthy(!isSxTruthy(value)) ? makeCekValue(value, env, restK) : (function() {
})()], ["and", (isSxTruthy(!isSxTruthy(value)) ? makeCekValue(value, env, restK) : (function() {
var remaining = get(frame, "remaining");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, env, restK) : makeCekState(first(remaining), get(frame, "env"), (isSxTruthy((len(remaining) == 1)) ? restK : kontPush(makeAndFrame(rest(remaining), get(frame, "env")), restK))));
})()) : (isSxTruthy((ft == "or")) ? (isSxTruthy(value) ? makeCekValue(value, env, restK) : (function() {
})())], ["or", (isSxTruthy(value) ? makeCekValue(value, env, restK) : (function() {
var remaining = get(frame, "remaining");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(false, env, restK) : makeCekState(first(remaining), get(frame, "env"), (isSxTruthy((len(remaining) == 1)) ? restK : kontPush(makeOrFrame(rest(remaining), get(frame, "env")), restK))));
})()) : (isSxTruthy((ft == "cond")) ? (function() {
})())], ["cond", (function() {
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
var scheme_p = get(frame, "scheme");
@@ -1910,18 +2055,18 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
return (isSxTruthy(isElseClause(nextTest)) ? makeCekState(nth(nextClause, 1), fenv, restK) : makeCekState(nextTest, fenv, kontPush(makeCondFrame(nextClauses, fenv, true), restK)));
})());
})()) : (isSxTruthy(value) ? makeCekState(nth(remaining, 1), fenv, restK) : (function() {
var next = slice(remaining, 2);
var next = slice(remaining, 2, len(remaining));
return (isSxTruthy((len(next) < 2)) ? makeCekValue(NIL, fenv, restK) : (function() {
var nextTest = first(next);
return (isSxTruthy(isElseClause(nextTest)) ? makeCekState(nth(next, 1), fenv, restK) : makeCekState(nextTest, fenv, kontPush(makeCondFrame(next, fenv, false), restK)));
})());
})()));
})() : (isSxTruthy((ft == "case")) ? (function() {
})()], ["case", (function() {
var matchVal = get(frame, "match-val");
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isNil(matchVal)) ? sfCaseStepLoop(value, remaining, fenv, restK) : sfCaseStepLoop(matchVal, remaining, fenv, restK));
})() : (isSxTruthy((ft == "thread")) ? (function() {
})()], ["thread", (function() {
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : (function() {
@@ -1929,19 +2074,11 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
var restForms = rest(remaining);
var newKont = (isSxTruthy(isEmpty(rest(remaining))) ? restK : kontPush(makeThreadFrame(rest(remaining), fenv), restK));
return (isSxTruthy((isSxTruthy((typeOf(form) == "list")) && isSxTruthy(!isSxTruthy(isEmpty(form))) && isSxTruthy((typeOf(first(form)) == "symbol")) && hoFormName_p(symbolName(first(form))))) ? makeCekState(cons(first(form), cons([new Symbol("quote"), value], rest(form))), fenv, newKont) : (function() {
var result = (isSxTruthy((typeOf(form) == "list")) ? (function() {
var f = trampoline(evalExpr(first(form), fenv));
var rargs = map(function(a) { return trampoline(evalExpr(a, fenv)); }, rest(form));
var allArgs = cons(value, rargs);
return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? apply(f, allArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, allArgs, fenv)) : error((String("-> form not callable: ") + String(inspect(f))))));
})() : (function() {
var f = trampoline(evalExpr(form, fenv));
return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? f(value) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, [value], fenv)) : error((String("-> form not callable: ") + String(inspect(f))))));
})());
var result = threadInsertArg(form, value, fenv);
return (isSxTruthy(isEmpty(restForms)) ? makeCekValue(result, fenv, restK) : makeCekValue(result, fenv, kontPush(makeThreadFrame(restForms, fenv), restK)));
})());
})());
})() : (isSxTruthy((ft == "arg")) ? (function() {
})()], ["arg", (function() {
var f = get(frame, "f");
var evaled = get(frame, "evaled");
var remaining = get(frame, "remaining");
@@ -1952,7 +2089,7 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
var newEvaled = append(evaled, [value]);
return (isSxTruthy(isEmpty(remaining)) ? ((isSxTruthy((isSxTruthy(_strict_) && hname)) ? strictCheckArgs(hname, newEvaled) : NIL), continueWithCall(f, newEvaled, fenv, rawArgs, restK)) : makeCekState(first(remaining), fenv, kontPush(makeArgFrame(f, newEvaled, rest(remaining), fenv, rawArgs, hname), restK)));
})());
})() : (isSxTruthy((ft == "dict")) ? (function() {
})()], ["dict", (function() {
var remaining = get(frame, "remaining");
var results = get(frame, "results");
var fenv = get(frame, "env");
@@ -1968,48 +2105,48 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
return makeCekState(nth(nextEntry, 1), fenv, kontPush(makeDictFrame(rest(remaining), append(completed, [[first(nextEntry)]]), fenv), restK));
})());
})();
})() : (isSxTruthy((ft == "ho-setup")) ? (function() {
})()], ["ho-setup", (function() {
var hoType = get(frame, "ho-type");
var remaining = get(frame, "remaining");
var evaled = append(get(frame, "evaled"), [value]);
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? hoSetupDispatch(hoType, evaled, fenv, restK) : makeCekState(first(remaining), fenv, kontPush(makeHoSetupFrame(hoType, rest(remaining), evaled, fenv), restK)));
})() : (isSxTruthy((ft == "reset")) ? makeCekValue(value, env, restK) : (isSxTruthy((ft == "deref")) ? (function() {
var val = value;
})()], ["reset", makeCekValue(value, env, restK)], ["deref", (function() {
var val = get(frame, "value");
var fenv = get(frame, "env");
return (isSxTruthy(!isSxTruthy(isSignal(val))) ? makeCekValue(val, fenv, restK) : (isSxTruthy(hasReactiveResetFrame_p(restK)) ? reactiveShiftDeref(val, fenv, restK) : ((function() {
var ctx = sxContext("sx-reactive", NIL);
var ctx = getTrackingContext();
return (isSxTruthy(ctx) ? (function() {
var depList = get(ctx, "deps");
var notifyFn = get(ctx, "notify");
return (isSxTruthy(!isSxTruthy(contains(depList, val))) ? (append_b(depList, val), signalAddSub(val, notifyFn)) : NIL);
})() : NIL);
})(), makeCekValue(signalValue(val), fenv, restK))));
})() : (isSxTruthy((ft == "reactive-reset")) ? (function() {
})()], ["reactive-reset", (function() {
var updateFn = get(frame, "update-fn");
var first_p = get(frame, "first-render");
if (isSxTruthy((isSxTruthy(updateFn) && !isSxTruthy(first_p)))) {
cekCall(updateFn, [value]);
}
return makeCekValue(value, env, restK);
})() : (isSxTruthy((ft == "scope")) ? (function() {
})()], ["scope", (function() {
var name = get(frame, "name");
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? (scopePop(name), makeCekValue(value, fenv, restK)) : makeCekState(first(remaining), fenv, kontPush(makeScopeFrame(name, rest(remaining), fenv), restK)));
})() : (isSxTruthy((ft == "provide")) ? (function() {
})()], ["provide", (function() {
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : makeCekState(first(remaining), fenv, kontPush(makeProvideFrame(get(frame, "name"), get(frame, "value"), rest(remaining), fenv), restK)));
})() : (isSxTruthy((ft == "scope-acc")) ? (function() {
})()], ["scope-acc", (function() {
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : makeCekState(first(remaining), fenv, kontPush((function() {
var newFrame = makeScopeAccFrame(get(frame, "name"), get(frame, "value"), rest(remaining), fenv);
var newFrame = makeScopeAccFrame(get(frame, "name"), rest(remaining), fenv);
newFrame["emitted"] = get(frame, "emitted");
return newFrame;
})(), restK)));
})() : (isSxTruthy((ft == "map")) ? (function() {
})()], ["map", (function() {
var f = get(frame, "f");
var remaining = get(frame, "remaining");
var results = get(frame, "results");
@@ -2023,7 +2160,7 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
return continueWithCall(f, callArgs, fenv, [], kontPush(nextFrame, restK));
})());
})();
})() : (isSxTruthy((ft == "filter")) ? (function() {
})()], ["filter", (function() {
var f = get(frame, "f");
var remaining = get(frame, "remaining");
var results = get(frame, "results");
@@ -2033,27 +2170,34 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
var newResults = (isSxTruthy(value) ? append(results, [currentItem]) : results);
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(newResults, fenv, restK) : continueWithCall(f, [first(remaining)], fenv, [], kontPush(makeFilterFrame(f, rest(remaining), newResults, first(remaining), fenv), restK)));
})();
})() : (isSxTruthy((ft == "reduce")) ? (function() {
})()], ["reduce", (function() {
var f = get(frame, "f");
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : continueWithCall(f, [value, first(remaining)], fenv, [], kontPush(makeReduceFrame(f, rest(remaining), fenv), restK)));
})() : (isSxTruthy((ft == "for-each")) ? (function() {
})()], ["for-each", (function() {
var f = get(frame, "f");
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(NIL, fenv, restK) : continueWithCall(f, [first(remaining)], fenv, [], kontPush(makeForEachFrame(f, rest(remaining), fenv), restK)));
})() : (isSxTruthy((ft == "some")) ? (function() {
})()], ["some", (function() {
var f = get(frame, "f");
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(value) ? makeCekValue(value, fenv, restK) : (isSxTruthy(isEmpty(remaining)) ? makeCekValue(false, fenv, restK) : continueWithCall(f, [first(remaining)], fenv, [], kontPush(makeSomeFrame(f, rest(remaining), fenv), restK))));
})() : (isSxTruthy((ft == "every")) ? (function() {
})()], ["every", (function() {
var f = get(frame, "f");
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(!isSxTruthy(value)) ? makeCekValue(false, fenv, restK) : (isSxTruthy(isEmpty(remaining)) ? makeCekValue(true, fenv, restK) : continueWithCall(f, [first(remaining)], fenv, [], kontPush(makeEveryFrame(f, rest(remaining), fenv), restK))));
})() : (isSxTruthy((ft == "comp-trace")) ? makeCekValue(value, env, restK) : error((String("Unknown frame type: ") + String(ft))))))))))))))))))))))))))))));
})()], ["handler", (function() {
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : makeCekState(first(remaining), fenv, kontPush(makeHandlerFrame(get(frame, "f"), rest(remaining), fenv), restK)));
})()], ["restart", makeCekValue(value, env, restK)], ["signal-return", (function() {
var savedKont = get(frame, "saved-kont");
return makeCekValue(value, get(frame, "env"), savedKont);
})()], ["comp-trace", makeCekValue(value, env, restK)], _(error((String("Unknown frame type: ") + String(ft)))));
})());
})(); };
PRIMITIVES["step-continue"] = stepContinue;
@@ -2082,7 +2226,7 @@ PRIMITIVES["step-continue"] = stepContinue;
if (isSxTruthy(componentHasChildren(f))) {
envBind(local, "children", children);
}
return makeCekState(componentBody(f), local, kontPush(makeCompTraceFrame(componentName(f), NIL), kont));
return makeCekState(componentBody(f), local, kontPush(makeCompTraceFrame(componentName(f), componentFile(f)), kont));
})() : error((String("Not callable: ") + String(inspect(f)))))))); };
PRIMITIVES["continue-with-call"] = continueWithCall;
@@ -2897,7 +3041,7 @@ PRIMITIVES["render-html-marsh"] = renderHtmlMarsh;
envBind(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))));
}
return (function() {
var bodyHtml = cekTry(function() { return renderToHtml(componentBody(island), local); }, function(err) { return ""; });
var bodyHtml = renderToHtml(componentBody(island), local);
var stateSx = serializeIslandState(kwargs);
return (String("<span data-sx-island=\"") + String(escapeAttr(islandName)) + String("\"") + String((isSxTruthy(stateSx) ? (String(" data-sx-state=\"") + String(escapeAttr(stateSx)) + String("\"")) : "")) + String(">") + String(bodyHtml) + String("</span>"));
})();
@@ -3713,7 +3857,20 @@ PRIMITIVES["render-to-dom"] = renderToDom;
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(args, env, ns) : (isSxTruthy((name == "marsh")) ? renderDomMarsh(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? (isSxTruthy(scopePeek("sx-render-markers")) ? (function() {
var island = envGet(env, name);
var marker = domCreateElement("span", NIL);
var kwState = {};
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var kname = keywordName(arg);
var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
kwState[kname] = kval;
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : assoc(state, "i", (get(state, "i") + 1))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
domSetAttr(marker, "data-sx-island", componentName(island));
if (isSxTruthy(!isSxTruthy(isEmptyDict(kwState)))) {
domSetAttr(marker, "data-sx-state", sxSerialize(kwState));
}
return marker;
})() : renderDomIsland(envGet(env, name), args, env, ns)) : (isSxTruthy(startsWith(name, "~")) ? (function() {
var comp = envGet(env, name);
@@ -4074,6 +4231,9 @@ PRIMITIVES["render-lambda-dom"] = renderLambdaDom;
var container = domCreateElement("span", NIL);
var disposers = [];
domSetAttr(container, "data-sx-island", islandName);
if (isSxTruthy(!isSxTruthy(isEmptyDict(kwargs)))) {
domSetAttr(container, "data-sx-state", sxSerialize(kwargs));
}
markProcessed(container, "island-hydrated");
return (function() {
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(island), local, ns); });
@@ -4875,7 +5035,7 @@ return processElements(t); });
disposeIslandsIn(target);
return withTransition(useTransition, function() { return (function() {
var swapResult = swapDomNodes(target, content, swapStyle);
return postSwap(sxOr(swapResult, target));
return postSwap((isSxTruthy((swapStyle == "outerHTML")) ? domParent(sxOr(swapResult, target)) : sxOr(swapResult, target)));
})(); });
})();
})() : NIL);
@@ -4892,8 +5052,11 @@ PRIMITIVES["handle-sx-response"] = handleSxResponse;
disposeIslandsIn(target);
return (isSxTruthy(selectSel) ? (function() {
var html = selectHtmlFromDoc(doc, selectSel);
return withTransition(useTransition, function() { swapHtmlString(target, html, swapStyle);
return postSwap(target); });
return withTransition(useTransition, function() { return (function() {
var swapRoot = swapHtmlString(target, html, swapStyle);
logInfo((String("swap-root: ") + String((isSxTruthy(swapRoot) ? domTagName(swapRoot) : "nil")) + String(" target: ") + String(domTagName(target))));
return postSwap(sxOr(swapRoot, target));
})(); });
})() : (function() {
var container = domCreateElement("div", NIL);
domSetInnerHtml(container, domBodyInnerHtml(doc));
@@ -5684,7 +5847,7 @@ return cekCall(hook, NIL); }, _postRenderHooks_); };
PRIMITIVES["run-post-render-hooks"] = runPostRenderHooks;
// boot-init
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), runPostRenderHooks(), processElements(NIL), domListen(domWindow(), "popstate", function(e) { return handlePopstate(0); })); };
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), runPostRenderHooks(), processElements(NIL), domListen(domWindow(), "popstate", function(e) { return handlePopstate(0); }), domSetAttr(hostGet(domDocument(), "documentElement"), "data-sx-ready", "true"), domDispatch(domDocument(), "sx:ready", NIL), logInfo("sx:ready")); };
PRIMITIVES["boot-init"] = bootInit;

View File

@@ -228,38 +228,58 @@
}
/**
* Try loading a pre-compiled .sxbc.json bytecode module.
* Try loading a pre-compiled bytecode module.
* Tries .sxbc.json first, then .sxbc (SX s-expression format).
* Returns true on success, null on failure (caller falls back to .sx source).
*/
function loadBytecodeFile(path) {
var bcPath = path.replace(/\.sx$/, '.sxbc.json');
var url = _baseUrl + bcPath + _cacheBust;
// Try .sxbc.json (JSON dict format)
var jsonPath = path.replace(/\.sx$/, '.sxbc.json');
var jsonUrl = _baseUrl + jsonPath + _cacheBust;
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.open("GET", jsonUrl, false);
xhr.send();
if (xhr.status !== 200) return null;
var json = JSON.parse(xhr.responseText);
if (!json.module || json.magic !== 'SXBC') return null;
var module = {
_type: 'dict',
arity: json.module.arity || 0,
bytecode: { _type: 'list', items: json.module.bytecode },
constants: { _type: 'list', items: json.module.constants.map(deserializeConstant) },
};
var result = K.loadModule(module);
if (typeof result === 'string' && result.indexOf('Error') === 0) {
console.warn("[sx-platform] bytecode FAIL " + path + ":", result);
return null;
if (xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
if (json.module && json.magic === 'SXBC') {
var module = {
_type: 'dict',
arity: json.module.arity || 0,
bytecode: { _type: 'list', items: json.module.bytecode },
constants: { _type: 'list', items: json.module.constants.map(deserializeConstant) },
};
var result = K.loadModule(module);
if (typeof result !== 'string' || result.indexOf('Error') !== 0) {
console.log("[sx-platform] ok " + path + " (bytecode-json)");
return true;
}
console.warn("[sx-platform] bytecode-json FAIL " + path + ":", result);
}
}
console.log("[sx-platform] ok " + path + " (bytecode)");
return true;
} catch(e) {
return null;
}
} catch(e) { /* fall through to .sxbc */ }
// Try .sxbc (SX s-expression format, loaded via load-sxbc primitive)
var sxbcPath = path.replace(/\.sx$/, '.sxbc');
var sxbcUrl = _baseUrl + sxbcPath + _cacheBust;
try {
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", sxbcUrl, false);
xhr2.send();
if (xhr2.status === 200) {
// Store text in global, parse via SX to avoid JS string escaping
window.__sxbcText = xhr2.responseText;
var result2 = K.eval('(load-sxbc (first (parse (host-global "__sxbcText"))))');
delete window.__sxbcText;
if (typeof result2 !== 'string' || result2.indexOf('Error') !== 0) {
console.log("[sx-platform] ok " + path + " (bytecode-sx)");
return true;
}
console.warn("[sx-platform] bytecode-sx FAIL " + path + ":", result2);
}
} catch(e) { delete window.__sxbcText; /* fall through to source */ }
return null;
}
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,25 +1,554 @@
(define render-to-html :effects (render) (fn (expr (env :as dict)) (set-render-active! true) (case (type-of expr) "nil" "" "string" (escape-html expr) "number" (str expr) "boolean" (if expr "true" "false") "list" (if (empty? expr) "" (render-list-to-html expr env)) "symbol" (render-value-to-html (trampoline (eval-expr expr env)) env) "keyword" (escape-html (keyword-name expr)) "raw-html" (raw-html-content expr) "spread" (do (scope-emit! "element-attrs" (spread-attrs expr)) "") "thunk" (render-to-html (thunk-expr expr) (thunk-env expr)) :else (render-value-to-html (trampoline (eval-expr expr env)) env))))
(define
render-to-html
:effects (render)
(fn
(expr (env :as dict))
(set-render-active! true)
(case
(type-of expr)
"nil"
""
"string"
(escape-html expr)
"number"
(str expr)
"boolean"
(if expr "true" "false")
"list"
(if (empty? expr) "" (render-list-to-html expr env))
"symbol"
(render-value-to-html (trampoline (eval-expr expr env)) env)
"keyword"
(escape-html (keyword-name expr))
"raw-html"
(raw-html-content expr)
"spread"
(do (scope-emit! "element-attrs" (spread-attrs expr)) "")
"thunk"
(render-to-html (thunk-expr expr) (thunk-env expr))
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))
(define render-value-to-html :effects (render) (fn (val (env :as dict)) (case (type-of val) "nil" "" "string" (escape-html val) "number" (str val) "boolean" (if val "true" "false") "list" (render-list-to-html val env) "raw-html" (raw-html-content val) "spread" (do (scope-emit! "element-attrs" (spread-attrs val)) "") "thunk" (render-to-html (thunk-expr val) (thunk-env val)) :else (escape-html (str val)))))
(define
render-value-to-html
:effects (render)
(fn
(val (env :as dict))
(case
(type-of val)
"nil"
""
"string"
(escape-html val)
"number"
(str val)
"boolean"
(if val "true" "false")
"list"
(render-list-to-html val env)
"raw-html"
(raw-html-content val)
"spread"
(do (scope-emit! "element-attrs" (spread-attrs val)) "")
"thunk"
(render-to-html (thunk-expr val) (thunk-env val))
:else (escape-html (str val)))))
(define RENDER_HTML_FORMS (list "if" "when" "cond" "case" "let" "let*" "letrec" "begin" "do" "define" "defcomp" "defisland" "defmacro" "defstyle" "deftype" "defeffect" "map" "map-indexed" "filter" "for-each" "scope" "provide"))
(define
RENDER_HTML_FORMS
(list
"if"
"when"
"cond"
"case"
"let"
"let*"
"letrec"
"begin"
"do"
"define"
"defcomp"
"defisland"
"defmacro"
"defstyle"
"deftype"
"defeffect"
"map"
"map-indexed"
"filter"
"for-each"
"scope"
"provide"))
(define render-html-form? :effects () (fn ((name :as string)) (contains? RENDER_HTML_FORMS name)))
(define
render-html-form?
:effects ()
(fn ((name :as string)) (contains? RENDER_HTML_FORMS name)))
(define render-list-to-html :effects (render) (fn ((expr :as list) (env :as dict)) (if (empty? expr) "" (let ((head (first expr))) (if (not (= (type-of head) "symbol")) (join "" (map (fn (x) (render-value-to-html x env)) expr)) (let ((name (symbol-name head)) (args (rest expr))) (cond (= name "<>") (join "" (map (fn (x) (render-to-html x env)) args)) (= name "raw!") (join "" (map (fn (x) (str (trampoline (eval-expr x env)))) args)) (= name "lake") (render-html-lake args env) (= name "marsh") (render-html-marsh args env) (or (= name "portal") (= name "error-boundary") (= name "promise-delayed")) (join "" (map (fn (x) (render-to-html x env)) args)) (contains? HTML_TAGS name) (render-html-element name args env) (and (starts-with? name "~") (env-has? env name) (island? (env-get env name))) (render-html-island (env-get env name) args env) (starts-with? name "~") (let ((val (env-get env name))) (cond (component? val) (render-html-component val args env) (macro? val) (render-to-html (expand-macro val args env) env) :else (error (str "Unknown component: " name)))) (render-html-form? name) (dispatch-html-form name expr env) (and (env-has? env name) (macro? (env-get env name))) (render-to-html (expand-macro (env-get env name) args env) env) :else (render-value-to-html (trampoline (eval-expr expr env)) env))))))))
(define
render-list-to-html
:effects (render)
(fn
((expr :as list) (env :as dict))
(if
(empty? expr)
""
(let
((head (first expr)))
(if
(not (= (type-of head) "symbol"))
(join "" (map (fn (x) (render-value-to-html x env)) expr))
(let
((name (symbol-name head)) (args (rest expr)))
(cond
(= name "<>")
(join "" (map (fn (x) (render-to-html x env)) args))
(= name "raw!")
(join
""
(map (fn (x) (str (trampoline (eval-expr x env)))) args))
(= name "lake")
(render-html-lake args env)
(= name "marsh")
(render-html-marsh args env)
(or
(= name "portal")
(= name "error-boundary")
(= name "promise-delayed"))
(join "" (map (fn (x) (render-to-html x env)) args))
(contains? HTML_TAGS name)
(render-html-element name args env)
(and
(starts-with? name "~")
(env-has? env name)
(island? (env-get env name)))
(render-html-island (env-get env name) args env)
(starts-with? name "~")
(let
((val (env-get env name)))
(cond
(component? val)
(render-html-component val args env)
(macro? val)
(render-to-html (expand-macro val args env) env)
:else (str "<!-- unknown component: " name " -->")))
(render-html-form? name)
(dispatch-html-form name expr env)
(and (env-has? env name) (macro? (env-get env name)))
(render-to-html (expand-macro (env-get env name) args env) env)
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))))))
(define dispatch-html-form :effects (render) (fn ((name :as string) (expr :as list) (env :as dict)) (cond (= name "if") (let ((cond-val (trampoline (eval-expr (nth expr 1) env)))) (if cond-val (render-to-html (nth expr 2) env) (if (> (len expr) 3) (render-to-html (nth expr 3) env) ""))) (= name "when") (if (not (trampoline (eval-expr (nth expr 1) env))) "" (if (= (len expr) 3) (render-to-html (nth expr 2) env) (join "" (map (fn (i) (render-to-html (nth expr i) env)) (range 2 (len expr)))))) (= name "cond") (let ((branch (eval-cond (rest expr) env))) (if branch (render-to-html branch env) "")) (= name "case") (render-to-html (trampoline (eval-expr expr env)) env) (= name "letrec") (let ((bindings (nth expr 1)) (body (slice expr 2)) (local (env-extend env))) (for-each (fn (pair) (let ((pname (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair))))) (env-bind! local pname nil))) bindings) (for-each (fn (pair) (let ((pname (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair))))) (env-set! local pname (trampoline (eval-expr (nth pair 1) local))))) bindings) (when (> (len body) 1) (for-each (fn (e) (trampoline (eval-expr e local))) (init body))) (render-to-html (last body) local)) (or (= name "let") (= name "let*")) (let ((local (process-bindings (nth expr 1) env))) (if (= (len expr) 3) (render-to-html (nth expr 2) local) (join "" (map (fn (i) (render-to-html (nth expr i) local)) (range 2 (len expr)))))) (or (= name "begin") (= name "do")) (if (= (len expr) 2) (render-to-html (nth expr 1) env) (join "" (map (fn (i) (render-to-html (nth expr i) env)) (range 1 (len expr))))) (definition-form? name) (do (trampoline (eval-expr expr env)) "") (= name "map") (let ((f (trampoline (eval-expr (nth expr 1) env))) (coll (trampoline (eval-expr (nth expr 2) env)))) (join "" (map (fn (item) (if (lambda? f) (render-lambda-html f (list item) env) (render-to-html (apply f (list item)) env))) coll))) (= name "map-indexed") (let ((f (trampoline (eval-expr (nth expr 1) env))) (coll (trampoline (eval-expr (nth expr 2) env)))) (join "" (map-indexed (fn (i item) (if (lambda? f) (render-lambda-html f (list i item) env) (render-to-html (apply f (list i item)) env))) coll))) (= name "filter") (render-to-html (trampoline (eval-expr expr env)) env) (= name "for-each") (let ((f (trampoline (eval-expr (nth expr 1) env))) (coll (trampoline (eval-expr (nth expr 2) env)))) (join "" (map (fn (item) (if (lambda? f) (render-lambda-html f (list item) env) (render-to-html (apply f (list item)) env))) coll))) (= name "scope") (let ((scope-name (trampoline (eval-expr (nth expr 1) env))) (rest-args (slice expr 2)) (scope-val nil) (body-exprs nil)) (if (and (>= (len rest-args) 2) (= (type-of (first rest-args)) "keyword") (= (keyword-name (first rest-args)) "value")) (do (set! scope-val (trampoline (eval-expr (nth rest-args 1) env))) (set! body-exprs (slice rest-args 2))) (set! body-exprs rest-args)) (scope-push! scope-name scope-val) (let ((result (if (= (len body-exprs) 1) (render-to-html (first body-exprs) env) (join "" (map (fn (e) (render-to-html e env)) body-exprs))))) (scope-pop! scope-name) result)) (= name "provide") (let ((prov-name (trampoline (eval-expr (nth expr 1) env))) (prov-val (trampoline (eval-expr (nth expr 2) env))) (body-start 3) (body-count (- (len expr) 3))) (scope-push! prov-name prov-val) (let ((result (if (= body-count 1) (render-to-html (nth expr body-start) env) (join "" (map (fn (i) (render-to-html (nth expr i) env)) (range body-start (+ body-start body-count))))))) (scope-pop! prov-name) result)) :else (render-value-to-html (trampoline (eval-expr expr env)) env))))
(define
dispatch-html-form
:effects (render)
(fn
((name :as string) (expr :as list) (env :as dict))
(cond
(= name "if")
(let
((cond-val (trampoline (eval-expr (nth expr 1) env))))
(if
cond-val
(render-to-html (nth expr 2) env)
(if (> (len expr) 3) (render-to-html (nth expr 3) env) "")))
(= name "when")
(if
(not (trampoline (eval-expr (nth expr 1) env)))
""
(if
(= (len expr) 3)
(render-to-html (nth expr 2) env)
(join
""
(map
(fn (i) (render-to-html (nth expr i) env))
(range 2 (len expr))))))
(= name "cond")
(let
((branch (eval-cond (rest expr) env)))
(if branch (render-to-html branch env) ""))
(= name "case")
(render-to-html (trampoline (eval-expr expr env)) env)
(= name "letrec")
(let
((bindings (nth expr 1))
(body (slice expr 2))
(local (env-extend env)))
(for-each
(fn
(pair)
(let
((pname (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair)))))
(env-bind! local pname nil)))
bindings)
(for-each
(fn
(pair)
(let
((pname (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair)))))
(env-set!
local
pname
(trampoline (eval-expr (nth pair 1) local)))))
bindings)
(when
(> (len body) 1)
(for-each (fn (e) (trampoline (eval-expr e local))) (init body)))
(render-to-html (last body) local))
(or (= name "let") (= name "let*"))
(let
((local (process-bindings (nth expr 1) env)))
(if
(= (len expr) 3)
(render-to-html (nth expr 2) local)
(join
""
(map
(fn (i) (render-to-html (nth expr i) local))
(range 2 (len expr))))))
(or (= name "begin") (= name "do"))
(if
(= (len expr) 2)
(render-to-html (nth expr 1) env)
(join
""
(map
(fn (i) (render-to-html (nth expr i) env))
(range 1 (len expr)))))
(definition-form? name)
(do (trampoline (eval-expr expr env)) "")
(= name "map")
(let
((f (trampoline (eval-expr (nth expr 1) env)))
(coll (trampoline (eval-expr (nth expr 2) env))))
(join
""
(map
(fn
(item)
(if
(lambda? f)
(render-lambda-html f (list item) env)
(render-to-html (apply f (list item)) env)))
coll)))
(= name "map-indexed")
(let
((f (trampoline (eval-expr (nth expr 1) env)))
(coll (trampoline (eval-expr (nth expr 2) env))))
(join
""
(map-indexed
(fn
(i item)
(if
(lambda? f)
(render-lambda-html f (list i item) env)
(render-to-html (apply f (list i item)) env)))
coll)))
(= name "filter")
(render-to-html (trampoline (eval-expr expr env)) env)
(= name "for-each")
(let
((f (trampoline (eval-expr (nth expr 1) env)))
(coll (trampoline (eval-expr (nth expr 2) env))))
(join
""
(map
(fn
(item)
(if
(lambda? f)
(render-lambda-html f (list item) env)
(render-to-html (apply f (list item)) env)))
coll)))
(= name "scope")
(let
((scope-name (trampoline (eval-expr (nth expr 1) env)))
(rest-args (slice expr 2))
(scope-val nil)
(body-exprs nil))
(if
(and
(>= (len rest-args) 2)
(= (type-of (first rest-args)) "keyword")
(= (keyword-name (first rest-args)) "value"))
(do
(set! scope-val (trampoline (eval-expr (nth rest-args 1) env)))
(set! body-exprs (slice rest-args 2)))
(set! body-exprs rest-args))
(scope-push! scope-name scope-val)
(let
((result (if (= (len body-exprs) 1) (render-to-html (first body-exprs) env) (join "" (map (fn (e) (render-to-html e env)) body-exprs)))))
(scope-pop! scope-name)
result))
(= name "provide")
(let
((prov-name (trampoline (eval-expr (nth expr 1) env)))
(prov-val (trampoline (eval-expr (nth expr 2) env)))
(body-start 3)
(body-count (- (len expr) 3)))
(scope-push! prov-name prov-val)
(let
((result (if (= body-count 1) (render-to-html (nth expr body-start) env) (join "" (map (fn (i) (render-to-html (nth expr i) env)) (range body-start (+ body-start body-count)))))))
(scope-pop! prov-name)
result))
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))
(define render-lambda-html :effects (render) (fn ((f :as lambda) (args :as list) (env :as dict)) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed (fn (i p) (env-bind! local p (nth args i))) (lambda-params f)) (render-to-html (lambda-body f) local))))
(define
render-lambda-html
:effects (render)
(fn
((f :as lambda) (args :as list) (env :as dict))
(let
((local (env-merge (lambda-closure f) env)))
(for-each-indexed
(fn (i p) (env-bind! local p (nth args i)))
(lambda-params f))
(render-to-html (lambda-body f) local))))
(define render-html-component :effects (render) (fn ((comp :as component) (args :as list) (env :as dict)) (let ((kwargs (dict)) (children (list))) (reduce (fn (state arg) (let ((skip (get state "skip"))) (if skip (assoc state "skip" false "i" (inc (get state "i"))) (if (and (= (type-of arg) "keyword") (< (inc (get state "i")) (len args))) (let ((val (trampoline (eval-expr (nth args (inc (get state "i"))) env)))) (dict-set! kwargs (keyword-name arg) val) (assoc state "skip" true "i" (inc (get state "i")))) (do (append! children arg) (assoc state "i" (inc (get state "i")))))))) (dict "i" 0 "skip" false) args) (let ((local (env-merge (component-closure comp) env))) (for-each (fn (p) (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) (when (component-has-children? comp) (env-bind! local "children" (make-raw-html (join "" (map (fn (c) (render-to-html c env)) children))))) (render-to-html (component-body comp) local)))))
(define
render-html-component
:effects (render)
(fn
((comp :as component) (args :as list) (env :as dict))
(let
((kwargs (dict)) (children (list)))
(reduce
(fn
(state arg)
(let
((skip (get state "skip")))
(if
skip
(assoc state "skip" false "i" (inc (get state "i")))
(if
(and
(= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let
((val (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
(dict-set! kwargs (keyword-name arg) val)
(assoc state "skip" true "i" (inc (get state "i"))))
(do
(append! children arg)
(assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false)
args)
(let
((local (env-merge (component-closure comp) env)))
(for-each
(fn
(p)
(env-bind!
local
p
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params comp))
(when
(component-has-children? comp)
(env-bind!
local
"children"
(make-raw-html
(join "" (map (fn (c) (render-to-html c env)) children)))))
(render-to-html (component-body comp) local)))))
(define render-html-element :effects (render) (fn ((tag :as string) (args :as list) (env :as dict)) (let ((parsed (parse-element-args args env)) (attrs (first parsed)) (children (nth parsed 1)) (is-void (contains? VOID_ELEMENTS tag))) (if is-void (str "<" tag (render-attrs attrs) " />") (do (scope-push! "element-attrs" nil) (let ((content (join "" (map (fn (c) (render-to-html c env)) children)))) (for-each (fn (spread-dict) (merge-spread-attrs attrs spread-dict)) (scope-emitted "element-attrs")) (scope-pop! "element-attrs") (str "<" tag (render-attrs attrs) ">" content "</" tag ">")))))))
(define
render-html-element
:effects (render)
(fn
((tag :as string) (args :as list) (env :as dict))
(let
((parsed (parse-element-args args env))
(attrs (first parsed))
(children (nth parsed 1))
(is-void (contains? VOID_ELEMENTS tag)))
(if
is-void
(str "<" tag (render-attrs attrs) " />")
(do
(scope-push! "element-attrs" nil)
(let
((content (join "" (map (fn (c) (render-to-html c env)) children))))
(for-each
(fn (spread-dict) (merge-spread-attrs attrs spread-dict))
(scope-emitted "element-attrs"))
(scope-pop! "element-attrs")
(str "<" tag (render-attrs attrs) ">" content "</" tag ">")))))))
(define render-html-lake :effects (render) (fn ((args :as list) (env :as dict)) (let ((lake-id nil) (lake-tag "div") (children (list))) (reduce (fn (state arg) (let ((skip (get state "skip"))) (if skip (assoc state "skip" false "i" (inc (get state "i"))) (if (and (= (type-of arg) "keyword") (< (inc (get state "i")) (len args))) (let ((kname (keyword-name arg)) (kval (trampoline (eval-expr (nth args (inc (get state "i"))) env)))) (cond (= kname "id") (set! lake-id kval) (= kname "tag") (set! lake-tag kval)) (assoc state "skip" true "i" (inc (get state "i")))) (do (append! children arg) (assoc state "i" (inc (get state "i")))))))) (dict "i" 0 "skip" false) args) (let ((lake-attrs (dict "data-sx-lake" (or lake-id "")))) (scope-push! "element-attrs" nil) (let ((content (join "" (map (fn (c) (render-to-html c env)) children)))) (for-each (fn (spread-dict) (merge-spread-attrs lake-attrs spread-dict)) (scope-emitted "element-attrs")) (scope-pop! "element-attrs") (str "<" lake-tag (render-attrs lake-attrs) ">" content "</" lake-tag ">"))))))
(define
render-html-lake
:effects (render)
(fn
((args :as list) (env :as dict))
(let
((lake-id nil) (lake-tag "div") (children (list)))
(reduce
(fn
(state arg)
(let
((skip (get state "skip")))
(if
skip
(assoc state "skip" false "i" (inc (get state "i")))
(if
(and
(= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let
((kname (keyword-name arg))
(kval
(trampoline
(eval-expr (nth args (inc (get state "i"))) env))))
(cond
(= kname "id")
(set! lake-id kval)
(= kname "tag")
(set! lake-tag kval))
(assoc state "skip" true "i" (inc (get state "i"))))
(do
(append! children arg)
(assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false)
args)
(let
((lake-attrs (dict "data-sx-lake" (or lake-id ""))))
(scope-push! "element-attrs" nil)
(let
((content (join "" (map (fn (c) (render-to-html c env)) children))))
(for-each
(fn (spread-dict) (merge-spread-attrs lake-attrs spread-dict))
(scope-emitted "element-attrs"))
(scope-pop! "element-attrs")
(str
"<"
lake-tag
(render-attrs lake-attrs)
">"
content
"</"
lake-tag
">"))))))
(define render-html-marsh :effects (render) (fn ((args :as list) (env :as dict)) (let ((marsh-id nil) (marsh-tag "div") (children (list))) (reduce (fn (state arg) (let ((skip (get state "skip"))) (if skip (assoc state "skip" false "i" (inc (get state "i"))) (if (and (= (type-of arg) "keyword") (< (inc (get state "i")) (len args))) (let ((kname (keyword-name arg)) (kval (trampoline (eval-expr (nth args (inc (get state "i"))) env)))) (cond (= kname "id") (set! marsh-id kval) (= kname "tag") (set! marsh-tag kval) (= kname "transform") nil) (assoc state "skip" true "i" (inc (get state "i")))) (do (append! children arg) (assoc state "i" (inc (get state "i")))))))) (dict "i" 0 "skip" false) args) (let ((marsh-attrs (dict "data-sx-marsh" (or marsh-id "")))) (scope-push! "element-attrs" nil) (let ((content (join "" (map (fn (c) (render-to-html c env)) children)))) (for-each (fn (spread-dict) (merge-spread-attrs marsh-attrs spread-dict)) (scope-emitted "element-attrs")) (scope-pop! "element-attrs") (str "<" marsh-tag (render-attrs marsh-attrs) ">" content "</" marsh-tag ">"))))))
(define
render-html-marsh
:effects (render)
(fn
((args :as list) (env :as dict))
(let
((marsh-id nil) (marsh-tag "div") (children (list)))
(reduce
(fn
(state arg)
(let
((skip (get state "skip")))
(if
skip
(assoc state "skip" false "i" (inc (get state "i")))
(if
(and
(= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let
((kname (keyword-name arg))
(kval
(trampoline
(eval-expr (nth args (inc (get state "i"))) env))))
(cond
(= kname "id")
(set! marsh-id kval)
(= kname "tag")
(set! marsh-tag kval)
(= kname "transform")
nil)
(assoc state "skip" true "i" (inc (get state "i"))))
(do
(append! children arg)
(assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false)
args)
(let
((marsh-attrs (dict "data-sx-marsh" (or marsh-id ""))))
(scope-push! "element-attrs" nil)
(let
((content (join "" (map (fn (c) (render-to-html c env)) children))))
(for-each
(fn (spread-dict) (merge-spread-attrs marsh-attrs spread-dict))
(scope-emitted "element-attrs"))
(scope-pop! "element-attrs")
(str
"<"
marsh-tag
(render-attrs marsh-attrs)
">"
content
"</"
marsh-tag
">"))))))
(define render-html-island :effects (render) (fn ((island :as island) (args :as list) (env :as dict)) (let ((kwargs (dict)) (children (list))) (reduce (fn (state arg) (let ((skip (get state "skip"))) (if skip (assoc state "skip" false "i" (inc (get state "i"))) (if (and (= (type-of arg) "keyword") (< (inc (get state "i")) (len args))) (let ((val (trampoline (eval-expr (nth args (inc (get state "i"))) env)))) (dict-set! kwargs (keyword-name arg) val) (assoc state "skip" true "i" (inc (get state "i")))) (do (append! children arg) (assoc state "i" (inc (get state "i")))))))) (dict "i" 0 "skip" false) args) (let ((local (env-merge (component-closure island) env)) (island-name (component-name island))) (for-each (fn (p) (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params island)) (when (component-has-children? island) (env-bind! local "children" (make-raw-html (join "" (map (fn (c) (render-to-html c env)) children))))) (let ((body-html (cek-try (fn () (render-to-html (component-body island) local)) (fn (err) ""))) (state-sx (serialize-island-state kwargs))) (str "<span data-sx-island=\"" (escape-attr island-name) "\"" (if state-sx (str " data-sx-state=\"" (escape-attr state-sx) "\"") "") ">" body-html "</span>"))))))
(define
render-html-island
:effects (render)
(fn
((island :as island) (args :as list) (env :as dict))
(let
((kwargs (dict)) (children (list)))
(reduce
(fn
(state arg)
(let
((skip (get state "skip")))
(if
skip
(assoc state "skip" false "i" (inc (get state "i")))
(if
(and
(= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let
((val (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
(dict-set! kwargs (keyword-name arg) val)
(assoc state "skip" true "i" (inc (get state "i"))))
(do
(append! children arg)
(assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false)
args)
(let
((local (env-merge (component-closure island) env))
(island-name (component-name island)))
(for-each
(fn
(p)
(env-bind!
local
p
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params island))
(when
(component-has-children? island)
(env-bind!
local
"children"
(make-raw-html
(join "" (map (fn (c) (render-to-html c env)) children)))))
(let
((body-html (render-to-html (component-body island) local))
(state-sx (serialize-island-state kwargs)))
(str
"<span data-sx-island=\""
(escape-attr island-name)
"\""
(if
state-sx
(str " data-sx-state=\"" (escape-attr state-sx) "\"")
"")
">"
body-html
"</span>"))))))
(define serialize-island-state :effects () (fn ((kwargs :as dict)) (if (empty-dict? kwargs) nil (sx-serialize kwargs))))
(define
serialize-island-state
:effects ()
(fn
((kwargs :as dict))
(if (empty-dict? kwargs) nil (sx-serialize kwargs))))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -270,6 +270,7 @@
(= name "defisland") (compile-defcomp em args scope)
(= name "quasiquote") (compile-quasiquote em (first args) scope)
(= name "letrec") (compile-letrec em args scope tail?)
(= name "match") (compile-match em args scope tail?)
;; Default — function call
:else
(compile-call em head args scope tail?)))))))
@@ -634,6 +635,75 @@
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))))))
;; compile-match — compile (match expr (pattern body) ...) to bytecode.
;; Self-contained via letrec so JIT can find the recursive helper.
(define compile-match
(fn (em args scope tail?)
(compile-expr em (first args) scope false)
(letrec
((do-clauses (fn (clauses)
(if (empty? clauses)
(do (emit-op em 5)
(let ((idx (pool-add (get em "pool") "match: no clause matched")))
(emit-op em 1) (emit-u16 em idx)
(emit-op em 52) (emit-u16 em (pool-add (get em "pool") "error"))
(emit-byte em 1)))
(let ((clause (first clauses))
(pattern (first clause))
(body (nth clause 1))
(rest-clauses (rest clauses)))
(cond
;; Wildcard _
(and (= (type-of pattern) "symbol") (= (symbol-name pattern) "_"))
(do (emit-op em 5) (compile-expr em body scope tail?))
;; Symbol binding
(and (= (type-of pattern) "symbol")
(not (= (symbol-name pattern) "true"))
(not (= (symbol-name pattern) "false"))
(not (= (symbol-name pattern) "nil")))
(let ((var-name (symbol-name pattern))
(inner-scope (scope-add scope var-name)))
(emit-op em 13) (emit-byte em (scope-index inner-scope var-name))
(compile-expr em body inner-scope tail?))
;; Quoted symbol 'foo
(and (list? pattern) (= (len pattern) 2)
(= (type-of (first pattern)) "symbol")
(= (symbol-name (first pattern)) "quote")
(= (type-of (nth pattern 1)) "symbol"))
(do (emit-op em 6)
(let ((idx (pool-add (get em "pool") (make-symbol (symbol-name (nth pattern 1))))))
(emit-op em 1) (emit-u16 em idx))
(let ((eq-idx (pool-add (get em "pool") "=")))
(emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2))
(emit-op em 33)
(let ((skip (current-offset em)))
(emit-i16 em 0)
(emit-op em 5) (compile-expr em body scope tail?)
(emit-op em 32)
(let ((end-jump (current-offset em)))
(emit-i16 em 0)
(patch-i16 em skip (- (current-offset em) (+ skip 2)))
(do-clauses rest-clauses)
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))
;; Literal (string, number, boolean, nil)
:else
(do (emit-op em 6)
(compile-expr em pattern scope false)
(let ((eq-idx (pool-add (get em "pool") "=")))
(emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2))
(emit-op em 33)
(let ((skip (current-offset em)))
(emit-i16 em 0)
(emit-op em 5) (compile-expr em body scope tail?)
(emit-op em 32)
(let ((end-jump (current-offset em)))
(emit-i16 em 0)
(patch-i16 em skip (- (current-offset em) (+ skip 2)))
(do-clauses rest-clauses)
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))))))
(do-clauses (rest args)))))
(define compile-thread
(fn (em args scope tail?)
"Compile (-> val (f1 a) (f2 b)) by desugaring to nested calls."

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,4 @@
(define
make-signal
(fn
(value)
(dict "__signal" true "value" value "subscribers" (list) "deps" (list))))
(define make-signal (fn (value) (dict "__signal" true "value" value "subscribers" (list) "deps" (list))))
(define signal? (fn (x) (and (dict? x) (has-key? x "__signal"))))
@@ -12,179 +8,38 @@
(define signal-subscribers (fn (s) (get s "subscribers")))
(define
signal-add-sub!
(fn
(s f)
(when
(not (contains? (get s "subscribers") f))
(append! (get s "subscribers") f))))
(define signal-add-sub! (fn (s f) (when (not (contains? (get s "subscribers") f)) (append! (get s "subscribers") f))))
(define
signal-remove-sub!
(fn (s f) (let ((subs (get s "subscribers"))) (remove! subs f))))
(define signal-remove-sub! (fn (s f) (dict-set! s "subscribers" (filter (fn (sub) (not (identical? sub f))) (get s "subscribers")))))
(define signal-deps (fn (s) (get s "deps")))
(define signal-set-deps! (fn (s deps) (dict-set! s "deps" deps)))
(define
signal
:effects ()
(fn ((initial-value :as any)) (make-signal initial-value)))
(define signal :effects () (fn ((initial-value :as any)) (make-signal initial-value)))
(define
deref
:effects ()
(fn
((s :as any))
(if
(not (signal? s))
s
(let
((ctx (context "sx-reactive" nil)))
(when
ctx
(let
((dep-list (get ctx "deps")) (notify-fn (get ctx "notify")))
(when
(not (contains? dep-list s))
(append! dep-list s)
(signal-add-sub! s notify-fn))))
(signal-value s)))))
(define deref :effects () (fn ((s :as any)) (if (not (signal? s)) s (let ((ctx (context "sx-reactive" nil))) (when ctx (let ((dep-list (get ctx "deps")) (notify-fn (get ctx "notify"))) (when (not (contains? dep-list s)) (append! dep-list s) (signal-add-sub! s notify-fn)))) (signal-value s)))))
(define
reset!
:effects (mutation)
(fn
((s :as signal) value)
(when
(signal? s)
(let
((old (signal-value s)))
(when
(not (identical? old value))
(signal-set-value! s value)
(notify-subscribers s))))))
(define reset! :effects (mutation) (fn ((s :as signal) value) (when (signal? s) (let ((old (signal-value s))) (when (not (identical? old value)) (signal-set-value! s value) (notify-subscribers s))))))
(define
swap!
:effects (mutation)
(fn
((s :as signal) (f :as lambda) &rest args)
(when
(signal? s)
(let
((old (signal-value s))
(new-val (trampoline (apply f (cons old args)))))
(when
(not (identical? old new-val))
(signal-set-value! s new-val)
(notify-subscribers s))))))
(define swap! :effects (mutation) (fn ((s :as signal) (f :as lambda) &rest args) (when (signal? s) (let ((old (signal-value s)) (new-val (trampoline (apply f (cons old args))))) (when (not (identical? old new-val)) (signal-set-value! s new-val) (notify-subscribers s))))))
(define
computed
:effects (mutation)
(fn
((compute-fn :as lambda))
(let
((s (make-signal nil)) (deps (list)) (compute-ctx nil))
(let
((recompute (fn () (for-each (fn ((dep :as signal)) (signal-remove-sub! dep recompute)) (signal-deps s)) (signal-set-deps! s (list)) (let ((ctx (dict "deps" (list) "notify" recompute))) (scope-push! "sx-reactive" ctx) (let ((new-val (compute-fn))) (scope-pop! "sx-reactive") (signal-set-deps! s (get ctx "deps")) (let ((old (signal-value s))) (signal-set-value! s new-val) (when (not (identical? old new-val)) (notify-subscribers s))))))))
(recompute)
(register-in-scope (fn () (dispose-computed s)))
s))))
(define computed :effects (mutation) (fn ((compute-fn :as lambda)) (let ((s (make-signal nil)) (deps (list)) (compute-ctx nil)) (let ((recompute (fn () (for-each (fn ((dep :as signal)) (signal-remove-sub! dep recompute)) (signal-deps s)) (signal-set-deps! s (list)) (let ((ctx (dict "deps" (list) "notify" recompute))) (scope-push! "sx-reactive" ctx) (let ((new-val (cek-call compute-fn nil))) (scope-pop! "sx-reactive") (signal-set-deps! s (get ctx "deps")) (let ((old (signal-value s))) (signal-set-value! s new-val) (when (not (identical? old new-val)) (notify-subscribers s)))))))) (recompute) (register-in-scope (fn () (dispose-computed s))) s))))
(define
effect
:effects (mutation)
(fn
((effect-fn :as lambda))
(let
((deps (list)) (disposed false) (cleanup-fn nil))
(let
((run-effect (fn () (when (not disposed) (when cleanup-fn (cleanup-fn)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list)) (let ((ctx (dict "deps" (list) "notify" run-effect))) (scope-push! "sx-reactive" ctx) (let ((result (effect-fn))) (scope-pop! "sx-reactive") (set! deps (get ctx "deps")) (when (callable? result) (set! cleanup-fn result))))))))
(run-effect)
(let
((dispose-fn (fn () (set! disposed true) (when cleanup-fn (cleanup-fn)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list)))))
(register-in-scope dispose-fn)
dispose-fn)))))
(define effect :effects (mutation) (fn ((effect-fn :as lambda)) (let ((deps (list)) (disposed false) (cleanup-fn nil)) (let ((run-effect (fn () (when (not disposed) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list)) (let ((ctx (dict "deps" (list) "notify" run-effect))) (scope-push! "sx-reactive" ctx) (let ((result (cek-call effect-fn nil))) (scope-pop! "sx-reactive") (set! deps (get ctx "deps")) (when (callable? result) (set! cleanup-fn result)))))))) (run-effect) (let ((dispose-fn (fn () (set! disposed true) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list))))) (register-in-scope dispose-fn) dispose-fn)))))
(define *batch-depth* 0)
(define *batch-queue* (list))
(define
batch
:effects (mutation)
(fn
((thunk :as lambda))
(set! *batch-depth* (+ *batch-depth* 1))
(thunk)
(set! *batch-depth* (- *batch-depth* 1))
(when
(= *batch-depth* 0)
(let
((queue *batch-queue*))
(set! *batch-queue* (list))
(let
((seen (list)) (pending (list)))
(for-each
(fn
((s :as signal))
(for-each
(fn
((sub :as lambda))
(when
(not (contains? seen sub))
(append! seen sub)
(append! pending sub)))
(signal-subscribers s)))
queue)
(for-each (fn ((sub :as lambda)) (sub)) pending))))))
(define batch :effects (mutation) (fn ((thunk :as lambda)) (set! *batch-depth* (+ *batch-depth* 1)) (cek-call thunk nil) (set! *batch-depth* (- *batch-depth* 1)) (when (= *batch-depth* 0) (let ((queue *batch-queue*)) (set! *batch-queue* (list)) (let ((seen (list)) (pending (list))) (for-each (fn ((s :as signal)) (for-each (fn ((sub :as lambda)) (when (not (contains? seen sub)) (append! seen sub) (append! pending sub))) (signal-subscribers s))) queue) (for-each (fn ((sub :as lambda)) (sub)) pending))))))
(define
notify-subscribers
:effects (mutation)
(fn
((s :as signal))
(if
(> *batch-depth* 0)
(when (not (contains? *batch-queue* s)) (append! *batch-queue* s))
(flush-subscribers s))))
(define notify-subscribers :effects (mutation) (fn ((s :as signal)) (if (> *batch-depth* 0) (when (not (contains? *batch-queue* s)) (append! *batch-queue* s)) (flush-subscribers s))))
(define
flush-subscribers
:effects (mutation)
(fn
((s :as signal))
(for-each (fn ((sub :as lambda)) (sub)) (signal-subscribers s))))
(define flush-subscribers :effects (mutation) (fn ((s :as signal)) (for-each (fn ((sub :as lambda)) (sub)) (signal-subscribers s))))
(define
dispose-computed
:effects (mutation)
(fn
((s :as signal))
(when
(signal? s)
(for-each
(fn ((dep :as signal)) (signal-remove-sub! dep nil))
(signal-deps s))
(signal-set-deps! s (list)))))
(define dispose-computed :effects (mutation) (fn ((s :as signal)) (when (signal? s) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep nil)) (signal-deps s)) (signal-set-deps! s (list)))))
(define
with-island-scope
:effects (mutation)
(fn
((scope-fn :as lambda) (body-fn :as lambda))
(scope-push! "sx-island-scope" scope-fn)
(let ((result (body-fn))) (scope-pop! "sx-island-scope") result)))
(define with-island-scope :effects (mutation) (fn ((scope-fn :as lambda) (body-fn :as lambda)) (scope-push! "sx-island-scope" scope-fn) (let ((result (body-fn))) (scope-pop! "sx-island-scope") result)))
(define
register-in-scope
:effects (mutation)
(fn
((disposable :as lambda))
(let
((collector (scope-peek "sx-island-scope")))
(when collector (cek-call collector (list disposable))))))
(define register-in-scope :effects (mutation) (fn ((disposable :as lambda)) (let ((collector (scope-peek "sx-island-scope"))) (when collector (cek-call collector (list disposable))))))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"magic":"SXBC","version":1,"hash":"bfa1b3e64a390451","module":{"arity":0,"bytecode":[52,1,0,0,128,0,0,5,51,3,0,128,2,0,5,51,5,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,50],"constants":[{"t":"s","v":"freeze-registry"},{"t":"s","v":"dict"},{"t":"s","v":"freeze-signal"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,1,1,0,2,48,2,17,2,20,2,0,33,62,0,20,4,0,20,2,0,52,3,0,2,6,34,5,0,5,52,5,0,0,17,3,20,6,0,20,7,0,1,9,0,20,9,0,1,10,0,20,11,0,52,8,0,4,48,2,5,20,4,0,20,2,0,20,7,0,52,12,0,3,32,1,0,2,50],"constants":[{"t":"s","v":"context"},{"t":"s","v":"sx-freeze-scope"},{"t":"s","v":"scope-name"},{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"append!"},{"t":"s","v":"entries"},{"t":"s","v":"dict"},{"t":"s","v":"name"},{"t":"s","v":"signal"},{"t":"s","v":"sig"},{"t":"s","v":"dict-set!"}]}},{"t":"s","v":"freeze-scope"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,1,1,0,20,2,0,48,2,5,20,4,0,20,2,0,52,5,0,0,52,3,0,3,5,20,6,0,20,7,0,2,48,2,5,20,8,0,1,1,0,48,1,5,2,50],"constants":[{"t":"s","v":"scope-push!"},{"t":"s","v":"sx-freeze-scope"},{"t":"s","v":"name"},{"t":"s","v":"dict-set!"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"cek-call"},{"t":"s","v":"body-fn"},{"t":"s","v":"scope-pop!"}]}},{"t":"s","v":"cek-freeze-scope"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,1,0,20,2,0,52,0,0,2,6,34,5,0,5,52,3,0,0,17,1,52,4,0,0,17,2,51,6,0,20,7,0,52,5,0,2,5,1,2,0,20,2,0,1,8,0,20,9,0,52,4,0,4,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"name"},{"t":"s","v":"list"},{"t":"s","v":"dict"},{"t":"s","v":"for-each"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,1,0,20,3,0,1,4,0,52,2,0,2,20,5,0,20,3,0,1,6,0,52,2,0,2,48,1,52,0,0,3,50],"constants":[{"t":"s","v":"dict-set!"},{"t":"s","v":"signals-dict"},{"t":"s","v":"get"},{"t":"s","v":"entry"},{"t":"s","v":"name"},{"t":"s","v":"signal-value"},{"t":"s","v":"signal"}]}},{"t":"s","v":"entries"},{"t":"s","v":"signals"},{"t":"s","v":"signals-dict"}]}},{"t":"s","v":"cek-freeze-all"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[51,1,0,20,3,0,52,2,0,1,52,0,0,2,50],"constants":[{"t":"s","v":"map"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,49,1,50],"constants":[{"t":"s","v":"cek-freeze-scope"},{"t":"s","v":"name"}]}},{"t":"s","v":"keys"},{"t":"s","v":"freeze-registry"}]}},{"t":"s","v":"cek-thaw-scope"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,1,0,20,2,0,52,0,0,2,6,34,5,0,5,52,3,0,0,17,2,20,4,0,1,5,0,52,0,0,2,17,3,20,6,0,33,13,0,51,8,0,20,9,0,52,7,0,2,32,1,0,2,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"name"},{"t":"s","v":"list"},{"t":"s","v":"frozen"},{"t":"s","v":"signals"},{"t":"s","v":"values"},{"t":"s","v":"for-each"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,1,0,1,2,0,52,0,0,2,17,1,20,1,0,1,3,0,52,0,0,2,17,2,20,4,0,20,5,0,52,0,0,2,17,3,20,8,0,52,7,0,1,52,6,0,1,33,14,0,20,9,0,20,10,0,20,8,0,49,2,32,1,0,2,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"entry"},{"t":"s","v":"name"},{"t":"s","v":"signal"},{"t":"s","v":"values"},{"t":"s","v":"sig-name"},{"t":"s","v":"not"},{"t":"s","v":"nil?"},{"t":"s","v":"val"},{"t":"s","v":"reset!"},{"t":"s","v":"sig"}]}},{"t":"s","v":"entries"}]}},{"t":"s","v":"cek-thaw-all"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[51,1,0,20,2,0,52,0,0,2,50],"constants":[{"t":"s","v":"for-each"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,2,0,1,3,0,52,1,0,2,20,2,0,49,2,50],"constants":[{"t":"s","v":"cek-thaw-scope"},{"t":"s","v":"get"},{"t":"s","v":"frozen"},{"t":"s","v":"name"}]}},{"t":"s","v":"frozen-list"}]}},{"t":"s","v":"freeze-to-sx"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,20,2,0,48,1,49,1,50],"constants":[{"t":"s","v":"sx-serialize"},{"t":"s","v":"cek-freeze-scope"},{"t":"s","v":"name"}]}},{"t":"s","v":"thaw-from-sx"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,48,1,17,1,20,4,0,52,3,0,1,52,2,0,1,33,30,0,20,4,0,52,5,0,1,17,2,20,6,0,20,8,0,1,9,0,52,7,0,2,20,8,0,49,2,32,1,0,2,50],"constants":[{"t":"s","v":"sx-parse"},{"t":"s","v":"sx-text"},{"t":"s","v":"not"},{"t":"s","v":"empty?"},{"t":"s","v":"parsed"},{"t":"s","v":"first"},{"t":"s","v":"cek-thaw-scope"},{"t":"s","v":"get"},{"t":"s","v":"frozen"},{"t":"s","v":"name"}]}}]}}

View File

@@ -1,3 +1,3 @@
(sxbc 1 "93780bb9539e858f"
(sxbc 1 "57726b5b82c1a3cb"
(code
:constants ("assert-signal-value" {:upvalue-count 0 :arity 2 :constants ("deref" "assert=" "str" "Expected signal value " ", got ") :bytecode (20 0 0 16 0 48 1 17 2 20 1 0 16 2 16 1 1 3 0 16 1 1 4 0 16 2 52 2 0 4 49 3 50)} "assert-signal-has-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" ">" "len" "signal-subscribers" 0 "Expected signal to have subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-no-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" "=" "len" "signal-subscribers" 0 "Expected signal to have no subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-subscriber-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-subscribers" "assert=" "str" "Expected " " subscribers, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "simulate-signal-set!" {:upvalue-count 0 :arity 2 :constants ("reset!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "simulate-signal-swap!" {:upvalue-count 0 :arity 3 :constants ("apply" "swap!" "cons") :bytecode (20 1 0 16 0 16 1 16 2 52 2 0 2 52 2 0 2 52 0 0 2 50)} "assert-computed-dep-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-deps" "assert=" "str" "Expected " " deps, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "assert-computed-depends-on" {:upvalue-count 0 :arity 2 :constants ("assert" "contains?" "signal-deps" "Expected computed to depend on the given signal") :bytecode (20 0 0 20 2 0 16 0 48 1 16 1 52 1 0 2 1 3 0 49 2 50)} "count-effect-runs" {:upvalue-count 0 :arity 1 :constants ("signal" 0 "effect" {:upvalue-count 1 :arity 0 :constants ("deref") :bytecode (20 0 0 18 0 49 1 50)} {:upvalue-count 2 :arity 0 :constants ("+" 1 "cek-call") :bytecode (18 0 1 1 0 52 0 0 2 19 0 5 20 2 0 18 1 2 49 2 50)}) :bytecode (20 0 0 1 1 0 48 1 17 1 20 2 0 51 3 0 1 1 48 1 5 1 1 0 17 2 20 2 0 51 4 0 1 2 1 0 48 1 17 3 16 2 50)} "make-test-signal" {:upvalue-count 0 :arity 1 :constants ("signal" "list" "effect" {:upvalue-count 2 :arity 0 :constants ("append!" "deref") :bytecode (20 0 0 18 0 20 1 0 18 1 48 1 49 2 50)} "history") :bytecode (20 0 0 16 0 48 1 17 1 52 1 0 0 17 2 20 2 0 51 3 0 1 2 1 1 48 1 5 1 0 0 16 1 1 4 0 16 2 65 2 0 50)} "assert-batch-coalesces" {:upvalue-count 0 :arity 2 :constants (0 "signal" "effect" {:upvalue-count 2 :arity 0 :constants ("deref" "+" 1) :bytecode (20 0 0 18 0 48 1 5 18 1 1 2 0 52 1 0 2 19 1 50)} "batch" "assert=" "str" "Expected " " notifications, got ") :bytecode (1 0 0 17 2 20 1 0 1 0 0 48 1 17 3 20 2 0 51 3 0 1 3 1 2 48 1 5 1 0 0 17 2 5 20 4 0 16 0 48 1 5 20 5 0 16 2 16 1 1 7 0 16 1 1 8 0 16 2 52 6 0 4 49 3 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 51 15 0 128 14 0 5 51 17 0 128 16 0 5 51 19 0 128 18 0 5 51 21 0 128 20 0 50)))
:constants ("assert-signal-value" {:upvalue-count 0 :arity 2 :constants ("deref" "assert=" "str" "Expected signal value " ", got ") :bytecode (20 0 0 16 0 48 1 17 2 20 1 0 16 2 16 1 1 3 0 16 1 1 4 0 16 2 52 2 0 4 49 3 50)} "assert-signal-has-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" ">" "len" "signal-subscribers" 0 "Expected signal to have subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-no-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" "=" "len" "signal-subscribers" 0 "Expected signal to have no subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-subscriber-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-subscribers" "assert=" "str" "Expected " " subscribers, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "simulate-signal-set!" {:upvalue-count 0 :arity 2 :constants ("reset!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "simulate-signal-swap!" {:upvalue-count 0 :arity 2 :constants ("swap!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "assert-computed-dep-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-deps" "assert=" "str" "Expected " " deps, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "assert-computed-depends-on" {:upvalue-count 0 :arity 2 :constants ("assert" "contains?" "signal-deps" "Expected computed to depend on the given signal") :bytecode (20 0 0 20 2 0 16 0 48 1 16 1 52 1 0 2 1 3 0 49 2 50)} "count-effect-runs" {:upvalue-count 0 :arity 1 :constants ("signal" 0 "effect" {:upvalue-count 1 :arity 0 :constants ("deref") :bytecode (20 0 0 18 0 49 1 50)} {:upvalue-count 2 :arity 0 :constants ("+" 1 "cek-call") :bytecode (18 0 1 1 0 52 0 0 2 19 0 5 20 2 0 18 1 2 49 2 50)}) :bytecode (20 0 0 1 1 0 48 1 17 1 20 2 0 51 3 0 1 1 48 1 5 1 1 0 17 2 20 2 0 51 4 0 1 2 1 0 48 1 17 3 16 2 50)} "make-test-signal" {:upvalue-count 0 :arity 1 :constants ("signal" "list" "effect" {:upvalue-count 2 :arity 0 :constants ("append!" "deref") :bytecode (20 0 0 18 0 20 1 0 18 1 48 1 49 2 50)} "history") :bytecode (20 0 0 16 0 48 1 17 1 52 1 0 0 17 2 20 2 0 51 3 0 1 2 1 1 48 1 5 1 0 0 16 1 1 4 0 16 2 65 2 0 50)} "assert-batch-coalesces" {:upvalue-count 0 :arity 2 :constants (0 "signal" "effect" {:upvalue-count 2 :arity 0 :constants ("deref" "+" 1) :bytecode (20 0 0 18 0 48 1 5 18 1 1 2 0 52 1 0 2 19 1 50)} "batch" "assert=" "str" "Expected " " notifications, got ") :bytecode (1 0 0 17 2 20 1 0 1 0 0 48 1 17 3 20 2 0 51 3 0 1 3 1 2 48 1 5 1 0 0 17 2 5 20 4 0 16 0 48 1 5 20 5 0 16 2 16 1 1 7 0 16 1 1 8 0 16 2 52 6 0 4 49 3 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 51 15 0 128 14 0 5 51 17 0 128 16 0 5 51 19 0 128 18 0 5 51 21 0 128 20 0 50)))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,101 +1,229 @@
;; ==========================================================================
;; render.sx — Core rendering specification
;;
;; Shared registries and utilities used by all rendering adapters.
;; This file defines WHAT is renderable (tag registries, attribute rules)
;; and HOW arguments are parsed — but not the output format.
;;
;; Adapters:
;; adapter-html.sx — HTML string output (server)
;; adapter-sx.sx — SX wire format output (server → client)
;; adapter-dom.sx — Live DOM node output (browser)
;;
;; Each adapter imports these shared definitions and provides its own
;; render entry point (render-to-html, render-to-sx, render-to-dom).
;; ==========================================================================
;; --------------------------------------------------------------------------
;; HTML tag registry
;; --------------------------------------------------------------------------
;; Tags known to the renderer. Unknown names are treated as function calls.
;; Void elements self-close (no children). Boolean attrs emit name only.
(define HTML_TAGS
(define
HTML_TAGS
(list
;; Document
"html" "head" "body" "title" "meta" "link" "script" "style" "noscript"
;; Sections
"header" "nav" "main" "section" "article" "aside" "footer"
"h1" "h2" "h3" "h4" "h5" "h6" "hgroup"
;; Block
"div" "p" "blockquote" "pre" "figure" "figcaption" "address" "details" "summary"
;; Inline
"a" "span" "em" "strong" "small" "b" "i" "u" "s" "mark" "sub" "sup"
"abbr" "cite" "code" "kbd" "samp" "var" "time" "br" "wbr" "hr"
;; Lists
"ul" "ol" "li" "dl" "dt" "dd"
;; Tables
"table" "thead" "tbody" "tfoot" "tr" "th" "td" "caption" "colgroup" "col"
;; Forms
"form" "input" "textarea" "select" "option" "optgroup" "button" "label"
"fieldset" "legend" "output" "datalist"
;; Media
"img" "video" "audio" "source" "picture" "canvas" "iframe"
;; SVG
"svg" "math" "path" "circle" "ellipse" "rect" "line" "polyline" "polygon"
"text" "tspan" "g" "defs" "use" "clipPath" "mask" "pattern"
"linearGradient" "radialGradient" "stop" "filter"
"feGaussianBlur" "feOffset" "feBlend" "feColorMatrix" "feComposite"
"feMerge" "feMergeNode" "feTurbulence"
"feComponentTransfer" "feFuncR" "feFuncG" "feFuncB" "feFuncA"
"feDisplacementMap" "feFlood" "feImage" "feMorphology"
"feSpecularLighting" "feDiffuseLighting"
"fePointLight" "feSpotLight" "feDistantLight"
"animate" "animateTransform" "foreignObject"
;; Other
"template" "slot" "dialog" "menu"))
"html"
"head"
"body"
"title"
"meta"
"link"
"script"
"style"
"noscript"
"header"
"nav"
"main"
"section"
"article"
"aside"
"footer"
"h1"
"h2"
"h3"
"h4"
"h5"
"h6"
"hgroup"
"div"
"p"
"blockquote"
"pre"
"figure"
"figcaption"
"address"
"details"
"summary"
"a"
"span"
"em"
"strong"
"small"
"b"
"i"
"u"
"s"
"mark"
"sub"
"sup"
"abbr"
"cite"
"code"
"kbd"
"samp"
"var"
"time"
"br"
"wbr"
"hr"
"ul"
"ol"
"li"
"dl"
"dt"
"dd"
"table"
"thead"
"tbody"
"tfoot"
"tr"
"th"
"td"
"caption"
"colgroup"
"col"
"form"
"input"
"textarea"
"select"
"option"
"optgroup"
"button"
"label"
"fieldset"
"legend"
"output"
"datalist"
"img"
"video"
"audio"
"source"
"picture"
"canvas"
"iframe"
"svg"
"math"
"path"
"circle"
"ellipse"
"rect"
"line"
"polyline"
"polygon"
"text"
"tspan"
"g"
"defs"
"use"
"clipPath"
"mask"
"pattern"
"linearGradient"
"radialGradient"
"stop"
"filter"
"feGaussianBlur"
"feOffset"
"feBlend"
"feColorMatrix"
"feComposite"
"feMerge"
"feMergeNode"
"feTurbulence"
"feComponentTransfer"
"feFuncR"
"feFuncG"
"feFuncB"
"feFuncA"
"feDisplacementMap"
"feFlood"
"feImage"
"feMorphology"
"feSpecularLighting"
"feDiffuseLighting"
"fePointLight"
"feSpotLight"
"feDistantLight"
"animate"
"animateTransform"
"foreignObject"
"template"
"slot"
"dialog"
"menu"))
(define VOID_ELEMENTS
(list "area" "base" "br" "col" "embed" "hr" "img" "input"
"link" "meta" "param" "source" "track" "wbr"))
(define
VOID_ELEMENTS
(list
"area"
"base"
"br"
"col"
"embed"
"hr"
"img"
"input"
"link"
"meta"
"param"
"source"
"track"
"wbr"))
(define BOOLEAN_ATTRS
(list "async" "autofocus" "autoplay" "checked" "controls" "default"
"defer" "disabled" "formnovalidate" "hidden" "inert" "ismap"
"loop" "multiple" "muted" "nomodule" "novalidate" "open"
"playsinline" "readonly" "required" "reversed" "selected"))
(define
BOOLEAN_ATTRS
(list
"async"
"autofocus"
"autoplay"
"checked"
"controls"
"default"
"defer"
"disabled"
"formnovalidate"
"hidden"
"inert"
"ismap"
"loop"
"multiple"
"muted"
"nomodule"
"novalidate"
"open"
"playsinline"
"readonly"
"required"
"reversed"
"selected"))
;; --------------------------------------------------------------------------
;; Shared utilities
;; --------------------------------------------------------------------------
;; Extension point for definition forms — modules append names here.
;; Survives spec reloads (no function wrapping needed).
(define *definition-form-extensions* (list))
(define definition-form? :effects []
(fn ((name :as string))
(or (= name "define") (= name "defcomp") (= name "defisland")
(= name "defmacro") (= name "defstyle")
(= name "deftype") (= name "defeffect")
(contains? *definition-form-extensions* name))))
(define
definition-form?
:effects ()
(fn
((name :as string))
(or
(= name "define")
(= name "defcomp")
(= name "defisland")
(= name "defmacro")
(= name "defstyle")
(= name "deftype")
(= name "defeffect")
(contains? *definition-form-extensions* name))))
(define parse-element-args :effects [render]
(fn ((args :as list) (env :as dict))
;; Parse (:key val :key2 val2 child1 child2) into (attrs-dict children-list)
(let ((attrs (dict))
(children (list)))
(define
parse-element-args
:effects (render)
(fn
((args :as list) (env :as dict))
(let
((attrs (dict)) (children (list)))
(reduce
(fn ((state :as dict) arg)
(let ((skip (get state "skip")))
(if skip
(fn
((state :as dict) arg)
(let
((skip (get state "skip")))
(if
skip
(assoc state "skip" false "i" (inc (get state "i")))
(if (and (= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let ((val (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
(if
(and
(= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let
((val (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
(dict-set! attrs (keyword-name arg) val)
(assoc state "skip" true "i" (inc (get state "i"))))
(do
@@ -105,193 +233,168 @@
args)
(list attrs children))))
(define render-attrs :effects []
(fn ((attrs :as dict))
;; Render an attrs dict to an HTML attribute string.
;; Used by adapter-html.sx and adapter-sx.sx.
(join ""
(define
render-attrs
:effects ()
(fn
((attrs :as dict))
(join
""
(map
(fn ((key :as string))
(let ((val (dict-get attrs key)))
(fn
((key :as string))
(let
((val (dict-get attrs key)))
(cond
;; Boolean attrs
(and (contains? BOOLEAN_ATTRS key) val)
(str " " key)
(str " " key)
(and (contains? BOOLEAN_ATTRS key) (not val))
""
;; Nil values — skip
(nil? val) ""
;; Normal attr
""
(nil? val)
""
:else (str " " key "=\"" (escape-attr (str val)) "\""))))
(keys attrs)))))
;; --------------------------------------------------------------------------
;; Render adapter helpers
;; --------------------------------------------------------------------------
;; Shared by HTML and DOM adapters for evaluating control forms during
;; rendering. Unlike sf-cond (eval.sx) which returns a thunk for TCO,
;; eval-cond returns the unevaluated body expression so the adapter
;; can render it in its own mode (HTML string vs DOM nodes).
;; eval-cond: find matching cond branch, return unevaluated body expr.
;; Handles both scheme-style ((test body) ...) and clojure-style
;; (test body test body ...).
(define eval-cond :effects []
(fn ((clauses :as list) (env :as dict))
(if (cond-scheme? clauses)
(define
eval-cond
:effects ()
(fn
((clauses :as list) (env :as dict))
(if
(cond-scheme? clauses)
(eval-cond-scheme clauses env)
(eval-cond-clojure clauses env))))
(define eval-cond-scheme :effects []
(fn ((clauses :as list) (env :as dict))
(if (empty? clauses)
(define
eval-cond-scheme
:effects ()
(fn
((clauses :as list) (env :as dict))
(if
(empty? clauses)
nil
(let ((clause (first clauses))
(test (first clause))
(body (nth clause 1)))
(if (is-else-clause? test)
(let
((clause (first clauses))
(test (first clause))
(body (nth clause 1)))
(if
(is-else-clause? test)
body
(if (trampoline (eval-expr test env))
(if
(trampoline (eval-expr test env))
body
(eval-cond-scheme (rest clauses) env)))))))
(define eval-cond-clojure :effects []
(fn ((clauses :as list) (env :as dict))
(if (< (len clauses) 2)
(define
eval-cond-clojure
:effects ()
(fn
((clauses :as list) (env :as dict))
(if
(< (len clauses) 2)
nil
(let ((test (first clauses))
(body (nth clauses 1)))
(if (is-else-clause? test)
(let
((test (first clauses)) (body (nth clauses 1)))
(if
(is-else-clause? test)
body
(if (trampoline (eval-expr test env))
(if
(trampoline (eval-expr test env))
body
(eval-cond-clojure (slice clauses 2) env)))))))
;; process-bindings: evaluate let-binding pairs, return extended env.
;; bindings = ((name1 expr1) (name2 expr2) ...)
(define process-bindings :effects [mutation]
(fn ((bindings :as list) (env :as dict))
;; env-extend (not merge) — Env is not a dict subclass, so merge()
;; returns an empty dict, losing all parent scope bindings.
(let ((local (env-extend env)))
(define
process-bindings
:effects (mutation)
(fn
((bindings :as list) (env :as dict))
(let
((local (env-extend env)))
(for-each
(fn ((pair :as list))
(when (and (= (type-of pair) "list") (>= (len pair) 2))
(let ((name (if (= (type-of (first pair)) "symbol")
(symbol-name (first pair))
(str (first pair)))))
(env-bind! local name (trampoline (eval-expr (nth pair 1) local))))))
(fn
((pair :as list))
(when
(and (= (type-of pair) "list") (>= (len pair) 2))
(let
((name (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair)))))
(env-bind!
local
name
(trampoline (eval-expr (nth pair 1) local))))))
bindings)
local)))
;; --------------------------------------------------------------------------
;; is-render-expr? — check if expression is a rendering form
;; --------------------------------------------------------------------------
;; Used by eval-list to dispatch rendering forms to the active adapter
;; (HTML, SX wire, or DOM) rather than evaluating them as function calls.
(define is-render-expr? :effects []
(fn (expr)
(if (or (not (= (type-of expr) "list")) (empty? expr))
(define
is-render-expr?
:effects ()
(fn
(expr)
(if
(or (not (= (type-of expr) "list")) (empty? expr))
false
(let ((h (first expr)))
(if (not (= (type-of h) "symbol"))
(let
((h (first expr)))
(if
(not (= (type-of h) "symbol"))
false
(let ((n (symbol-name h)))
(or (= n "<>")
(= n "raw!")
(starts-with? n "~")
(starts-with? n "html:")
(contains? HTML_TAGS n)
(and (> (index-of n "-") 0)
(> (len expr) 1)
(= (type-of (nth expr 1)) "keyword")))))))))
(let
((n (symbol-name h)))
(or
(= n "<>")
(= n "raw!")
(starts-with? n "~")
(starts-with? n "html:")
(contains? HTML_TAGS n)
(and
(> (index-of n "-") 0)
(> (len expr) 1)
(= (type-of (nth expr 1)) "keyword")))))))))
;; --------------------------------------------------------------------------
;; Spread — attribute injection from children into parent elements
;; --------------------------------------------------------------------------
;;
;; A spread value is a dict of attributes that, when returned as a child
;; of an HTML element, merges its attrs onto the parent element.
;; This enables components to inject classes/styles/data-attrs onto their
;; parent without the parent knowing about the specific attrs.
;;
;; merge-spread-attrs: merge a spread's attrs into an element's attrs dict.
;; Class values are joined (space-separated); others overwrite.
;; Mutates the target attrs dict in place.
(define merge-spread-attrs :effects [mutation]
(fn ((target :as dict) (spread-dict :as dict))
(define
merge-spread-attrs
:effects (mutation)
(fn
((target :as dict) (spread-dict :as dict))
(for-each
(fn ((key :as string))
(let ((val (dict-get spread-dict key)))
(if (= key "class")
;; Class: join existing + new with space
(let ((existing (dict-get target "class")))
(dict-set! target "class"
(if (and existing (not (= existing "")))
(fn
((key :as string))
(let
((val (dict-get spread-dict key)))
(if
(= key "class")
(let
((existing (dict-get target "class")))
(dict-set!
target
"class"
(if
(and existing (not (= existing "")))
(str existing " " val)
val)))
;; Style: join with semicolons
(if (= key "style")
(let ((existing (dict-get target "style")))
(dict-set! target "style"
(if (and existing (not (= existing "")))
(if
(= key "style")
(let
((existing (dict-get target "style")))
(dict-set!
target
"style"
(if
(and existing (not (= existing "")))
(str existing ";" val)
val)))
;; Everything else: overwrite
(dict-set! target key val)))))
(keys spread-dict))))
;; --------------------------------------------------------------------------
;; HTML escaping — library functions (pure text processing)
;; --------------------------------------------------------------------------
(define escape-html
(fn (s)
(let ((r (str s)))
(define
escape-html
(fn
(s)
(let
((r (str s)))
(set! r (replace r "&" "&amp;"))
(set! r (replace r "<" "&lt;"))
(set! r (replace r ">" "&gt;"))
(set! r (replace r "\"" "&quot;"))
r)))
(define escape-attr
(fn (s)
(escape-html s)))
;; --------------------------------------------------------------------------
;; Platform interface (shared across adapters)
;; --------------------------------------------------------------------------
;;
;; Raw HTML (marker type for unescaped content):
;; (raw-html-content r) → unwrap RawHTML marker to string
;;
;; Spread (render-time attribute injection):
;; (make-spread attrs) → Spread value
;; (spread? x) → boolean
;; (spread-attrs s) → dict
;;
;; Render-time accumulators:
;; (collect! bucket value) → void
;; (collected bucket) → list
;; (clear-collected! bucket) → void
;;
;; Scoped effects (scope/provide/context/emit!):
;; (scope-push! name val) → void (general form)
;; (scope-pop! name) → void (general form)
;; (provide-push! name val) → alias for scope-push!
;; (provide-pop! name) → alias for scope-pop!
;; (context name &rest def) → value from nearest scope
;; (emit! name value) → void (append to scope accumulator)
;; (emitted name) → list of emitted values
;;
;; From parser.sx:
;; (sx-serialize val) → SX source string (aliased as serialize above)
;; --------------------------------------------------------------------------
(define escape-attr (fn (s) (escape-html s)))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -35,25 +35,9 @@
;; Named stores — page-level signal containers
;; --------------------------------------------------------------------------
(define *store-registry* (dict))
(define def-store :effects [mutation]
(fn ((name :as string) (init-fn :as lambda))
(let ((registry *store-registry*))
(when (not (has-key? registry name))
(set! *store-registry* (assoc registry name (cek-call init-fn nil))))
(get *store-registry* name))))
(define use-store :effects []
(fn ((name :as string))
(if (has-key? *store-registry* name)
(get *store-registry* name)
(error (str "Store not found: " name
". Call (def-store ...) before (use-store ...).")))))
(define clear-stores :effects [mutation]
(fn ()
(set! *store-registry* (dict))))
;; def-store, use-store, clear-stores are now OCaml primitives
;; (sx_primitives.ml) with a global mutable registry that survives
;; env scoping across bytecode modules and island hydration.
;; --------------------------------------------------------------------------

View File

@@ -1,3 +1,3 @@
(sxbc 1 "1e908c466d2b8c22"
(sxbc 1 "7e4a727b2f55684e"
(code
:constants ("with-marsh-scope" {:upvalue-count 0 :arity 2 :constants ("list" "with-island-scope" {:upvalue-count 1 :arity 1 :constants ("append!") :bytecode (20 0 0 18 0 16 0 49 2 50)} "dom-set-data" "sx-marsh-disposers") :bytecode (52 0 0 0 17 2 20 1 0 51 2 0 1 2 16 1 48 2 5 20 3 0 16 0 1 4 0 16 2 49 3 50)} "dispose-marsh-scope" {:upvalue-count 0 :arity 1 :constants ("dom-get-data" "sx-marsh-disposers" "for-each" {:upvalue-count 0 :arity 1 :constants ("cek-call") :bytecode (20 0 0 16 0 2 49 2 50)} "dom-set-data") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 33 24 0 51 3 0 16 1 52 2 0 2 5 20 4 0 16 0 1 1 0 2 49 3 32 1 0 2 50)} "*store-registry*" "dict" "def-store" {:upvalue-count 0 :arity 2 :constants ("*store-registry*" "not" "has-key?" "assoc" "cek-call" "get") :bytecode (20 0 0 17 2 16 2 16 0 52 2 0 2 52 1 0 1 33 22 0 16 2 16 0 20 4 0 16 1 2 48 2 52 3 0 3 21 0 0 32 1 0 2 5 20 0 0 16 0 52 5 0 2 50)} "use-store" {:upvalue-count 0 :arity 1 :constants ("has-key?" "*store-registry*" "get" "error" "str" "Store not found: " ". Call (def-store ...) before (use-store ...).") :bytecode (20 1 0 16 0 52 0 0 2 33 12 0 20 1 0 16 0 52 2 0 2 32 16 0 1 5 0 16 0 1 6 0 52 4 0 3 52 3 0 1 50)} "clear-stores" {:upvalue-count 0 :arity 0 :constants ("dict" "*store-registry*") :bytecode (52 0 0 0 21 1 0 50)} "emit-event" {:upvalue-count 0 :arity 3 :constants ("dom-dispatch") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "on-event" {:upvalue-count 0 :arity 3 :constants ("dom-on") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "bridge-event" {:upvalue-count 0 :arity 4 :constants ("effect" {:upvalue-count 4 :arity 0 :constants ("dom-on" {:upvalue-count 2 :arity 1 :constants ("event-detail" "cek-call" "list" "reset!") :bytecode (20 0 0 16 0 48 1 17 1 18 0 33 16 0 20 1 0 18 0 16 1 52 2 0 1 48 2 32 2 0 16 1 17 2 20 3 0 18 1 16 2 49 2 50)}) :bytecode (20 0 0 18 0 18 1 51 1 0 0 2 0 3 48 3 17 0 16 0 50)}) :bytecode (20 0 0 51 1 0 1 0 1 1 1 3 1 2 49 1 50)} "resource" {:upvalue-count 0 :arity 1 :constants ("signal" "dict" "loading" "data" "error" "promise-then" "cek-call" {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 16 0 1 4 0 2 52 1 0 6 49 2 50)} {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 2 1 4 0 16 0 52 1 0 6 49 2 50)}) :bytecode (20 0 0 1 2 0 3 1 3 0 2 1 4 0 2 52 1 0 6 48 1 17 1 20 5 0 20 6 0 16 0 2 48 2 51 7 0 1 1 51 8 0 1 1 48 3 5 16 1 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 52 5 0 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 51 15 0 128 14 0 5 51 17 0 128 16 0 5 51 19 0 128 18 0 50)))
:constants ("with-marsh-scope" {:upvalue-count 0 :arity 2 :constants ("list" "with-island-scope" {:upvalue-count 1 :arity 1 :constants ("append!") :bytecode (20 0 0 18 0 16 0 49 2 50)} "dom-set-data" "sx-marsh-disposers") :bytecode (52 0 0 0 17 2 20 1 0 51 2 0 1 2 16 1 48 2 5 20 3 0 16 0 1 4 0 16 2 49 3 50)} "dispose-marsh-scope" {:upvalue-count 0 :arity 1 :constants ("dom-get-data" "sx-marsh-disposers" "for-each" {:upvalue-count 0 :arity 1 :constants ("cek-call") :bytecode (20 0 0 16 0 2 49 2 50)} "dom-set-data") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 33 24 0 51 3 0 16 1 52 2 0 2 5 20 4 0 16 0 1 1 0 2 49 3 32 1 0 2 50)} "emit-event" {:upvalue-count 0 :arity 3 :constants ("dom-dispatch") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "on-event" {:upvalue-count 0 :arity 3 :constants ("dom-on") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "bridge-event" {:upvalue-count 0 :arity 4 :constants ("effect" {:upvalue-count 4 :arity 0 :constants ("dom-on" {:upvalue-count 2 :arity 1 :constants ("event-detail" "cek-call" "list" "reset!") :bytecode (20 0 0 16 0 48 1 17 1 18 0 33 16 0 20 1 0 18 0 16 1 52 2 0 1 48 2 32 2 0 16 1 17 2 20 3 0 18 1 16 2 49 2 50)}) :bytecode (20 0 0 18 0 18 1 51 1 0 0 2 0 3 48 3 17 0 16 0 50)}) :bytecode (20 0 0 51 1 0 1 0 1 1 1 3 1 2 49 1 50)} "resource" {:upvalue-count 0 :arity 1 :constants ("signal" "dict" "loading" "data" "error" "promise-then" "cek-call" {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 16 0 1 4 0 2 52 1 0 6 49 2 50)} {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 2 1 4 0 16 0 52 1 0 6 49 2 50)}) :bytecode (20 0 0 1 2 0 3 1 3 0 2 1 4 0 2 52 1 0 6 48 1 17 1 20 5 0 20 6 0 16 0 2 48 2 51 7 0 1 1 51 8 0 1 1 48 3 5 16 1 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 50)))

View File

@@ -1 +0,0 @@
{"magic":"SXBC","version":1,"hash":"f460911b9f86fad2","module":{"arity":0,"bytecode":[51,1,0,128,0,0,5,51,3,0,128,2,0,5,52,5,0,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,5,51,19,0,128,18,0,50],"constants":[{"t":"s","v":"with-marsh-scope"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[52,0,0,0,17,2,20,1,0,51,2,0,20,3,0,48,2,5,20,4,0,20,5,0,1,6,0,20,7,0,49,3,50],"constants":[{"t":"s","v":"list"},{"t":"s","v":"with-island-scope"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,20,2,0,49,2,50],"constants":[{"t":"s","v":"append!"},{"t":"s","v":"disposers"},{"t":"s","v":"d"}]}},{"t":"s","v":"body-fn"},{"t":"s","v":"dom-set-data"},{"t":"s","v":"marsh-el"},{"t":"s","v":"sx-marsh-disposers"},{"t":"s","v":"disposers"}]}},{"t":"s","v":"dispose-marsh-scope"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,1,2,0,48,2,17,1,20,3,0,33,26,0,51,5,0,20,3,0,52,4,0,2,5,20,6,0,20,1,0,1,2,0,2,49,3,32,1,0,2,50],"constants":[{"t":"s","v":"dom-get-data"},{"t":"s","v":"marsh-el"},{"t":"s","v":"sx-marsh-disposers"},{"t":"s","v":"disposers"},{"t":"s","v":"for-each"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,2,49,2,50],"constants":[{"t":"s","v":"cek-call"},{"t":"s","v":"d"}]}},{"t":"s","v":"dom-set-data"}]}},{"t":"s","v":"*store-registry*"},{"t":"s","v":"dict"},{"t":"s","v":"def-store"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,17,2,20,3,0,20,4,0,52,2,0,2,52,1,0,1,33,25,0,20,3,0,20,4,0,20,6,0,20,7,0,2,48,2,52,5,0,3,21,0,0,32,1,0,2,5,20,0,0,20,4,0,52,8,0,2,50],"constants":[{"t":"s","v":"*store-registry*"},{"t":"s","v":"not"},{"t":"s","v":"has-key?"},{"t":"s","v":"registry"},{"t":"s","v":"name"},{"t":"s","v":"assoc"},{"t":"s","v":"cek-call"},{"t":"s","v":"init-fn"},{"t":"s","v":"get"}]}},{"t":"s","v":"use-store"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,1,0,20,2,0,52,0,0,2,33,13,0,20,1,0,20,2,0,52,3,0,2,32,17,0,1,6,0,20,2,0,1,7,0,52,5,0,3,52,4,0,1,50],"constants":[{"t":"s","v":"has-key?"},{"t":"s","v":"*store-registry*"},{"t":"s","v":"name"},{"t":"s","v":"get"},{"t":"s","v":"error"},{"t":"s","v":"str"},{"t":"s","v":"Store not found: "},{"t":"s","v":". Call (def-store ...) before (use-store ...)."}]}},{"t":"s","v":"clear-stores"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[52,0,0,0,21,1,0,50],"constants":[{"t":"s","v":"dict"},{"t":"s","v":"*store-registry*"}]}},{"t":"s","v":"emit-event"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,20,2,0,20,3,0,49,3,50],"constants":[{"t":"s","v":"dom-dispatch"},{"t":"s","v":"el"},{"t":"s","v":"event-name"},{"t":"s","v":"detail"}]}},{"t":"s","v":"on-event"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,20,2,0,20,3,0,49,3,50],"constants":[{"t":"s","v":"dom-on"},{"t":"s","v":"el"},{"t":"s","v":"event-name"},{"t":"s","v":"handler"}]}},{"t":"s","v":"bridge-event"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,51,1,0,49,1,50],"constants":[{"t":"s","v":"effect"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,20,2,0,51,3,0,48,3,17,0,20,4,0,50],"constants":[{"t":"s","v":"dom-on"},{"t":"s","v":"el"},{"t":"s","v":"event-name"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,48,1,17,1,20,2,0,33,18,0,20,3,0,20,2,0,20,5,0,52,4,0,1,48,2,32,3,0,20,5,0,17,2,20,6,0,20,7,0,20,8,0,49,2,50],"constants":[{"t":"s","v":"event-detail"},{"t":"s","v":"e"},{"t":"s","v":"transform-fn"},{"t":"s","v":"cek-call"},{"t":"s","v":"list"},{"t":"s","v":"detail"},{"t":"s","v":"reset!"},{"t":"s","v":"target-signal"},{"t":"s","v":"new-val"}]}},{"t":"s","v":"remove"}]}}]}},{"t":"s","v":"resource"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,1,2,0,3,1,3,0,2,1,4,0,2,52,1,0,6,48,1,17,1,20,5,0,20,6,0,20,7,0,2,48,2,51,8,0,51,9,0,48,3,5,20,10,0,50],"constants":[{"t":"s","v":"signal"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"},{"t":"s","v":"promise-then"},{"t":"s","v":"cek-call"},{"t":"s","v":"fetch-fn"},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,1,3,0,4,1,4,0,20,4,0,1,5,0,2,52,2,0,6,49,2,50],"constants":[{"t":"s","v":"reset!"},{"t":"s","v":"state"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"}]}},{"t":"code","v":{"arity":0,"upvalue-count":0,"bytecode":[20,0,0,20,1,0,1,3,0,4,1,4,0,2,1,5,0,20,6,0,52,2,0,6,49,2,50],"constants":[{"t":"s","v":"reset!"},{"t":"s","v":"state"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"},{"t":"s","v":"err"}]}},{"t":"s","v":"state"}]}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long