Rebrand sexp → sx across web platform (173 files)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
Rename all sexp directories, files, identifiers, and references to sx. artdag/ excluded (separate media processing DSL). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* sexp.js — S-expression parser, evaluator, and DOM renderer.
|
||||
* sx.js — S-expression parser, evaluator, and DOM renderer.
|
||||
*
|
||||
* Client-side counterpart to shared/sexp/ Python modules.
|
||||
* Client-side counterpart to shared/sx/ Python modules.
|
||||
* Parses s-expression text, evaluates it, and renders to DOM nodes.
|
||||
*
|
||||
* Usage:
|
||||
* Sexp.loadComponents('(defcomp ~card (&key title) (div :class "c" title))');
|
||||
* const node = Sexp.render('(~card :title "Hello")');
|
||||
* Sx.loadComponents('(defcomp ~card (&key title) (div :class "c" title))');
|
||||
* const node = Sx.render('(~card :title "Hello")');
|
||||
* document.body.appendChild(node);
|
||||
*/
|
||||
;(function (global) {
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isTruthy(x) { return x !== false && !isNil(x) && x !== 0 && x !== ""; }
|
||||
// Note: 0 and "" are falsy in sexp but we match Python semantics where
|
||||
// Note: 0 and "" are falsy in sx but we match Python semantics where
|
||||
// only nil/false/None are falsy for control flow. Revisit if needed.
|
||||
function isSexpTruthy(x) { return x !== false && !isNil(x); }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
|
||||
function Symbol(name) { this.name = name; }
|
||||
Symbol.prototype.toString = function () { return this.name; };
|
||||
@@ -243,7 +243,7 @@
|
||||
PRIMITIVES["pow"] = Math.pow;
|
||||
|
||||
// Comparison
|
||||
PRIMITIVES["="] = function (a, b) { return a == b; }; // loose, matches Python sexp
|
||||
PRIMITIVES["="] = function (a, b) { return a == b; }; // loose, matches Python sx
|
||||
PRIMITIVES["!="] = function (a, b) { return a != b; };
|
||||
PRIMITIVES["<"] = function (a, b) { return a < b; };
|
||||
PRIMITIVES[">"] = function (a, b) { return a > b; };
|
||||
@@ -251,7 +251,7 @@
|
||||
PRIMITIVES[">="] = function (a, b) { return a >= b; };
|
||||
|
||||
// Logic
|
||||
PRIMITIVES["not"] = function (x) { return !isSexpTruthy(x); };
|
||||
PRIMITIVES["not"] = function (x) { return !isSxTruthy(x); };
|
||||
|
||||
// String
|
||||
PRIMITIVES["str"] = function () {
|
||||
@@ -326,7 +326,7 @@
|
||||
// Evaluator
|
||||
// =========================================================================
|
||||
|
||||
function sexpEval(expr, env) {
|
||||
function sxEval(expr, env) {
|
||||
// Literals
|
||||
if (typeof expr === "number" || typeof expr === "string" || typeof expr === "boolean") return expr;
|
||||
if (isNil(expr)) return NIL;
|
||||
@@ -348,7 +348,7 @@
|
||||
// Dict literal
|
||||
if (expr && typeof expr === "object" && !Array.isArray(expr) && !expr._sym && !expr._kw && !expr._raw) {
|
||||
var d = {};
|
||||
for (var dk in expr) d[dk] = sexpEval(expr[dk], env);
|
||||
for (var dk in expr) d[dk] = sxEval(expr[dk], env);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@
|
||||
|
||||
// Non-callable head → data list
|
||||
if (!isSym(head) && !isLambda(head) && !Array.isArray(head)) {
|
||||
return expr.map(function (x) { return sexpEval(x, env); });
|
||||
return expr.map(function (x) { return sxEval(x, env); });
|
||||
}
|
||||
|
||||
// Special forms
|
||||
@@ -372,9 +372,9 @@
|
||||
}
|
||||
|
||||
// Function call
|
||||
var fn = sexpEval(head, env);
|
||||
var fn = sxEval(head, env);
|
||||
var args = [];
|
||||
for (var ai = 1; ai < expr.length; ai++) args.push(sexpEval(expr[ai], env));
|
||||
for (var ai = 1; ai < expr.length; ai++) args.push(sxEval(expr[ai], env));
|
||||
|
||||
if (typeof fn === "function") return fn.apply(null, args);
|
||||
if (isLambda(fn)) return callLambda(fn, args, env);
|
||||
@@ -388,7 +388,7 @@
|
||||
}
|
||||
var local = merge({}, fn.closure, callerEnv);
|
||||
for (var i = 0; i < fn.params.length; i++) local[fn.params[i]] = args[i];
|
||||
return sexpEval(fn.body, local);
|
||||
return sxEval(fn.body, local);
|
||||
}
|
||||
|
||||
function callComponent(comp, rawArgs, env) {
|
||||
@@ -396,10 +396,10 @@
|
||||
var i = 0;
|
||||
while (i < rawArgs.length) {
|
||||
if (isKw(rawArgs[i]) && i + 1 < rawArgs.length) {
|
||||
kwargs[rawArgs[i].name] = sexpEval(rawArgs[i + 1], env);
|
||||
kwargs[rawArgs[i].name] = sxEval(rawArgs[i + 1], env);
|
||||
i += 2;
|
||||
} else {
|
||||
children.push(sexpEval(rawArgs[i], env));
|
||||
children.push(sxEval(rawArgs[i], env));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
@@ -409,7 +409,7 @@
|
||||
local[p] = (p in kwargs) ? kwargs[p] : NIL;
|
||||
}
|
||||
if (comp.hasChildren) local["children"] = children;
|
||||
return sexpEval(comp.body, local);
|
||||
return sxEval(comp.body, local);
|
||||
}
|
||||
|
||||
// --- Special forms -------------------------------------------------------
|
||||
@@ -417,15 +417,15 @@
|
||||
var SPECIAL_FORMS = {};
|
||||
|
||||
SPECIAL_FORMS["if"] = function (expr, env) {
|
||||
var cond = sexpEval(expr[1], env);
|
||||
if (isSexpTruthy(cond)) return sexpEval(expr[2], env);
|
||||
return expr.length > 3 ? sexpEval(expr[3], env) : NIL;
|
||||
var cond = sxEval(expr[1], env);
|
||||
if (isSxTruthy(cond)) return sxEval(expr[2], env);
|
||||
return expr.length > 3 ? sxEval(expr[3], env) : NIL;
|
||||
};
|
||||
|
||||
SPECIAL_FORMS["when"] = function (expr, env) {
|
||||
if (!isSexpTruthy(sexpEval(expr[1], env))) return NIL;
|
||||
if (!isSxTruthy(sxEval(expr[1], env))) return NIL;
|
||||
var result = NIL;
|
||||
for (var i = 2; i < expr.length; i++) result = sexpEval(expr[i], env);
|
||||
for (var i = 2; i < expr.length; i++) result = sxEval(expr[i], env);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -437,28 +437,28 @@
|
||||
for (var i = 0; i < clauses.length; i++) {
|
||||
var test = clauses[i][0];
|
||||
if ((isSym(test) && (test.name === "else" || test.name === ":else")) ||
|
||||
(isKw(test) && test.name === "else")) return sexpEval(clauses[i][1], env);
|
||||
if (isSexpTruthy(sexpEval(test, env))) return sexpEval(clauses[i][1], env);
|
||||
(isKw(test) && test.name === "else")) return sxEval(clauses[i][1], env);
|
||||
if (isSxTruthy(sxEval(test, env))) return sxEval(clauses[i][1], env);
|
||||
}
|
||||
} else {
|
||||
// Clojure-style
|
||||
for (var j = 0; j < clauses.length - 1; j += 2) {
|
||||
var t = clauses[j];
|
||||
if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
|
||||
return sexpEval(clauses[j + 1], env);
|
||||
if (isSexpTruthy(sexpEval(t, env))) return sexpEval(clauses[j + 1], env);
|
||||
return sxEval(clauses[j + 1], env);
|
||||
if (isSxTruthy(sxEval(t, env))) return sxEval(clauses[j + 1], env);
|
||||
}
|
||||
}
|
||||
return NIL;
|
||||
};
|
||||
|
||||
SPECIAL_FORMS["case"] = function (expr, env) {
|
||||
var val = sexpEval(expr[1], env);
|
||||
var val = sxEval(expr[1], env);
|
||||
for (var i = 2; i < expr.length - 1; i += 2) {
|
||||
var t = expr[i];
|
||||
if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
|
||||
return sexpEval(expr[i + 1], env);
|
||||
if (val == sexpEval(t, env)) return sexpEval(expr[i + 1], env);
|
||||
return sxEval(expr[i + 1], env);
|
||||
if (val == sxEval(t, env)) return sxEval(expr[i + 1], env);
|
||||
}
|
||||
return NIL;
|
||||
};
|
||||
@@ -466,8 +466,8 @@
|
||||
SPECIAL_FORMS["and"] = function (expr, env) {
|
||||
var result = true;
|
||||
for (var i = 1; i < expr.length; i++) {
|
||||
result = sexpEval(expr[i], env);
|
||||
if (!isSexpTruthy(result)) return result;
|
||||
result = sxEval(expr[i], env);
|
||||
if (!isSxTruthy(result)) return result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -475,8 +475,8 @@
|
||||
SPECIAL_FORMS["or"] = function (expr, env) {
|
||||
var result = false;
|
||||
for (var i = 1; i < expr.length; i++) {
|
||||
result = sexpEval(expr[i], env);
|
||||
if (isSexpTruthy(result)) return result;
|
||||
result = sxEval(expr[i], env);
|
||||
if (isSxTruthy(result)) return result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -488,18 +488,18 @@
|
||||
// Scheme-style
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var vname = isSym(bindings[i][0]) ? bindings[i][0].name : bindings[i][0];
|
||||
local[vname] = sexpEval(bindings[i][1], local);
|
||||
local[vname] = sxEval(bindings[i][1], local);
|
||||
}
|
||||
} else {
|
||||
// Clojure-style
|
||||
for (var j = 0; j < bindings.length; j += 2) {
|
||||
var vn = isSym(bindings[j]) ? bindings[j].name : bindings[j];
|
||||
local[vn] = sexpEval(bindings[j + 1], local);
|
||||
local[vn] = sxEval(bindings[j + 1], local);
|
||||
}
|
||||
}
|
||||
}
|
||||
var result = NIL;
|
||||
for (var k = 2; k < expr.length; k++) result = sexpEval(expr[k], local);
|
||||
for (var k = 2; k < expr.length; k++) result = sxEval(expr[k], local);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
|
||||
SPECIAL_FORMS["define"] = function (expr, env) {
|
||||
var name = expr[1].name;
|
||||
var value = sexpEval(expr[2], env);
|
||||
var value = sxEval(expr[2], env);
|
||||
if (isLambda(value) && !value.name) value.name = name;
|
||||
env[name] = value;
|
||||
return value;
|
||||
@@ -541,29 +541,29 @@
|
||||
|
||||
SPECIAL_FORMS["begin"] = SPECIAL_FORMS["do"] = function (expr, env) {
|
||||
var result = NIL;
|
||||
for (var i = 1; i < expr.length; i++) result = sexpEval(expr[i], env);
|
||||
for (var i = 1; i < expr.length; i++) result = sxEval(expr[i], env);
|
||||
return result;
|
||||
};
|
||||
|
||||
SPECIAL_FORMS["quote"] = function (expr) { return expr[1]; };
|
||||
|
||||
SPECIAL_FORMS["set!"] = function (expr, env) {
|
||||
var v = sexpEval(expr[2], env);
|
||||
var v = sxEval(expr[2], env);
|
||||
env[expr[1].name] = v;
|
||||
return v;
|
||||
};
|
||||
|
||||
SPECIAL_FORMS["->"] = function (expr, env) {
|
||||
var result = sexpEval(expr[1], env);
|
||||
var result = sxEval(expr[1], env);
|
||||
for (var i = 2; i < expr.length; i++) {
|
||||
var form = expr[i];
|
||||
var fn, args;
|
||||
if (Array.isArray(form)) {
|
||||
fn = sexpEval(form[0], env);
|
||||
fn = sxEval(form[0], env);
|
||||
args = [result];
|
||||
for (var j = 1; j < form.length; j++) args.push(sexpEval(form[j], env));
|
||||
for (var j = 1; j < form.length; j++) args.push(sxEval(form[j], env));
|
||||
} else {
|
||||
fn = sexpEval(form, env);
|
||||
fn = sxEval(form, env);
|
||||
args = [result];
|
||||
}
|
||||
if (typeof fn === "function") result = fn.apply(null, args);
|
||||
@@ -578,48 +578,48 @@
|
||||
var HO_FORMS = {};
|
||||
|
||||
HO_FORMS["map"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
return coll.map(function (item) { return isLambda(fn) ? callLambda(fn, [item], env) : fn(item); });
|
||||
};
|
||||
|
||||
HO_FORMS["map-indexed"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
return coll.map(function (item, i) { return isLambda(fn) ? callLambda(fn, [i, item], env) : fn(i, item); });
|
||||
};
|
||||
|
||||
HO_FORMS["filter"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
return coll.filter(function (item) {
|
||||
var r = isLambda(fn) ? callLambda(fn, [item], env) : fn(item);
|
||||
return isSexpTruthy(r);
|
||||
return isSxTruthy(r);
|
||||
});
|
||||
};
|
||||
|
||||
HO_FORMS["reduce"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), acc = sexpEval(expr[2], env), coll = sexpEval(expr[3], env);
|
||||
var fn = sxEval(expr[1], env), acc = sxEval(expr[2], env), coll = sxEval(expr[3], env);
|
||||
for (var i = 0; i < coll.length; i++) acc = isLambda(fn) ? callLambda(fn, [acc, coll[i]], env) : fn(acc, coll[i]);
|
||||
return acc;
|
||||
};
|
||||
|
||||
HO_FORMS["some"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
for (var i = 0; i < coll.length; i++) {
|
||||
var r = isLambda(fn) ? callLambda(fn, [coll[i]], env) : fn(coll[i]);
|
||||
if (isSexpTruthy(r)) return r;
|
||||
if (isSxTruthy(r)) return r;
|
||||
}
|
||||
return NIL;
|
||||
};
|
||||
|
||||
HO_FORMS["every?"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
for (var i = 0; i < coll.length; i++) {
|
||||
if (!isSexpTruthy(isLambda(fn) ? callLambda(fn, [coll[i]], env) : fn(coll[i]))) return false;
|
||||
if (!isSxTruthy(isLambda(fn) ? callLambda(fn, [coll[i]], env) : fn(coll[i]))) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
HO_FORMS["for-each"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
for (var i = 0; i < coll.length; i++) isLambda(fn) ? callLambda(fn, [coll[i]], env) : fn(coll[i]);
|
||||
return NIL;
|
||||
};
|
||||
@@ -686,7 +686,7 @@
|
||||
if (typeof expr === "number") return document.createTextNode(String(expr));
|
||||
|
||||
// Symbol → evaluate then render
|
||||
if (isSym(expr)) return renderDOM(sexpEval(expr, env), env);
|
||||
if (isSym(expr)) return renderDOM(sxEval(expr, env), env);
|
||||
|
||||
// Keyword → text
|
||||
if (isKw(expr)) return document.createTextNode(expr.name);
|
||||
@@ -707,13 +707,13 @@
|
||||
var RENDER_FORMS = {};
|
||||
|
||||
RENDER_FORMS["if"] = function (expr, env) {
|
||||
var cond = sexpEval(expr[1], env);
|
||||
if (isSexpTruthy(cond)) return renderDOM(expr[2], env);
|
||||
var cond = sxEval(expr[1], env);
|
||||
if (isSxTruthy(cond)) return renderDOM(expr[2], env);
|
||||
return expr.length > 3 ? renderDOM(expr[3], env) : document.createDocumentFragment();
|
||||
};
|
||||
|
||||
RENDER_FORMS["when"] = function (expr, env) {
|
||||
if (!isSexpTruthy(sexpEval(expr[1], env))) return document.createDocumentFragment();
|
||||
if (!isSxTruthy(sxEval(expr[1], env))) return document.createDocumentFragment();
|
||||
var frag = document.createDocumentFragment();
|
||||
for (var i = 2; i < expr.length; i++) frag.appendChild(renderDOM(expr[i], env));
|
||||
return frag;
|
||||
@@ -727,14 +727,14 @@
|
||||
var test = clauses[i][0];
|
||||
if ((isSym(test) && (test.name === "else" || test.name === ":else")) ||
|
||||
(isKw(test) && test.name === "else")) return renderDOM(clauses[i][1], env);
|
||||
if (isSexpTruthy(sexpEval(test, env))) return renderDOM(clauses[i][1], env);
|
||||
if (isSxTruthy(sxEval(test, env))) return renderDOM(clauses[i][1], env);
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < clauses.length - 1; j += 2) {
|
||||
var t = clauses[j];
|
||||
if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
|
||||
return renderDOM(clauses[j + 1], env);
|
||||
if (isSexpTruthy(sexpEval(t, env))) return renderDOM(clauses[j + 1], env);
|
||||
if (isSxTruthy(sxEval(t, env))) return renderDOM(clauses[j + 1], env);
|
||||
}
|
||||
}
|
||||
return document.createDocumentFragment();
|
||||
@@ -745,11 +745,11 @@
|
||||
if (Array.isArray(bindings)) {
|
||||
if (bindings.length && Array.isArray(bindings[0])) {
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
local[isSym(bindings[i][0]) ? bindings[i][0].name : bindings[i][0]] = sexpEval(bindings[i][1], local);
|
||||
local[isSym(bindings[i][0]) ? bindings[i][0].name : bindings[i][0]] = sxEval(bindings[i][1], local);
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < bindings.length; j += 2) {
|
||||
local[isSym(bindings[j]) ? bindings[j].name : bindings[j]] = sexpEval(bindings[j + 1], local);
|
||||
local[isSym(bindings[j]) ? bindings[j].name : bindings[j]] = sxEval(bindings[j + 1], local);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -764,11 +764,11 @@
|
||||
return frag;
|
||||
};
|
||||
|
||||
RENDER_FORMS["define"] = function (expr, env) { sexpEval(expr, env); return document.createDocumentFragment(); };
|
||||
RENDER_FORMS["defcomp"] = function (expr, env) { sexpEval(expr, env); return document.createDocumentFragment(); };
|
||||
RENDER_FORMS["define"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
||||
RENDER_FORMS["defcomp"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
|
||||
|
||||
RENDER_FORMS["map"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
var frag = document.createDocumentFragment();
|
||||
for (var i = 0; i < coll.length; i++) {
|
||||
var val = isLambda(fn) ? renderLambdaDOM(fn, [coll[i]], env) : renderDOM(fn(coll[i]), env);
|
||||
@@ -778,7 +778,7 @@
|
||||
};
|
||||
|
||||
RENDER_FORMS["map-indexed"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
var frag = document.createDocumentFragment();
|
||||
for (var i = 0; i < coll.length; i++) {
|
||||
var val = isLambda(fn) ? renderLambdaDOM(fn, [i, coll[i]], env) : renderDOM(fn(i, coll[i]), env);
|
||||
@@ -788,12 +788,12 @@
|
||||
};
|
||||
|
||||
RENDER_FORMS["filter"] = function (expr, env) {
|
||||
var result = sexpEval(expr, env);
|
||||
var result = sxEval(expr, env);
|
||||
return renderDOM(result, env);
|
||||
};
|
||||
|
||||
RENDER_FORMS["for-each"] = function (expr, env) {
|
||||
var fn = sexpEval(expr[1], env), coll = sexpEval(expr[2], env);
|
||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||
var frag = document.createDocumentFragment();
|
||||
for (var i = 0; i < coll.length; i++) {
|
||||
var val = isLambda(fn) ? renderLambdaDOM(fn, [coll[i]], env) : renderDOM(fn(coll[i]), env);
|
||||
@@ -819,7 +819,7 @@
|
||||
var v = args[i + 1];
|
||||
kwargs[args[i].name] = (typeof v === "string" || typeof v === "number" ||
|
||||
typeof v === "boolean" || isNil(v) || isKw(v))
|
||||
? v : (isSym(v) ? sexpEval(v, env) : v);
|
||||
? v : (isSym(v) ? sxEval(v, env) : v);
|
||||
i += 2;
|
||||
} else {
|
||||
children.push(args[i]);
|
||||
@@ -850,7 +850,7 @@
|
||||
if (name === "raw!") {
|
||||
var frag = document.createDocumentFragment();
|
||||
for (var ri = 1; ri < expr.length; ri++) {
|
||||
var val = sexpEval(expr[ri], env);
|
||||
var val = sxEval(expr[ri], env);
|
||||
if (typeof val === "string") {
|
||||
var tpl = document.createElement("template");
|
||||
tpl.innerHTML = val;
|
||||
@@ -883,7 +883,7 @@
|
||||
var comp = env[name];
|
||||
if (isComponent(comp)) return renderComponentDOM(comp, expr.slice(1), env);
|
||||
// Unknown component — render a visible warning, don't crash
|
||||
console.warn("sexp.js: unknown component " + name);
|
||||
console.warn("sx.js: unknown component " + name);
|
||||
var warn = document.createElement("div");
|
||||
warn.setAttribute("style",
|
||||
"background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;" +
|
||||
@@ -893,11 +893,11 @@
|
||||
}
|
||||
|
||||
// Fallback: evaluate then render
|
||||
return renderDOM(sexpEval(expr, env), env);
|
||||
return renderDOM(sxEval(expr, env), env);
|
||||
}
|
||||
|
||||
// Lambda/list head → evaluate
|
||||
if (isLambda(head) || Array.isArray(head)) return renderDOM(sexpEval(expr, env), env);
|
||||
if (isLambda(head) || Array.isArray(head)) return renderDOM(sxEval(expr, env), env);
|
||||
|
||||
// Data list
|
||||
var dl = document.createDocumentFragment();
|
||||
@@ -915,7 +915,7 @@
|
||||
var arg = args[i];
|
||||
if (isKw(arg) && i + 1 < args.length) {
|
||||
var attrName = arg.name;
|
||||
var attrVal = sexpEval(args[i + 1], env);
|
||||
var attrVal = sxEval(args[i + 1], env);
|
||||
i += 2;
|
||||
if (isNil(attrVal) || attrVal === false) continue;
|
||||
if (BOOLEAN_ATTRS[attrName]) {
|
||||
@@ -949,7 +949,7 @@
|
||||
if (isRaw(expr)) return expr.html;
|
||||
if (typeof expr === "string") return escapeText(expr);
|
||||
if (typeof expr === "number") return escapeText(String(expr));
|
||||
if (isSym(expr)) return renderStr(sexpEval(expr, env), env);
|
||||
if (isSym(expr)) return renderStr(sxEval(expr, env), env);
|
||||
if (isKw(expr)) return escapeText(expr.name);
|
||||
if (Array.isArray(expr)) { if (!expr.length) return ""; return renderStrList(expr, env); }
|
||||
if (expr && typeof expr === "object") return "";
|
||||
@@ -968,7 +968,7 @@
|
||||
if (name === "raw!") {
|
||||
var ps = [];
|
||||
for (var ri = 1; ri < expr.length; ri++) {
|
||||
var v = sexpEval(expr[ri], env);
|
||||
var v = sxEval(expr[ri], env);
|
||||
if (isRaw(v)) ps.push(v.html);
|
||||
else if (typeof v === "string") ps.push(v);
|
||||
else if (!isNil(v)) ps.push(String(v));
|
||||
@@ -981,12 +981,12 @@
|
||||
return fs.join("");
|
||||
}
|
||||
if (name === "if") {
|
||||
return isSexpTruthy(sexpEval(expr[1], env))
|
||||
return isSxTruthy(sxEval(expr[1], env))
|
||||
? renderStr(expr[2], env)
|
||||
: (expr.length > 3 ? renderStr(expr[3], env) : "");
|
||||
}
|
||||
if (name === "when") {
|
||||
if (!isSexpTruthy(sexpEval(expr[1], env))) return "";
|
||||
if (!isSxTruthy(sxEval(expr[1], env))) return "";
|
||||
var ws = [];
|
||||
for (var wi = 2; wi < expr.length; wi++) ws.push(renderStr(expr[wi], env));
|
||||
return ws.join("");
|
||||
@@ -996,11 +996,11 @@
|
||||
if (Array.isArray(bindings)) {
|
||||
if (bindings.length && Array.isArray(bindings[0])) {
|
||||
for (var li = 0; li < bindings.length; li++) {
|
||||
local[isSym(bindings[li][0]) ? bindings[li][0].name : bindings[li][0]] = sexpEval(bindings[li][1], local);
|
||||
local[isSym(bindings[li][0]) ? bindings[li][0].name : bindings[li][0]] = sxEval(bindings[li][1], local);
|
||||
}
|
||||
} else {
|
||||
for (var lj = 0; lj < bindings.length; lj += 2) {
|
||||
local[isSym(bindings[lj]) ? bindings[lj].name : bindings[lj]] = sexpEval(bindings[lj + 1], local);
|
||||
local[isSym(bindings[lj]) ? bindings[lj].name : bindings[lj]] = sxEval(bindings[lj + 1], local);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1013,11 +1013,11 @@
|
||||
for (var bi = 1; bi < expr.length; bi++) bs.push(renderStr(expr[bi], env));
|
||||
return bs.join("");
|
||||
}
|
||||
if (name === "define" || name === "defcomp") { sexpEval(expr, env); return ""; }
|
||||
if (name === "define" || name === "defcomp") { sxEval(expr, env); return ""; }
|
||||
|
||||
// Higher-order forms — render-aware (lambda bodies may contain HTML/components)
|
||||
if (name === "map") {
|
||||
var mapFn = sexpEval(expr[1], env), mapColl = sexpEval(expr[2], env);
|
||||
var mapFn = sxEval(expr[1], env), mapColl = sxEval(expr[2], env);
|
||||
if (!Array.isArray(mapColl)) return "";
|
||||
var mapParts = [];
|
||||
for (var mi = 0; mi < mapColl.length; mi++) {
|
||||
@@ -1027,7 +1027,7 @@
|
||||
return mapParts.join("");
|
||||
}
|
||||
if (name === "map-indexed") {
|
||||
var mixFn = sexpEval(expr[1], env), mixColl = sexpEval(expr[2], env);
|
||||
var mixFn = sxEval(expr[1], env), mixColl = sxEval(expr[2], env);
|
||||
if (!Array.isArray(mixColl)) return "";
|
||||
var mixParts = [];
|
||||
for (var mxi = 0; mxi < mixColl.length; mxi++) {
|
||||
@@ -1037,12 +1037,12 @@
|
||||
return mixParts.join("");
|
||||
}
|
||||
if (name === "filter") {
|
||||
var filtFn = sexpEval(expr[1], env), filtColl = sexpEval(expr[2], env);
|
||||
var filtFn = sxEval(expr[1], env), filtColl = sxEval(expr[2], env);
|
||||
if (!Array.isArray(filtColl)) return "";
|
||||
var filtParts = [];
|
||||
for (var fli = 0; fli < filtColl.length; fli++) {
|
||||
var keep = isLambda(filtFn) ? callLambda(filtFn, [filtColl[fli]], env) : filtFn(filtColl[fli]);
|
||||
if (isSexpTruthy(keep)) filtParts.push(renderStr(filtColl[fli], env));
|
||||
if (isSxTruthy(keep)) filtParts.push(renderStr(filtColl[fli], env));
|
||||
}
|
||||
return filtParts.join("");
|
||||
}
|
||||
@@ -1053,13 +1053,13 @@
|
||||
var comp = env[name];
|
||||
if (isComponent(comp)) return renderStrComponent(comp, expr.slice(1), env);
|
||||
// Unknown component — return visible warning
|
||||
console.warn("sexp.js: unknown component " + name);
|
||||
console.warn("sx.js: unknown component " + name);
|
||||
return '<div style="background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;' +
|
||||
'padding:4px 8px;margin:2px;border-radius:4px;font-size:12px;font-family:monospace">' +
|
||||
'Unknown component: ' + escapeText(name) + '</div>';
|
||||
}
|
||||
|
||||
return renderStr(sexpEval(expr, env), env);
|
||||
return renderStr(sxEval(expr, env), env);
|
||||
}
|
||||
|
||||
function renderStrElement(tag, args, env) {
|
||||
@@ -1067,7 +1067,7 @@
|
||||
var i = 0;
|
||||
while (i < args.length) {
|
||||
if (isKw(args[i]) && i + 1 < args.length) {
|
||||
var aname = args[i].name, aval = sexpEval(args[i + 1], env);
|
||||
var aname = args[i].name, aval = sxEval(args[i + 1], env);
|
||||
i += 2;
|
||||
if (isNil(aval) || aval === false) continue;
|
||||
if (BOOLEAN_ATTRS[aname]) { if (aval) attrs.push(" " + aname); }
|
||||
@@ -1099,7 +1099,7 @@
|
||||
var v = args[i + 1];
|
||||
kwargs[args[i].name] = (typeof v === "string" || typeof v === "number" ||
|
||||
typeof v === "boolean" || isNil(v) || isKw(v))
|
||||
? v : (isSym(v) ? sexpEval(v, env) : v);
|
||||
? v : (isSym(v) ? sxEval(v, env) : v);
|
||||
i += 2;
|
||||
} else { children.push(args[i]); i++; }
|
||||
}
|
||||
@@ -1134,7 +1134,7 @@
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Convert snake_case kwargs to kebab-case for sexp conventions. */
|
||||
/** Convert snake_case kwargs to kebab-case for sx conventions. */
|
||||
function toKebab(s) { return s.replace(/_/g, "-"); }
|
||||
|
||||
// =========================================================================
|
||||
@@ -1187,7 +1187,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
var Sexp = {
|
||||
var Sx = {
|
||||
// Types
|
||||
NIL: NIL,
|
||||
Symbol: Symbol,
|
||||
@@ -1198,7 +1198,7 @@
|
||||
parseAll: parseAll,
|
||||
|
||||
// Evaluator
|
||||
eval: function (expr, env) { return sexpEval(expr, env || _componentEnv); },
|
||||
eval: function (expr, env) { return sxEval(expr, env || _componentEnv); },
|
||||
|
||||
// DOM Renderer
|
||||
render: function (exprOrText, extraEnv) {
|
||||
@@ -1216,7 +1216,7 @@
|
||||
|
||||
/**
|
||||
* Render a named component with keyword args (Python-style API).
|
||||
* Sexp.renderComponent("card", {title: "Hi"})
|
||||
* Sx.renderComponent("card", {title: "Hi"})
|
||||
*/
|
||||
renderComponent: function (name, kwargs, extraEnv) {
|
||||
var fullName = name.charAt(0) === "~" ? name : "~" + name;
|
||||
@@ -1237,52 +1237,52 @@
|
||||
// Component management
|
||||
loadComponents: function (text) {
|
||||
var exprs = parseAll(text);
|
||||
for (var i = 0; i < exprs.length; i++) sexpEval(exprs[i], _componentEnv);
|
||||
for (var i = 0; i < exprs.length; i++) sxEval(exprs[i], _componentEnv);
|
||||
},
|
||||
|
||||
getEnv: function () { return _componentEnv; },
|
||||
|
||||
// Utility
|
||||
isTruthy: isSexpTruthy,
|
||||
isTruthy: isSxTruthy,
|
||||
isNil: isNil,
|
||||
|
||||
/**
|
||||
* Mount a sexp expression into a DOM element, replacing its contents.
|
||||
* Sexp.mount(el, '(~card :title "Hi")')
|
||||
* Sexp.mount("#target", '(~card :title "Hi")')
|
||||
* Sexp.mount(el, '(~card :title name)', {name: "Jo"})
|
||||
* Mount a sx expression into a DOM element, replacing its contents.
|
||||
* Sx.mount(el, '(~card :title "Hi")')
|
||||
* Sx.mount("#target", '(~card :title "Hi")')
|
||||
* Sx.mount(el, '(~card :title name)', {name: "Jo"})
|
||||
*/
|
||||
mount: function (target, exprOrText, extraEnv) {
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
if (!el) return;
|
||||
var node = Sexp.render(exprOrText, extraEnv);
|
||||
var node = Sx.render(exprOrText, extraEnv);
|
||||
el.textContent = "";
|
||||
el.appendChild(node);
|
||||
// Auto-hoist head elements (meta, title, link, script[ld+json]) to <head>
|
||||
_hoistHeadElements(el);
|
||||
// Process sx- attributes and hydrate the newly mounted content
|
||||
if (typeof SxEngine !== "undefined") SxEngine.process(el);
|
||||
Sexp.hydrate(el);
|
||||
Sx.hydrate(el);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process all <script type="text/sexp"> tags in the document.
|
||||
* Process all <script type="text/sx"> tags in the document.
|
||||
* Tags with data-components load component definitions.
|
||||
* Tags with data-mount="<selector>" render into that element.
|
||||
*/
|
||||
processScripts: function (root) {
|
||||
var scripts = (root || document).querySelectorAll('script[type="text/sexp"]');
|
||||
var scripts = (root || document).querySelectorAll('script[type="text/sx"]');
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var s = scripts[i];
|
||||
if (s._sexpProcessed) continue;
|
||||
s._sexpProcessed = true;
|
||||
if (s._sxProcessed) continue;
|
||||
s._sxProcessed = true;
|
||||
|
||||
var text = s.textContent;
|
||||
if (!text || !text.trim()) continue;
|
||||
|
||||
// data-components: load as component definitions
|
||||
if (s.hasAttribute("data-components")) {
|
||||
Sexp.loadComponents(text);
|
||||
Sx.loadComponents(text);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1290,32 +1290,32 @@
|
||||
var mountSel = s.getAttribute("data-mount");
|
||||
if (mountSel) {
|
||||
var target = document.querySelector(mountSel);
|
||||
if (target) Sexp.mount(target, text);
|
||||
if (target) Sx.mount(target, text);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default: load as components
|
||||
Sexp.loadComponents(text);
|
||||
Sx.loadComponents(text);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind client-side sexp rendering to elements with data-sexp-* attrs.
|
||||
* Bind client-side sx rendering to elements with data-sx-* attrs.
|
||||
*
|
||||
* Pattern:
|
||||
* <div data-sexp="(~card :title title)" data-sexp-env='{"title":"Hi"}'>
|
||||
* <div data-sx="(~card :title title)" data-sx-env='{"title":"Hi"}'>
|
||||
* <!-- server-rendered HTML (hydration target) -->
|
||||
* </div>
|
||||
*
|
||||
* Call Sexp.update(el, {title: "New"}) to re-render with new data.
|
||||
* Call Sx.update(el, {title: "New"}) to re-render with new data.
|
||||
*/
|
||||
update: function (target, newEnv) {
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
if (!el) return;
|
||||
var source = el.getAttribute("data-sexp");
|
||||
var source = el.getAttribute("data-sx");
|
||||
if (!source) return;
|
||||
var baseEnv = {};
|
||||
var envAttr = el.getAttribute("data-sexp-env");
|
||||
var envAttr = el.getAttribute("data-sx-env");
|
||||
if (envAttr) {
|
||||
try { baseEnv = JSON.parse(envAttr); } catch (e) { /* ignore */ }
|
||||
}
|
||||
@@ -1325,31 +1325,31 @@
|
||||
el.appendChild(node);
|
||||
if (newEnv) {
|
||||
merge(baseEnv, newEnv);
|
||||
el.setAttribute("data-sexp-env", JSON.stringify(baseEnv));
|
||||
el.setAttribute("data-sx-env", JSON.stringify(baseEnv));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find all [data-sexp] elements within root and render them.
|
||||
* Useful after HTMX swaps bring in new sexp-enabled elements.
|
||||
* Find all [data-sx] elements within root and render them.
|
||||
* Useful after HTMX swaps bring in new sx-enabled elements.
|
||||
*/
|
||||
hydrate: function (root) {
|
||||
var els = (root || document).querySelectorAll("[data-sexp]");
|
||||
var els = (root || document).querySelectorAll("[data-sx]");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
if (els[i]._sexpHydrated) continue;
|
||||
els[i]._sexpHydrated = true;
|
||||
Sexp.update(els[i]);
|
||||
if (els[i]._sxHydrated) continue;
|
||||
els[i]._sxHydrated = true;
|
||||
Sx.update(els[i]);
|
||||
}
|
||||
},
|
||||
|
||||
// For testing
|
||||
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
|
||||
_eval: sexpEval,
|
||||
_eval: sxEval,
|
||||
_renderStr: renderStr,
|
||||
_renderDOM: renderDOM,
|
||||
};
|
||||
|
||||
global.Sexp = Sexp;
|
||||
global.Sx = Sx;
|
||||
|
||||
// =========================================================================
|
||||
// SxEngine — native fetch/swap/history engine (replaces HTMX)
|
||||
@@ -1573,12 +1573,12 @@
|
||||
return resp.text().then(function (text) {
|
||||
dispatch(el, "sx:afterRequest", { response: resp });
|
||||
|
||||
// Check for text/sexp content type
|
||||
// Check for text/sx content type
|
||||
var ct = resp.headers.get("Content-Type") || "";
|
||||
if (ct.indexOf("text/sexp") >= 0) {
|
||||
try { text = Sexp.renderToString(text); }
|
||||
if (ct.indexOf("text/sx") >= 0) {
|
||||
try { text = Sx.renderToString(text); }
|
||||
catch (err) {
|
||||
console.error("sexp.js render error:", err);
|
||||
console.error("sx.js render error:", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1594,8 +1594,8 @@
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(text, "text/html");
|
||||
|
||||
// Process any sexp script blocks in the response (e.g. cross-domain component defs)
|
||||
Sexp.processScripts(doc);
|
||||
// Process any sx script blocks in the response (e.g. cross-domain component defs)
|
||||
Sx.processScripts(doc);
|
||||
|
||||
// OOB processing: extract elements with sx-swap-oob
|
||||
var oobs = doc.querySelectorAll("[sx-swap-oob]");
|
||||
@@ -1679,8 +1679,8 @@
|
||||
tgt.insertAdjacentHTML("afterend", html);
|
||||
parent.removeChild(tgt);
|
||||
// Process parent to catch all newly inserted siblings
|
||||
Sexp.processScripts(parent);
|
||||
Sexp.hydrate(parent);
|
||||
Sx.processScripts(parent);
|
||||
Sx.hydrate(parent);
|
||||
SxEngine.process(parent);
|
||||
return; // early return — afterSwap handling done inline
|
||||
case "afterend":
|
||||
@@ -1701,8 +1701,8 @@
|
||||
default:
|
||||
target.innerHTML = html;
|
||||
}
|
||||
Sexp.processScripts(target);
|
||||
Sexp.hydrate(target);
|
||||
Sx.processScripts(target);
|
||||
Sx.hydrate(target);
|
||||
SxEngine.process(target);
|
||||
}
|
||||
|
||||
@@ -1863,8 +1863,8 @@
|
||||
var main = document.getElementById("main-panel");
|
||||
if (main) {
|
||||
main.innerHTML = _historyCache[url];
|
||||
Sexp.processScripts(main);
|
||||
Sexp.hydrate(main);
|
||||
Sx.processScripts(main);
|
||||
Sx.hydrate(main);
|
||||
SxEngine.process(main);
|
||||
dispatch(document.body, "sx:afterSettle", { target: main });
|
||||
return;
|
||||
@@ -1885,9 +1885,9 @@
|
||||
return resp.text();
|
||||
}).then(function (text) {
|
||||
var ct = "";
|
||||
// Response content-type is lost here, check for sexp
|
||||
// Response content-type is lost here, check for sx
|
||||
if (text.charAt(0) === "(") {
|
||||
try { text = Sexp.renderToString(text); } catch (e) { /* not sexp */ }
|
||||
try { text = Sx.renderToString(text); } catch (e) { /* not sx */ }
|
||||
}
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(text, "text/html");
|
||||
@@ -1895,8 +1895,8 @@
|
||||
var main = document.getElementById("main-panel");
|
||||
if (main && newMain) {
|
||||
main.innerHTML = newMain.innerHTML;
|
||||
Sexp.processScripts(main);
|
||||
Sexp.hydrate(main);
|
||||
Sx.processScripts(main);
|
||||
Sx.hydrate(main);
|
||||
SxEngine.process(main);
|
||||
dispatch(document.body, "sx:afterSettle", { target: main });
|
||||
}
|
||||
@@ -1975,13 +1975,13 @@
|
||||
// Auto-init in browser
|
||||
// =========================================================================
|
||||
|
||||
Sexp.VERSION = "2026-03-01a";
|
||||
Sx.VERSION = "2026-03-01a";
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
var init = function () {
|
||||
console.log("[sexp.js] v" + Sexp.VERSION + " init");
|
||||
Sexp.processScripts();
|
||||
Sexp.hydrate();
|
||||
console.log("[sx.js] v" + Sx.VERSION + " init");
|
||||
Sx.processScripts();
|
||||
Sx.hydrate();
|
||||
SxEngine.process();
|
||||
};
|
||||
if (document.readyState === "loading") {
|
||||
Reference in New Issue
Block a user