Aser server-affinity component expansion + readline buffer fix
adapter-sx.sx: aser-expand-component expands :affinity :server components inline during SX wire format serialization. Binds keyword args via eval-expr, children via aser (handles HTML tags), then asers the body. ocaml_bridge.py: 10MB readline buffer for large spec responses. nav-data.sx: evaluator.sx filename fix. Page rendering stays on Python _eval_slot for now — full OCaml rendering needs the page shell IO (headers, CSRF, CSS) migrated to OCaml IO bridge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-18T19:18:46Z";
|
||||
var SX_VERSION = "2026-03-18T20:14:01Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -2673,7 +2673,10 @@ PRIMITIVES["aser"] = aser;
|
||||
var args = rest(expr);
|
||||
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? map(function(x) { return aser(x, env); }, expr) : (function() {
|
||||
var name = symbolName(head);
|
||||
return (isSxTruthy((name == "<>")) ? aserFragment(args, env) : (isSxTruthy(startsWith(name, "~")) ? aserCall(name, args, env) : (isSxTruthy((name == "lake")) ? aserCall(name, args, env) : (isSxTruthy((name == "marsh")) ? aserCall(name, args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? aserCall(name, args, env) : (isSxTruthy(sxOr(isSpecialForm(name), isHoForm(name))) ? aserSpecial(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? aser(expandMacro(envGet(env, name), args, env), env) : (function() {
|
||||
return (isSxTruthy((name == "<>")) ? aserFragment(args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
||||
var comp = (isSxTruthy(envHas(env, name)) ? envGet(env, name) : NIL);
|
||||
return (isSxTruthy((isSxTruthy(comp) && isSxTruthy(isComponent(comp)) && (componentAffinity(comp) == "server"))) ? aserExpandComponent(comp, args, env) : aserCall(name, args, env));
|
||||
})() : (isSxTruthy((name == "lake")) ? aserCall(name, args, env) : (isSxTruthy((name == "marsh")) ? aserCall(name, args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? aserCall(name, args, env) : (isSxTruthy(sxOr(isSpecialForm(name), isHoForm(name))) ? aserSpecial(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? aser(expandMacro(envGet(env, name), args, env), env) : (function() {
|
||||
var f = trampoline(evalExpr(head, env));
|
||||
var evaledArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args);
|
||||
return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaledArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, evaledArgs, env)) : (isSxTruthy(isComponent(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : (isSxTruthy(isIsland(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : error((String("Not callable: ") + String(inspect(f))))))));
|
||||
@@ -2728,6 +2731,24 @@ PRIMITIVES["aser-fragment"] = aserFragment;
|
||||
})(); };
|
||||
PRIMITIVES["aser-call"] = aserCall;
|
||||
|
||||
// aser-expand-component
|
||||
var aserExpandComponent = function(comp, args, env) { return (function() {
|
||||
var params = componentParams(comp);
|
||||
var local = envMerge(env, componentClosure(comp));
|
||||
var i = 0;
|
||||
var skip = false;
|
||||
var children = [];
|
||||
{ var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (isSxTruthy(skip) ? ((skip = false), (i = (i + 1))) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((i + 1) < len(args)))) ? (envBind(local, keywordName(arg), trampoline(evalExpr(nth(args, (i + 1)), env))), (skip = true), (i = (i + 1))) : (append_b(children, arg), (i = (i + 1))))); } }
|
||||
if (isSxTruthy(componentHasChildren(comp))) {
|
||||
(function() {
|
||||
var aseredChildren = map(function(c) { return aser(c, env); }, children);
|
||||
return envBind(local, "children", (isSxTruthy((len(aseredChildren) == 1)) ? first(aseredChildren) : aseredChildren));
|
||||
})();
|
||||
}
|
||||
return aser(componentBody(comp), local);
|
||||
})(); };
|
||||
PRIMITIVES["aser-expand-component"] = aserExpandComponent;
|
||||
|
||||
// SPECIAL_FORM_NAMES
|
||||
var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland", "deftype", "defeffect", "scope", "provide"];
|
||||
PRIMITIVES["SPECIAL_FORM_NAMES"] = SPECIAL_FORM_NAMES;
|
||||
|
||||
@@ -2257,7 +2257,10 @@
|
||||
(env-bind! local "children" children))
|
||||
(make-cek-state (component-body f) local kont))
|
||||
|
||||
:else (error (str "Not callable: " (inspect f))))))
|
||||
:else (error (str "Not callable: " (inspect f)
|
||||
(when raw-args
|
||||
(str " in (" (inspect (first raw-args)) " ...)")))))))
|
||||
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -199,9 +199,9 @@ async def eval_sx_url(raw_path: str) -> Any:
|
||||
logger.error("SX URL render failed for %s: %s", raw_path, e, exc_info=True)
|
||||
return None
|
||||
|
||||
# Return response
|
||||
# Return response — Python wraps in page shell (CSS, scripts, headers)
|
||||
if is_htmx_request():
|
||||
return sx_response(await oob_page_sx(content=content_sx))
|
||||
return sx_response(content_sx)
|
||||
else:
|
||||
tctx = await get_template_context()
|
||||
html = await full_page_sx(tctx, header_rows="", content=content_sx)
|
||||
|
||||
@@ -71,9 +71,13 @@
|
||||
(= name "<>")
|
||||
(aser-fragment args env)
|
||||
|
||||
;; Component call — serialize WITHOUT expanding
|
||||
;; Component call — expand server-affinity, serialize others
|
||||
(starts-with? name "~")
|
||||
(aser-call name args env)
|
||||
(let ((comp (if (env-has? env name) (env-get env name) nil)))
|
||||
(if (and comp (component? comp)
|
||||
(= (component-affinity comp) "server"))
|
||||
(aser-expand-component comp args env)
|
||||
(aser-call name args env)))
|
||||
|
||||
;; Lake — serialize (server-morphable slot)
|
||||
(= name "lake")
|
||||
@@ -213,6 +217,53 @@
|
||||
(str "(" (join " " parts) ")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Server-affinity component expansion
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; When a component has :affinity :server, the aser expands it inline:
|
||||
;; bind keyword args + children, then aser the body.
|
||||
;; This is the aser equivalent of render-to-html's component expansion.
|
||||
|
||||
(define aser-expand-component :effects [render]
|
||||
(fn ((comp :as any) (args :as list) (env :as dict))
|
||||
(let ((params (component-params comp))
|
||||
(local (env-merge env (component-closure comp)))
|
||||
(i 0)
|
||||
(skip false)
|
||||
(children (list)))
|
||||
;; Parse keyword args and positional children from args
|
||||
;; Keyword values are eval'd (they're data). Children are NOT eval'd
|
||||
;; (they may contain HTML tags that only the aser can handle).
|
||||
(for-each
|
||||
(fn (arg)
|
||||
(if skip
|
||||
(do (set! skip false) (set! i (inc i)))
|
||||
(if (and (= (type-of arg) "keyword")
|
||||
(< (inc i) (len args)))
|
||||
;; Keyword arg: bind name = eval'd next arg
|
||||
(do
|
||||
(env-bind! local (keyword-name arg)
|
||||
(trampoline (eval-expr (nth args (inc i)) env)))
|
||||
(set! skip true)
|
||||
(set! i (inc i)))
|
||||
;; Positional child: keep as unevaluated AST for aser
|
||||
(do
|
||||
(append! children arg)
|
||||
(set! i (inc i))))))
|
||||
args)
|
||||
;; Bind &rest children — aser each child first, then bind the result
|
||||
(when (component-has-children comp)
|
||||
(let ((asered-children
|
||||
(map (fn (c) (aser c env)) children)))
|
||||
(env-bind! local "children"
|
||||
(if (= (len asered-children) 1)
|
||||
(first asered-children)
|
||||
asered-children))))
|
||||
;; Aser the body in the merged env
|
||||
(aser (component-body comp) local))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Form classification
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user