diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 8966cf7..7b969d9 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-11T21:11:04Z"; + var SX_VERSION = "2026-03-11T23:22:03Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -729,10 +729,10 @@ var args = rest(expr); return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() { var name = symbolName(head); - return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { + return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "deftype")) ? sfDeftype(args, env) : (isSxTruthy((name == "defeffect")) ? sfDefeffect(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { var mac = envGet(env, name); return makeThunk(expandMacro(mac, args, env), env); -})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))))); +})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))))))); })() : evalCall(head, args, env))); })(); }; @@ -888,11 +888,22 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai // sf-define var sfDefine = function(args, env) { return (function() { var nameSym = first(args); - var value = trampoline(evalExpr(nth(args, 1), env)); + var hasEffects = (isSxTruthy((len(args) >= 4)) && isSxTruthy((typeOf(nth(args, 1)) == "keyword")) && (keywordName(nth(args, 1)) == "effects")); + var valIdx = (isSxTruthy((isSxTruthy((len(args) >= 4)) && isSxTruthy((typeOf(nth(args, 1)) == "keyword")) && (keywordName(nth(args, 1)) == "effects"))) ? 3 : 1); + var value = trampoline(evalExpr(nth(args, valIdx), env)); if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) { value.name = symbolName(nameSym); } envSet(env, symbolName(nameSym), value); + if (isSxTruthy(hasEffects)) { + (function() { + var effectsRaw = nth(args, 2); + var effectList = (isSxTruthy((typeOf(effectsRaw) == "list")) ? map(function(e) { return (isSxTruthy((typeOf(e) == "symbol")) ? symbolName(e) : (String(e))); }, effectsRaw) : [(String(effectsRaw))]); + var effectAnns = (isSxTruthy(envHas(env, "*effect-annotations*")) ? envGet(env, "*effect-annotations*") : {}); + effectAnns[symbolName(nameSym)] = effectList; + return envSet(env, "*effect-annotations*", effectAnns); +})(); +} return value; })(); }; @@ -909,8 +920,17 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var affinity = defcompKwarg(args, "affinity", "auto"); return (function() { var comp = makeComponent(compName, params, hasChildren, body, env, affinity); + var effects = defcompKwarg(args, "effects", NIL); if (isSxTruthy((isSxTruthy(!isSxTruthy(isNil(paramTypes))) && !isSxTruthy(isEmpty(keys(paramTypes)))))) { componentSetParamTypes_b(comp, paramTypes); +} + if (isSxTruthy(!isSxTruthy(isNil(effects)))) { + (function() { + var effectList = (isSxTruthy((typeOf(effects) == "list")) ? map(function(e) { return (isSxTruthy((typeOf(e) == "symbol")) ? symbolName(e) : (String(e))); }, effects) : [(String(effects))]); + var effectAnns = (isSxTruthy(envHas(env, "*effect-annotations*")) ? envGet(env, "*effect-annotations*") : {}); + effectAnns[symbolName(nameSym)] = effectList; + return envSet(env, "*effect-annotations*", effectAnns); +})(); } envSet(env, symbolName(nameSym), comp); return comp; @@ -997,6 +1017,45 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai return value; })(); }; + // make-type-def + var makeTypeDef = function(name, params, body) { return {"name": name, "params": params, "body": body}; }; + + // normalize-type-body + var normalizeTypeBody = function(body) { return (isSxTruthy(isNil(body)) ? "nil" : (isSxTruthy((typeOf(body) == "symbol")) ? symbolName(body) : (isSxTruthy((typeOf(body) == "string")) ? body : (isSxTruthy((typeOf(body) == "keyword")) ? keywordName(body) : (isSxTruthy((typeOf(body) == "dict")) ? mapDict(function(k, v) { return normalizeTypeBody(v); }, body) : (isSxTruthy((typeOf(body) == "list")) ? (isSxTruthy(isEmpty(body)) ? "any" : (function() { + var head = first(body); + return (function() { + var headName = (isSxTruthy((typeOf(head) == "symbol")) ? symbolName(head) : (String(head))); + return (isSxTruthy((headName == "union")) ? cons("or", map(normalizeTypeBody, rest(body))) : cons(headName, map(normalizeTypeBody, rest(body)))); +})(); +})()) : (String(body)))))))); }; + + // sf-deftype + var sfDeftype = function(args, env) { return (function() { + var nameOrForm = first(args); + var bodyExpr = nth(args, 1); + var typeName = NIL; + var typeParams = []; + (isSxTruthy((typeOf(nameOrForm) == "symbol")) ? (typeName = symbolName(nameOrForm)) : (isSxTruthy((typeOf(nameOrForm) == "list")) ? ((typeName = symbolName(first(nameOrForm))), (typeParams = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : (String(p))); }, rest(nameOrForm)))) : NIL)); + return (function() { + var body = normalizeTypeBody(bodyExpr); + var registry = (isSxTruthy(envHas(env, "*type-registry*")) ? envGet(env, "*type-registry*") : {}); + registry[typeName] = makeTypeDef(typeName, typeParams, body); + envSet(env, "*type-registry*", registry); + return NIL; +})(); +})(); }; + + // sf-defeffect + var sfDefeffect = function(args, env) { return (function() { + var effectName = (isSxTruthy((typeOf(first(args)) == "symbol")) ? symbolName(first(args)) : (String(first(args)))); + var registry = (isSxTruthy(envHas(env, "*effect-registry*")) ? envGet(env, "*effect-registry*") : []); + if (isSxTruthy(!isSxTruthy(contains(registry, effectName)))) { + registry.push(effectName); +} + envSet(env, "*effect-registry*", registry); + return NIL; +})(); }; + // sf-begin var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); }; @@ -1156,7 +1215,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var BOOLEAN_ATTRS = ["async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "hidden", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "selected"]; // definition-form? - var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler")); }; + var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "deftype"), (name == "defeffect")); }; // parse-element-args var parseElementArgs = function(args, env) { return (function() { @@ -1366,7 +1425,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m = var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); return escapeHtml((String(val))); })(); }; // RENDER_HTML_FORMS - var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"]; + var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each"]; // render-html-form? var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); }; @@ -1574,7 +1633,7 @@ return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if })(); }; // 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"]; + 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"]; // HO_FORM_NAMES var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"]; @@ -1645,7 +1704,7 @@ return result; }, args); return append_b(results, aser(lambdaBody(f), local)); })() : invoke(f, item)); } } return (isSxTruthy(isEmpty(results)) ? NIL : results); -})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env))))))))))))))); +})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"), (name == "deftype"), (name == "defeffect"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env))))))))))))))); })(); }; // eval-case-aser @@ -3999,20 +4058,12 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { function domCallMethod() { var obj = arguments[0], method = arguments[1]; var args = Array.prototype.slice.call(arguments, 2); + console.log("[sx] dom-call-method:", obj, method, args); if (obj && typeof obj[method] === 'function') { try { return obj[method].apply(obj, args); } catch(e) { console.error("[sx] dom-call-method error:", e); return NIL; } } - return NIL; - } - // Post a message to an iframe's contentWindow without exposing the cross-origin - // Window object to the SX evaluator (which would trigger _thunk access errors). - function domPostMessage(iframe, msg, origin) { - try { - if (iframe && iframe.contentWindow) { - iframe.contentWindow.postMessage(msg, origin || '*'); - } - } catch(e) { console.error("[sx] domPostMessage error:", e); } + console.warn("[sx] dom-call-method: method not found or obj null", obj, method); return NIL; } @@ -5221,7 +5272,6 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { PRIMITIVES["dom-get-prop"] = domGetProp; PRIMITIVES["dom-set-prop"] = domSetProp; PRIMITIVES["dom-call-method"] = domCallMethod; - PRIMITIVES["dom-post-message"] = domPostMessage; PRIMITIVES["stop-propagation"] = stopPropagation_; PRIMITIVES["error-message"] = errorMessage; PRIMITIVES["schedule-idle"] = scheduleIdle; diff --git a/shared/sx/ref/adapter-async.sx b/shared/sx/ref/adapter-async.sx index cfcce1b..0922111 100644 --- a/shared/sx/ref/adapter-async.sx +++ b/shared/sx/ref/adapter-async.sx @@ -40,7 +40,7 @@ ;; Async HTML renderer ;; -------------------------------------------------------------------------- -(define-async async-render +(define-async async-render :effects [render io] (fn (expr (env :as dict) ctx) (case (type-of expr) "nil" "" @@ -56,7 +56,7 @@ :else (escape-html (str expr))))) -(define-async async-render-list +(define-async async-render-list :effects [render io] (fn (expr (env :as dict) ctx) (let ((head (first expr))) (if (not (= (type-of head) "symbol")) @@ -138,7 +138,7 @@ ;; async-render-raw — handle (raw! ...) in async context ;; -------------------------------------------------------------------------- -(define-async async-render-raw +(define-async async-render-raw :effects [render io] (fn ((args :as list) (env :as dict) ctx) (let ((parts (list))) (for-each @@ -157,7 +157,7 @@ ;; async-render-element — render an HTML element with async arg evaluation ;; -------------------------------------------------------------------------- -(define-async async-render-element +(define-async async-render-element :effects [render io] (fn ((tag :as string) (args :as list) (env :as dict) ctx) (let ((attrs (dict)) (children (list))) @@ -185,7 +185,7 @@ ;; Uses for-each + mutable state instead of reduce, because the bootstrapper ;; compiles inline for-each lambdas as for loops (which can contain await). -(define-async async-parse-element-args +(define-async async-parse-element-args :effects [render io] (fn ((args :as list) (attrs :as dict) (children :as list) (env :as dict) ctx) (let ((skip false) (i 0)) @@ -210,7 +210,7 @@ ;; async-render-component — expand and render a component asynchronously ;; -------------------------------------------------------------------------- -(define-async async-render-component +(define-async async-render-component :effects [render io] (fn ((comp :as component) (args :as list) (env :as dict) ctx) (let ((kwargs (dict)) (children (list))) @@ -232,7 +232,7 @@ ;; async-render-island — SSR render of reactive island with hydration markers ;; -------------------------------------------------------------------------- -(define-async async-render-island +(define-async async-render-island :effects [render io] (fn ((island :as island) (args :as list) (env :as dict) ctx) (let ((kwargs (dict)) (children (list))) @@ -261,7 +261,7 @@ ;; async-render-lambda — render lambda body in HTML context ;; -------------------------------------------------------------------------- -(define-async async-render-lambda +(define-async async-render-lambda :effects [render io] (fn ((f :as lambda) (args :as list) (env :as dict) ctx) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed @@ -274,7 +274,7 @@ ;; async-parse-kw-args — parse keyword args and children with async eval ;; -------------------------------------------------------------------------- -(define-async async-parse-kw-args +(define-async async-parse-kw-args :effects [render io] (fn ((args :as list) (kwargs :as dict) (children :as list) (env :as dict) ctx) (let ((skip false) (i 0)) @@ -300,7 +300,7 @@ ;; -------------------------------------------------------------------------- ;; Bootstrapper emits this as: [await async_render(x, env, ctx) for x in exprs] -(define-async async-map-render +(define-async async-map-render :effects [render io] (fn ((exprs :as list) (env :as dict) ctx) (let ((results (list))) (for-each @@ -316,9 +316,10 @@ (define ASYNC_RENDER_FORMS (list "if" "when" "cond" "case" "let" "let*" "begin" "do" "define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler" + "deftype" "defeffect" "map" "map-indexed" "filter" "for-each")) -(define async-render-form? +(define async-render-form? :effects [] (fn ((name :as string)) (contains? ASYNC_RENDER_FORMS name))) @@ -330,7 +331,7 @@ ;; Uses cond-scheme? from eval.sx (the FIXED version with every? check) ;; and eval-cond from render.sx for correct scheme/clojure classification. -(define-async dispatch-async-render-form +(define-async dispatch-async-render-form :effects [render io] (fn ((name :as string) expr (env :as dict) ctx) (cond ;; if @@ -406,7 +407,7 @@ ;; async-render-cond-scheme — scheme-style cond for render mode ;; -------------------------------------------------------------------------- -(define-async async-render-cond-scheme +(define-async async-render-cond-scheme :effects [render io] (fn ((clauses :as list) (env :as dict) ctx) (if (empty? clauses) "" @@ -428,7 +429,7 @@ ;; async-render-cond-clojure — clojure-style cond for render mode ;; -------------------------------------------------------------------------- -(define-async async-render-cond-clojure +(define-async async-render-cond-clojure :effects [render io] (fn ((clauses :as list) (env :as dict) ctx) (if (< (len clauses) 2) "" @@ -448,7 +449,7 @@ ;; async-process-bindings — evaluate let-bindings asynchronously ;; -------------------------------------------------------------------------- -(define-async async-process-bindings +(define-async async-process-bindings :effects [render io] (fn (bindings (env :as dict) ctx) ;; env-extend (not merge) — Env is not a dict subclass, so merge() ;; returns an empty dict, losing all parent scope bindings. @@ -469,7 +470,7 @@ local))) -(define-async async-process-bindings-flat +(define-async async-process-bindings-flat :effects [render io] (fn ((bindings :as list) (local :as dict) ctx) (let ((skip false) (i 0)) @@ -494,7 +495,7 @@ ;; async-map-fn-render — map a lambda/callable over collection for render ;; -------------------------------------------------------------------------- -(define-async async-map-fn-render +(define-async async-map-fn-render :effects [render io] (fn (f (coll :as list) (env :as dict) ctx) (let ((results (list))) (for-each @@ -511,7 +512,7 @@ ;; async-map-indexed-fn-render — map-indexed variant for render ;; -------------------------------------------------------------------------- -(define-async async-map-indexed-fn-render +(define-async async-map-indexed-fn-render :effects [render io] (fn (f (coll :as list) (env :as dict) ctx) (let ((results (list)) (i 0)) @@ -530,7 +531,7 @@ ;; async-invoke — call a native callable, await if coroutine ;; -------------------------------------------------------------------------- -(define-async async-invoke +(define-async async-invoke :effects [io] (fn (f &rest args) (let ((r (apply f args))) (if (async-coroutine? r) @@ -542,7 +543,7 @@ ;; Async SX wire format (aser) ;; ========================================================================== -(define-async async-aser +(define-async async-aser :effects [render io] (fn (expr (env :as dict) ctx) (case (type-of expr) "number" expr @@ -572,7 +573,7 @@ :else expr))) -(define-async async-aser-dict +(define-async async-aser-dict :effects [render io] (fn ((expr :as dict) (env :as dict) ctx) (let ((result (dict))) (for-each @@ -586,7 +587,7 @@ ;; async-aser-list — dispatch on list head for aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-list +(define-async async-aser-list :effects [render io] (fn (expr (env :as dict) ctx) (let ((head (first expr)) (args (rest expr))) @@ -665,7 +666,7 @@ ;; async-aser-eval-call — evaluate a function call fully in aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-eval-call +(define-async async-aser-eval-call :effects [render io] (fn (head (args :as list) (env :as dict) ctx) (let ((f (async-eval head env ctx)) (evaled-args (async-eval-args args env ctx))) @@ -693,7 +694,7 @@ ;; async-eval-args — evaluate a list of args asynchronously ;; -------------------------------------------------------------------------- -(define-async async-eval-args +(define-async async-eval-args :effects [io] (fn ((args :as list) (env :as dict) ctx) (let ((results (list))) (for-each @@ -706,7 +707,7 @@ ;; async-aser-map-list — aser each element of a list ;; -------------------------------------------------------------------------- -(define-async async-aser-map-list +(define-async async-aser-map-list :effects [render io] (fn ((exprs :as list) (env :as dict) ctx) (let ((results (list))) (for-each @@ -719,7 +720,7 @@ ;; async-aser-fragment — serialize (<> child1 child2 ...) in aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-fragment +(define-async async-aser-fragment :effects [render io] (fn ((children :as list) (env :as dict) ctx) (let ((parts (list))) (for-each @@ -743,7 +744,7 @@ ;; async-aser-component — expand component server-side in aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-component +(define-async async-aser-component :effects [render io] (fn ((comp :as component) (args :as list) (env :as dict) ctx) (let ((kwargs (dict)) (children (list))) @@ -775,7 +776,7 @@ ;; async-parse-aser-kw-args — parse keyword args for aser mode ;; -------------------------------------------------------------------------- -(define-async async-parse-aser-kw-args +(define-async async-parse-aser-kw-args :effects [render io] (fn ((args :as list) (kwargs :as dict) (children :as list) (env :as dict) ctx) (let ((skip false) (i 0)) @@ -800,7 +801,7 @@ ;; async-aser-call — serialize an SX call (tag or component) in aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-call +(define-async async-aser-call :effects [render io] (fn ((name :as string) (args :as list) (env :as dict) ctx) (let ((token (if (or (= name "svg") (= name "math")) (svg-context-set! true) @@ -853,12 +854,13 @@ "let" "let*" "lambda" "fn" "define" "defcomp" "defmacro" "defstyle" "defhandler" "defpage" "defquery" "defaction" - "begin" "do" "quote" "->" "set!" "defisland")) + "begin" "do" "quote" "->" "set!" "defisland" + "deftype" "defeffect")) (define ASYNC_ASER_HO_NAMES (list "map" "map-indexed" "filter" "for-each")) -(define async-aser-form? +(define async-aser-form? :effects [] (fn ((name :as string)) (or (contains? ASYNC_ASER_FORM_NAMES name) (contains? ASYNC_ASER_HO_NAMES name)))) @@ -870,7 +872,7 @@ ;; ;; Uses cond-scheme? from eval.sx (the FIXED version with every? check). -(define-async dispatch-async-aser-form +(define-async dispatch-async-aser-form :effects [render io] (fn ((name :as string) expr (env :as dict) ctx) (let ((args (rest expr))) (cond @@ -987,7 +989,8 @@ ;; Definition forms — evaluate for side effects (or (= name "define") (= name "defcomp") (= name "defmacro") (= name "defstyle") (= name "defhandler") (= name "defpage") - (= name "defquery") (= name "defaction")) + (= name "defquery") (= name "defaction") + (= name "deftype") (= name "defeffect")) (do (async-eval expr env ctx) nil) ;; Fallback @@ -999,7 +1002,7 @@ ;; async-aser-cond-scheme — scheme-style cond for aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-cond-scheme +(define-async async-aser-cond-scheme :effects [render io] (fn ((clauses :as list) (env :as dict) ctx) (if (empty? clauses) nil @@ -1021,7 +1024,7 @@ ;; async-aser-cond-clojure — clojure-style cond for aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-cond-clojure +(define-async async-aser-cond-clojure :effects [render io] (fn ((clauses :as list) (env :as dict) ctx) (if (< (len clauses) 2) nil @@ -1041,7 +1044,7 @@ ;; async-aser-case-loop — case dispatch for aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-case-loop +(define-async async-aser-case-loop :effects [render io] (fn (match-val (clauses :as list) (env :as dict) ctx) (if (< (len clauses) 2) nil @@ -1061,7 +1064,7 @@ ;; async-aser-thread-first — -> form in aser mode ;; -------------------------------------------------------------------------- -(define-async async-aser-thread-first +(define-async async-aser-thread-first :effects [render io] (fn ((args :as list) (env :as dict) ctx) (let ((result (async-eval (first args) env ctx))) (for-each @@ -1081,7 +1084,7 @@ ;; async-invoke-or-lambda — invoke a callable or lambda with args ;; -------------------------------------------------------------------------- -(define-async async-invoke-or-lambda +(define-async async-invoke-or-lambda :effects [render io] (fn (f (args :as list) (env :as dict) ctx) (cond (and (callable? f) (not (lambda? f)) (not (component? f))) @@ -1103,7 +1106,7 @@ ;; Async aser HO forms (map, map-indexed, for-each) ;; -------------------------------------------------------------------------- -(define-async async-aser-ho-map +(define-async async-aser-ho-map :effects [render io] (fn ((args :as list) (env :as dict) ctx) (let ((f (async-eval (first args) env ctx)) (coll (async-eval (nth args 1) env ctx)) @@ -1119,7 +1122,7 @@ results))) -(define-async async-aser-ho-map-indexed +(define-async async-aser-ho-map-indexed :effects [render io] (fn ((args :as list) (env :as dict) ctx) (let ((f (async-eval (first args) env ctx)) (coll (async-eval (nth args 1) env ctx)) @@ -1138,7 +1141,7 @@ results))) -(define-async async-aser-ho-for-each +(define-async async-aser-ho-for-each :effects [render io] (fn ((args :as list) (env :as dict) ctx) (let ((f (async-eval (first args) env ctx)) (coll (async-eval (nth args 1) env ctx)) @@ -1169,7 +1172,7 @@ ;; (sx-expr? x) — check if SxExpr ;; (set-expand-components!) — enable component expansion context var -(define-async async-eval-slot-inner +(define-async async-eval-slot-inner :effects [render io] (fn (expr (env :as dict) ctx) ;; NOTE: Uses statement-form let + set! to avoid expression-context ;; let (IIFE lambdas) which can't contain await in Python. @@ -1195,7 +1198,7 @@ (make-sx-expr (serialize result)))))))) -(define-async async-maybe-expand-result +(define-async async-maybe-expand-result :effects [render io] (fn (result (env :as dict) ctx) ;; If the aser result is a component call string like "(~foo ...)", ;; re-parse and expand it. This handles indirect component references diff --git a/shared/sx/ref/adapter-dom.sx b/shared/sx/ref/adapter-dom.sx index 5ff0b53..8c00e92 100644 --- a/shared/sx/ref/adapter-dom.sx +++ b/shared/sx/ref/adapter-dom.sx @@ -18,7 +18,7 @@ ;; render-to-dom — main entry point ;; -------------------------------------------------------------------------- -(define render-to-dom +(define render-to-dom :effects [render] (fn (expr (env :as dict) (ns :as string)) (set-render-active! true) (case (type-of expr) @@ -66,7 +66,7 @@ ;; render-dom-list — dispatch on list head ;; -------------------------------------------------------------------------- -(define render-dom-list +(define render-dom-list :effects [render] (fn (expr (env :as dict) (ns :as string)) (let ((head (first expr))) (cond @@ -165,7 +165,7 @@ ;; render-dom-element — create a DOM element with attrs and children ;; -------------------------------------------------------------------------- -(define render-dom-element +(define render-dom-element :effects [render] (fn ((tag :as string) (args :as list) (env :as dict) (ns :as string)) ;; Detect namespace from tag (let ((new-ns (cond (= tag "svg") SVG_NS @@ -236,7 +236,7 @@ ;; render-dom-component — expand and render a component ;; -------------------------------------------------------------------------- -(define render-dom-component +(define render-dom-component :effects [render] (fn ((comp :as component) (args :as list) (env :as dict) (ns :as string)) ;; Parse kwargs and children, bind into component env, render body. (let ((kwargs (dict)) @@ -283,7 +283,7 @@ ;; render-dom-fragment — render children into a DocumentFragment ;; -------------------------------------------------------------------------- -(define render-dom-fragment +(define render-dom-fragment :effects [render] (fn ((args :as list) (env :as dict) (ns :as string)) (let ((frag (create-fragment))) (for-each @@ -296,7 +296,7 @@ ;; render-dom-raw — insert unescaped content ;; -------------------------------------------------------------------------- -(define render-dom-raw +(define render-dom-raw :effects [render] (fn ((args :as list) (env :as dict)) (let ((frag (create-fragment))) (for-each @@ -317,7 +317,7 @@ ;; render-dom-unknown-component — visible warning element ;; -------------------------------------------------------------------------- -(define render-dom-unknown-component +(define render-dom-unknown-component :effects [render] (fn ((name :as string)) (error (str "Unknown component: " name)))) @@ -334,11 +334,11 @@ "map" "map-indexed" "filter" "for-each" "portal" "error-boundary")) -(define render-dom-form? +(define render-dom-form? :effects [] (fn ((name :as string)) (contains? RENDER_DOM_FORMS name))) -(define dispatch-render-form +(define dispatch-render-form :effects [render] (fn ((name :as string) expr (env :as dict) (ns :as string)) (cond ;; if — reactive inside islands (re-renders when signal deps change) @@ -580,7 +580,7 @@ ;; render-lambda-dom — render a lambda body in DOM context ;; -------------------------------------------------------------------------- -(define render-lambda-dom +(define render-lambda-dom :effects [render] (fn ((f :as lambda) (args :as list) (env :as dict) (ns :as string)) ;; Bind lambda params and render body as DOM (let ((local (env-merge (lambda-closure f) env))) @@ -604,7 +604,7 @@ ;; - Attribute bindings: (deref sig) in attr → reactive attribute ;; - Conditional fragments: (when (deref sig) ...) → reactive show/hide -(define render-dom-island +(define render-dom-island :effects [render mutation] (fn ((island :as island) (args :as list) (env :as dict) (ns :as string)) ;; Parse kwargs and children (same as component) (let ((kwargs (dict)) @@ -678,7 +678,7 @@ ;; ;; Supports :tag keyword to change wrapper element (default "div"). -(define render-dom-lake +(define render-dom-lake :effects [render] (fn ((args :as list) (env :as dict) (ns :as string)) (let ((lake-id nil) (lake-tag "div") @@ -722,7 +722,7 @@ ;; Renders as