Spec eval-cond and process-bindings in render.sx (remove platform implementations)
eval-cond and process-bindings were hand-written platform JS in bootstrap_js.py rather than specced in .sx files. This violated the SX host architecture principle. Now specced in render.sx as shared render adapter helpers, bootstrapped to both JS and Python. eval-cond handles both scheme-style ((test body) ...) and clojure-style (test body test body ...) cond clauses. Returns unevaluated body expression for the adapter to render in its own mode. process-bindings evaluates let-binding pairs and returns extended env. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -493,39 +493,7 @@
|
||||
"map":1,"map-indexed":1,"filter":1,"reduce":1,"some":1,"every?":1,"for-each":1
|
||||
}; }
|
||||
|
||||
// processBindings and evalCond — exposed for DOM adapter render forms
|
||||
function processBindings(bindings, env) {
|
||||
var local = merge(env);
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var pair = bindings[i];
|
||||
if (Array.isArray(pair) && pair.length >= 2) {
|
||||
var name = isSym(pair[0]) ? pair[0].name : String(pair[0]);
|
||||
local[name] = trampoline(evalExpr(pair[1], local));
|
||||
}
|
||||
}
|
||||
return local;
|
||||
}
|
||||
function evalCond(clauses, env) {
|
||||
// Detect scheme-style ((test body) ...) vs clojure-style (test body test body ...)
|
||||
var scheme = clauses.length > 0 && Array.isArray(clauses[0]) && clauses[0].length === 2;
|
||||
if (scheme) {
|
||||
for (var i = 0; i < clauses.length; i++) {
|
||||
var clause = clauses[i];
|
||||
var test = clause[0], body = clause[1];
|
||||
if (isSym(test) && (test.name === "else" || test.name === ":else")) return body;
|
||||
if (isKw(test) && test.name === "else") return body;
|
||||
if (isSxTruthy(trampoline(evalExpr(test, env)))) return body;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < clauses.length; i += 2) {
|
||||
var test = clauses[i];
|
||||
if (isSym(test) && test.name === ":else") return clauses[i + 1];
|
||||
if (isKw(test) && test.name === "else") return clauses[i + 1];
|
||||
if (isSxTruthy(trampoline(evalExpr(test, env)))) return clauses[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
||||
|
||||
function isDefinitionForm(name) {
|
||||
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
||||
@@ -1082,6 +1050,36 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
||||
return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !val)) ? "" : (isSxTruthy(isNil(val)) ? "" : (isSxTruthy((isSxTruthy((key == "style")) && isStyleValue(val))) ? (String(" class=\"") + String(styleValueClass(val)) + String("\"")) : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\""))))));
|
||||
})(); }, keys(attrs))); };
|
||||
|
||||
// eval-cond
|
||||
var evalCond = function(clauses, env) { return (isSxTruthy((isSxTruthy(!isEmpty(clauses)) && isSxTruthy((typeOf(first(clauses)) == "list")) && (len(first(clauses)) == 2))) ? evalCondScheme(clauses, env) : evalCondClojure(clauses, env)); };
|
||||
|
||||
// eval-cond-scheme
|
||||
var evalCondScheme = function(clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() {
|
||||
var clause = first(clauses);
|
||||
var test = first(clause);
|
||||
var body = nth(clause, 1);
|
||||
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))), (isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondScheme(rest(clauses), env)));
|
||||
})()); };
|
||||
|
||||
// eval-cond-clojure
|
||||
var evalCondClojure = function(clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() {
|
||||
var test = first(clauses);
|
||||
var body = nth(clauses, 1);
|
||||
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondClojure(slice(clauses, 2), env)));
|
||||
})()); };
|
||||
|
||||
// process-bindings
|
||||
var processBindings = function(bindings, env) { return (function() {
|
||||
var local = merge(env);
|
||||
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) {
|
||||
(function() {
|
||||
var name = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair))));
|
||||
return envSet(local, name, trampoline(evalExpr(nth(pair, 1), local)));
|
||||
})();
|
||||
} } }
|
||||
return local;
|
||||
})(); };
|
||||
|
||||
|
||||
// === Transpiled from parser ===
|
||||
|
||||
|
||||
@@ -273,6 +273,8 @@ class JSEmitter:
|
||||
"dict-delete!": "dictDelete",
|
||||
"process-bindings": "processBindings",
|
||||
"eval-cond": "evalCond",
|
||||
"eval-cond-scheme": "evalCondScheme",
|
||||
"eval-cond-clojure": "evalCondClojure",
|
||||
"for-each-indexed": "forEachIndexed",
|
||||
"index-of": "indexOf_",
|
||||
"component-has-children?": "componentHasChildren",
|
||||
@@ -1823,39 +1825,7 @@ PLATFORM_JS_POST = '''
|
||||
"map":1,"map-indexed":1,"filter":1,"reduce":1,"some":1,"every?":1,"for-each":1
|
||||
}; }
|
||||
|
||||
// processBindings and evalCond — exposed for DOM adapter render forms
|
||||
function processBindings(bindings, env) {
|
||||
var local = merge(env);
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var pair = bindings[i];
|
||||
if (Array.isArray(pair) && pair.length >= 2) {
|
||||
var name = isSym(pair[0]) ? pair[0].name : String(pair[0]);
|
||||
local[name] = trampoline(evalExpr(pair[1], local));
|
||||
}
|
||||
}
|
||||
return local;
|
||||
}
|
||||
function evalCond(clauses, env) {
|
||||
// Detect scheme-style ((test body) ...) vs clojure-style (test body test body ...)
|
||||
var scheme = clauses.length > 0 && Array.isArray(clauses[0]) && clauses[0].length === 2;
|
||||
if (scheme) {
|
||||
for (var i = 0; i < clauses.length; i++) {
|
||||
var clause = clauses[i];
|
||||
var test = clause[0], body = clause[1];
|
||||
if (isSym(test) && (test.name === "else" || test.name === ":else")) return body;
|
||||
if (isKw(test) && test.name === "else") return body;
|
||||
if (isSxTruthy(trampoline(evalExpr(test, env)))) return body;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < clauses.length; i += 2) {
|
||||
var test = clauses[i];
|
||||
if (isSym(test) && test.name === ":else") return clauses[i + 1];
|
||||
if (isKw(test) && test.name === "else") return clauses[i + 1];
|
||||
if (isSxTruthy(trampoline(evalExpr(test, env)))) return clauses[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
||||
|
||||
function isDefinitionForm(name) {
|
||||
return name === "define" || name === "defcomp" || name === "defmacro" ||
|
||||
|
||||
@@ -236,6 +236,8 @@ class PyEmitter:
|
||||
"map-indexed": "map_indexed",
|
||||
"map-dict": "map_dict",
|
||||
"eval-cond": "eval_cond",
|
||||
"eval-cond-scheme": "eval_cond_scheme",
|
||||
"eval-cond-clojure": "eval_cond_clojure",
|
||||
"process-bindings": "process_bindings",
|
||||
# deps.sx
|
||||
"scan-refs": "scan_refs",
|
||||
|
||||
@@ -124,6 +124,75 @@
|
||||
(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
|
||||
(fn (clauses env)
|
||||
(if (and (not (empty? clauses))
|
||||
(= (type-of (first clauses)) "list")
|
||||
(= (len (first clauses)) 2))
|
||||
;; Scheme-style
|
||||
(eval-cond-scheme clauses env)
|
||||
;; Clojure-style
|
||||
(eval-cond-clojure clauses env))))
|
||||
|
||||
(define eval-cond-scheme
|
||||
(fn (clauses env)
|
||||
(if (empty? clauses)
|
||||
nil
|
||||
(let ((clause (first clauses))
|
||||
(test (first clause))
|
||||
(body (nth clause 1)))
|
||||
(if (or (and (= (type-of test) "symbol")
|
||||
(or (= (symbol-name test) "else")
|
||||
(= (symbol-name test) ":else")))
|
||||
(and (= (type-of test) "keyword")
|
||||
(= (keyword-name test) "else")))
|
||||
body
|
||||
(if (trampoline (eval-expr test env))
|
||||
body
|
||||
(eval-cond-scheme (rest clauses) env)))))))
|
||||
|
||||
(define eval-cond-clojure
|
||||
(fn (clauses env)
|
||||
(if (< (len clauses) 2)
|
||||
nil
|
||||
(let ((test (first clauses))
|
||||
(body (nth clauses 1)))
|
||||
(if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
|
||||
(and (= (type-of test) "symbol")
|
||||
(or (= (symbol-name test) "else")
|
||||
(= (symbol-name test) ":else"))))
|
||||
body
|
||||
(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
|
||||
(fn (bindings env)
|
||||
(let ((local (merge env)))
|
||||
(for-each
|
||||
(fn (pair)
|
||||
(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-set! local name (trampoline (eval-expr (nth pair 1) local))))))
|
||||
bindings)
|
||||
local)))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Platform interface (shared across adapters)
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -1140,6 +1140,18 @@ parse_element_args = lambda args, env: (lambda attrs: (lambda children: _sx_begi
|
||||
# render-attrs
|
||||
render_attrs = lambda attrs: join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else (sx_str(' class="', style_value_class(val), '"') if sx_truthy(((key == 'style') if not sx_truthy((key == 'style')) else is_style_value(val))) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"'))))))(dict_get(attrs, key)), keys(attrs)))
|
||||
|
||||
# eval-cond
|
||||
eval_cond = lambda clauses, env: (eval_cond_scheme(clauses, env) if sx_truthy(((not sx_truthy(empty_p(clauses))) if not sx_truthy((not sx_truthy(empty_p(clauses)))) else ((type_of(first(clauses)) == 'list') if not sx_truthy((type_of(first(clauses)) == 'list')) else (len(first(clauses)) == 2)))) else eval_cond_clojure(clauses, env))
|
||||
|
||||
# eval-cond-scheme
|
||||
eval_cond_scheme = lambda clauses, env: (NIL if sx_truthy(empty_p(clauses)) else (lambda clause: (lambda test: (lambda body: (body if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))) else (body if sx_truthy(trampoline(eval_expr(test, env))) else eval_cond_scheme(rest(clauses), env))))(nth(clause, 1)))(first(clause)))(first(clauses)))
|
||||
|
||||
# eval-cond-clojure
|
||||
eval_cond_clojure = lambda clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (body if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))) else (body if sx_truthy(trampoline(eval_expr(test, env))) else eval_cond_clojure(slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses)))
|
||||
|
||||
# process-bindings
|
||||
process_bindings = lambda bindings, env: (lambda local: _sx_begin(for_each(lambda pair: ((lambda name: _sx_dict_set(local, name, trampoline(eval_expr(nth(pair, 1), local))))((symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))) if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))) else NIL), bindings), local))(merge(env))
|
||||
|
||||
|
||||
# === Transpiled from adapter-html ===
|
||||
|
||||
|
||||
Reference in New Issue
Block a user