From 5a03943b394d76fabc9106d0b63746e427284909 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 15 Mar 2026 11:38:35 +0000 Subject: [PATCH] Split env-bind! from env-set!: fix lexical scoping and closures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fundamental environment bugs fixed: 1. env-set! was used for both binding creation (let, define, params) and mutation (set!). Binding creation must NOT walk the scope chain — it should set on the immediate env. Only set! should walk. Fix: introduce env-bind! for all binding creation. env-set! now exclusively means "mutate existing binding, walk scope chain". Changed across spec (eval.sx, cek.sx, render.sx) and all web adapters (dom, html, sx, async, boot, orchestration, forms). 2. makeLambda/makeComponent/makeMacro/makeIsland used merge(env) to flatten the closure into a plain object, destroying the prototype chain. This meant set! inside closures couldn't reach the original binding — it modified a snapshot copy instead. Fix: store env directly as closure (no merge). The prototype chain is preserved, so set! walks up to the original scope. Tests: 499/516 passing (96.7%), up from 485/516. Fixed: define self-reference, let scope isolation, set! through closures, counter-via-closure pattern, recursive functions. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/javascript/platform.py | 13 +++- hosts/javascript/run_tests.js | 3 +- hosts/javascript/transpiler.sx | 10 +++ hosts/python/platform.py | 15 +++- hosts/python/transpiler.sx | 20 ++++- shared/static/scripts/sx-browser.js | 109 +++++++++++++++------------- spec/cek.sx | 16 ++-- spec/eval.sx | 47 ++++++------ spec/render.sx | 2 +- web/adapter-async.sx | 32 ++++---- web/adapter-dom.sx | 10 +-- web/adapter-html.sx | 10 +-- web/adapter-sx.sx | 8 +- web/boot.sx | 2 +- web/forms.sx | 10 +-- web/orchestration.sx | 6 +- web/tests/test-cek-reactive.sx | 14 ++-- 17 files changed, 188 insertions(+), 139 deletions(-) diff --git a/hosts/javascript/platform.py b/hosts/javascript/platform.py index 79fffea..d5d3a97 100644 --- a/hosts/javascript/platform.py +++ b/hosts/javascript/platform.py @@ -1162,12 +1162,12 @@ PLATFORM_JS_PRE = ''' function makeSymbol(n) { return new Symbol(n); } function makeKeyword(n) { return new Keyword(n); } - function makeLambda(params, body, env) { return new Lambda(params, body, merge(env)); } + function makeLambda(params, body, env) { return new Lambda(params, body, env); } function makeComponent(name, params, hasChildren, body, env, affinity) { - return new Component(name, params, hasChildren, body, merge(env), affinity); + return new Component(name, params, hasChildren, body, env, affinity); } function makeMacro(params, restParam, body, env, name) { - return new Macro(params, restParam, body, merge(env), name); + return new Macro(params, restParam, body, env, name); } function makeThunk(expr, env) { return new Thunk(expr, env); } @@ -1257,7 +1257,7 @@ PLATFORM_JS_PRE = ''' // Island platform function makeIsland(name, params, hasChildren, body, env) { - return new Island(name, params, hasChildren, body, merge(env)); + return new Island(name, params, hasChildren, body, env); } // JSON / dict helpers for island state serialization @@ -1272,6 +1272,11 @@ PLATFORM_JS_PRE = ''' function envHas(env, name) { return name in env; } function envGet(env, name) { return env[name]; } + function envBind(env, name, val) { + // Direct property set — creates or overwrites on THIS env only. + // Used by let, define, defcomp, lambda param binding. + env[name] = val; + } function envSet(env, name, val) { // Walk prototype chain to find where the variable is defined (for set!) var obj = env; diff --git a/hosts/javascript/run_tests.js b/hosts/javascript/run_tests.js index 77880fa..e3ed1d8 100644 --- a/hosts/javascript/run_tests.js +++ b/hosts/javascript/run_tests.js @@ -72,8 +72,9 @@ env["cek-eval"] = function(s) { env["eval-expr-cek"] = function(expr, e) { return Sx.eval(expr, e || env); }; env["env-get"] = function(e, k) { return e && e[k] !== undefined ? e[k] : null; }; env["env-has?"] = function(e, k) { return e && k in e; }; +env["env-bind!"] = function(e, k, v) { if (e) e[k] = v; return v; }; env["env-set!"] = function(e, k, v) { if (e) e[k] = v; return v; }; -env["env-extend"] = function(e) { return Object.assign({}, e); }; +env["env-extend"] = function(e) { return Object.create(e); }; env["env-merge"] = function(a, b) { return Object.assign({}, a, b); }; // Missing primitives referenced by tests diff --git a/hosts/javascript/transpiler.sx b/hosts/javascript/transpiler.sx index 73b09a2..aecf918 100644 --- a/hosts/javascript/transpiler.sx +++ b/hosts/javascript/transpiler.sx @@ -107,6 +107,7 @@ "get-primitive" "getPrimitive" "env-has?" "envHas" "env-get" "envGet" + "env-bind!" "envBind" "env-set!" "envSet" "env-extend" "envExtend" "env-merge" "envMerge" @@ -989,6 +990,11 @@ ", " (js-expr (nth args 1)) ", " (js-expr (nth args 2)) ")") + (= op "env-bind!") + (str "envBind(" (js-expr (nth args 0)) + ", " (js-expr (nth args 1)) + ", " (js-expr (nth args 2)) ")") + (= op "env-set!") (str "envSet(" (js-expr (nth args 0)) ", " (js-expr (nth args 1)) @@ -1396,6 +1402,10 @@ "] = " (js-expr (nth expr 3)) ";") (= name "append!") (str (js-expr (nth expr 1)) ".push(" (js-expr (nth expr 2)) ");") + (= name "env-bind!") + (str "envBind(" (js-expr (nth expr 1)) + ", " (js-expr (nth expr 2)) + ", " (js-expr (nth expr 3)) ");") (= name "env-set!") (str "envSet(" (js-expr (nth expr 1)) ", " (js-expr (nth expr 2)) diff --git a/hosts/python/platform.py b/hosts/python/platform.py index 55c8032..fc31735 100644 --- a/hosts/python/platform.py +++ b/hosts/python/platform.py @@ -498,10 +498,23 @@ def env_get(env, name): return env.get(name, NIL) -def env_set(env, name, val): +def env_bind(env, name, val): + """Create/overwrite binding on THIS env only (let, define, param binding).""" env[name] = val +def env_set(env, name, val): + """Mutate existing binding, walking scope chain (set!).""" + if hasattr(env, 'set'): + try: + env.set(name, val) + except KeyError: + # Not found anywhere — bind on immediate env + env[name] = val + else: + env[name] = val + + def env_extend(env): return _ensure_env(env).extend() diff --git a/hosts/python/transpiler.sx b/hosts/python/transpiler.sx index 3a66658..e242b6c 100644 --- a/hosts/python/transpiler.sx +++ b/hosts/python/transpiler.sx @@ -107,6 +107,7 @@ "get-primitive" "get_primitive" "env-has?" "env_has" "env-get" "env_get" + "env-bind!" "env_bind" "env-set!" "env_set" "env-extend" "env_extend" "env-merge" "env_merge" @@ -524,11 +525,16 @@ ", " (py-expr-with-cells (nth args 1) cell-vars) ", " (py-expr-with-cells (nth args 2) cell-vars) ")") - (= op "env-set!") + (= op "env-bind!") (str "_sx_dict_set(" (py-expr-with-cells (nth args 0) cell-vars) ", " (py-expr-with-cells (nth args 1) cell-vars) ", " (py-expr-with-cells (nth args 2) cell-vars) ")") + (= op "env-set!") + (str "env_set(" (py-expr-with-cells (nth args 0) cell-vars) + ", " (py-expr-with-cells (nth args 1) cell-vars) + ", " (py-expr-with-cells (nth args 2) cell-vars) ")") + (= op "set-lambda-name!") (str "_sx_set_attr(" (py-expr-with-cells (nth args 0) cell-vars) ", 'name', " (py-expr-with-cells (nth args 1) cell-vars) ")") @@ -901,10 +907,14 @@ (= name "append!") (str pad (py-expr-with-cells (nth expr 1) cell-vars) ".append(" (py-expr-with-cells (nth expr 2) cell-vars) ")") - (= name "env-set!") + (= name "env-bind!") (str pad (py-expr-with-cells (nth expr 1) cell-vars) "[" (py-expr-with-cells (nth expr 2) cell-vars) "] = " (py-expr-with-cells (nth expr 3) cell-vars)) + (= name "env-set!") + (str pad "env_set(" (py-expr-with-cells (nth expr 1) cell-vars) + ", " (py-expr-with-cells (nth expr 2) cell-vars) + ", " (py-expr-with-cells (nth expr 3) cell-vars) ")") (= name "set-lambda-name!") (str pad (py-expr-with-cells (nth expr 1) cell-vars) ".name = " (py-expr-with-cells (nth expr 2) cell-vars)) @@ -1098,10 +1108,14 @@ (append! lines (str pad (py-expr-with-cells (nth expr 1) cell-vars) "[" (py-expr-with-cells (nth expr 2) cell-vars) "] = " (py-expr-with-cells (nth expr 3) cell-vars))) - (= name "env-set!") + (= name "env-bind!") (append! lines (str pad (py-expr-with-cells (nth expr 1) cell-vars) "[" (py-expr-with-cells (nth expr 2) cell-vars) "] = " (py-expr-with-cells (nth expr 3) cell-vars))) + (= name "env-set!") + (append! lines (str pad "env_set(" (py-expr-with-cells (nth expr 1) cell-vars) + ", " (py-expr-with-cells (nth expr 2) cell-vars) + ", " (py-expr-with-cells (nth expr 3) cell-vars) ")")) :else (append! lines (py-statement-with-cells expr indent cell-vars))))))))) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 1b34c71..7a50429 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-15T11:07:52Z"; + var SX_VERSION = "2026-03-15T11:38:02Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -122,12 +122,12 @@ function makeSymbol(n) { return new Symbol(n); } function makeKeyword(n) { return new Keyword(n); } - function makeLambda(params, body, env) { return new Lambda(params, body, merge(env)); } + function makeLambda(params, body, env) { return new Lambda(params, body, env); } function makeComponent(name, params, hasChildren, body, env, affinity) { - return new Component(name, params, hasChildren, body, merge(env), affinity); + return new Component(name, params, hasChildren, body, env, affinity); } function makeMacro(params, restParam, body, env, name) { - return new Macro(params, restParam, body, merge(env), name); + return new Macro(params, restParam, body, env, name); } function makeThunk(expr, env) { return new Thunk(expr, env); } @@ -217,7 +217,7 @@ // Island platform function makeIsland(name, params, hasChildren, body, env) { - return new Island(name, params, hasChildren, body, merge(env)); + return new Island(name, params, hasChildren, body, env); } // JSON / dict helpers for island state serialization @@ -232,6 +232,11 @@ function envHas(env, name) { return name in env; } function envGet(env, name) { return env[name]; } + function envBind(env, name, val) { + // Direct property set — creates or overwrites on THIS env only. + // Used by let, define, defcomp, lambda param binding. + env[name] = val; + } function envSet(env, name, val) { // Walk prototype chain to find where the variable is defined (for set!) var obj = env; @@ -843,7 +848,7 @@ PRIMITIVES["eval-call"] = evalCall; var callLambda = function(f, args, callerEnv) { return (function() { var params = lambdaParams(f); var local = envMerge(lambdaClosure(f), callerEnv); - return (isSxTruthy((len(args) > len(params))) ? error((String(sxOr(lambdaName(f), "lambda")) + String(" expects ") + String(len(params)) + String(" args, got ") + String(len(args)))) : (forEach(function(pair) { return envSet(local, first(pair), nth(pair, 1)); }, zip(params, args)), forEach(function(p) { return envSet(local, p, NIL); }, slice(params, len(args))), makeThunk(lambdaBody(f), local))); + return (isSxTruthy((len(args) > len(params))) ? error((String(sxOr(lambdaName(f), "lambda")) + String(" expects ") + String(len(params)) + String(" args, got ") + String(len(args)))) : (forEach(function(pair) { return envBind(local, first(pair), nth(pair, 1)); }, zip(params, args)), forEach(function(p) { return envBind(local, p, NIL); }, slice(params, len(args))), makeThunk(lambdaBody(f), local))); })(); }; PRIMITIVES["call-lambda"] = callLambda; @@ -853,9 +858,9 @@ PRIMITIVES["call-lambda"] = callLambda; var kwargs = first(parsed); var children = nth(parsed, 1); var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, sxOr(dictGet(kwargs, p), NIL)); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, sxOr(dictGet(kwargs, p), NIL)); } } if (isSxTruthy(componentHasChildren(comp))) { - envSet(local, "children", children); + envBind(local, "children", children); } return makeThunk(componentBody(comp), local); })(); }; @@ -951,13 +956,13 @@ PRIMITIVES["sf-or"] = sfOr; var local = envExtend(env); (isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { return (function() { var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding)); - return envSet(local, vname, trampoline(evalExpr(nth(binding, 1), local))); + return envBind(local, vname, trampoline(evalExpr(nth(binding, 1), local))); })(); }, bindings) : (function() { var i = 0; return reduce(function(acc, pairIdx) { return (function() { var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2))); var valExpr = nth(bindings, ((pairIdx * 2) + 1)); - return envSet(local, vname, trampoline(evalExpr(valExpr, local))); + return envBind(local, vname, trampoline(evalExpr(valExpr, local))); })(); }, NIL, range(0, (len(bindings) / 2))); })()); { var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } } @@ -978,7 +983,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var loopBody = (isSxTruthy((len(body) == 1)) ? first(body) : cons(makeSymbol("begin"), body)); var loopFn = makeLambda(params, loopBody, env); loopFn.name = loopName; - envSet(lambdaClosure(loopFn), loopName, loopFn); + envBind(lambdaClosure(loopFn), loopName, loopFn); return (function() { var initVals = map(function(e) { return trampoline(evalExpr(e, env)); }, inits); return callLambda(loopFn, initVals, env); @@ -1006,14 +1011,14 @@ PRIMITIVES["sf-lambda"] = sfLambda; if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) { value.name = symbolName(nameSym); } - envSet(env, symbolName(nameSym), value); + envBind(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 envBind(env, "*effect-annotations*", effectAnns); })(); } return value; @@ -1042,10 +1047,10 @@ PRIMITIVES["sf-define"] = sfDefine; 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); + return envBind(env, "*effect-annotations*", effectAnns); })(); } - envSet(env, symbolName(nameSym), comp); + envBind(env, symbolName(nameSym), comp); return comp; })(); })(); }; @@ -1097,7 +1102,7 @@ PRIMITIVES["parse-comp-params"] = parseCompParams; var hasChildren = nth(parsed, 1); return (function() { var island = makeIsland(compName, params, hasChildren, body, env); - envSet(env, symbolName(nameSym), island); + envBind(env, symbolName(nameSym), island); return island; })(); })(); }; @@ -1113,7 +1118,7 @@ PRIMITIVES["sf-defisland"] = sfDefisland; var restParam = nth(parsed, 1); return (function() { var mac = makeMacro(params, restParam, body, env, symbolName(nameSym)); - envSet(env, symbolName(nameSym), mac); + envBind(env, symbolName(nameSym), mac); return mac; })(); })(); }; @@ -1132,7 +1137,7 @@ PRIMITIVES["parse-macro-params"] = parseMacroParams; var sfDefstyle = function(args, env) { return (function() { var nameSym = first(args); var value = trampoline(evalExpr(nth(args, 1), env)); - envSet(env, symbolName(nameSym), value); + envBind(env, symbolName(nameSym), value); return value; })(); }; PRIMITIVES["sf-defstyle"] = sfDefstyle; @@ -1162,7 +1167,7 @@ PRIMITIVES["normalize-type-body"] = normalizeTypeBody; 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); + envBind(env, "*type-registry*", registry); return NIL; })(); })(); }; @@ -1175,7 +1180,7 @@ PRIMITIVES["sf-deftype"] = sfDeftype; if (isSxTruthy(!isSxTruthy(contains(registry, effectName)))) { registry.push(effectName); } - envSet(env, "*effect-registry*", registry); + envBind(env, "*effect-registry*", registry); return NIL; })(); }; PRIMITIVES["sf-defeffect"] = sfDefeffect; @@ -1237,18 +1242,18 @@ PRIMITIVES["sf-set!"] = sfSetBang; var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding)); names.push(vname); valExprs.push(nth(binding, 1)); - return envSet(local, vname, NIL); + return envBind(local, vname, NIL); })(); }, bindings) : reduce(function(acc, pairIdx) { return (function() { var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2))); var valExpr = nth(bindings, ((pairIdx * 2) + 1)); names.push(vname); valExprs.push(valExpr); - return envSet(local, vname, NIL); + return envBind(local, vname, NIL); })(); }, NIL, range(0, (len(bindings) / 2)))); (function() { var values = map(function(e) { return trampoline(evalExpr(e, local)); }, valExprs); - { var _c = zip(names, values); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), nth(pair, 1)); } } - return forEach(function(val) { return (isSxTruthy(isLambda(val)) ? forEach(function(n) { return envSet(lambdaClosure(val), n, envGet(local, n)); }, names) : NIL); }, values); + { var _c = zip(names, values); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envBind(local, first(pair), nth(pair, 1)); } } + return forEach(function(val) { return (isSxTruthy(isLambda(val)) ? forEach(function(n) { return envBind(lambdaClosure(val), n, envGet(local, n)); }, names) : NIL); }, values); })(); { var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } } return makeThunk(last(body), local); @@ -1297,9 +1302,9 @@ PRIMITIVES["sf-provide"] = sfProvide; // expand-macro var expandMacro = function(mac, rawArgs, env) { return (function() { var local = envMerge(macroClosure(mac), env); - { var _c = mapIndexed(function(i, p) { return [p, i]; }, macroParams(mac)); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envSet(local, first(pair), (isSxTruthy((nth(pair, 1) < len(rawArgs))) ? nth(rawArgs, nth(pair, 1)) : NIL)); } } + { var _c = mapIndexed(function(i, p) { return [p, i]; }, macroParams(mac)); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; envBind(local, first(pair), (isSxTruthy((nth(pair, 1) < len(rawArgs))) ? nth(rawArgs, nth(pair, 1)) : NIL)); } } if (isSxTruthy(macroRestParam(mac))) { - envSet(local, macroRestParam(mac), slice(rawArgs, len(macroParams(mac)))); + envBind(local, macroRestParam(mac), slice(rawArgs, len(macroParams(mac)))); } return trampoline(evalExpr(macroBody(mac), local)); })(); }; @@ -1435,7 +1440,7 @@ PRIMITIVES["eval-cond-clojure"] = evalCondClojure; { 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 envBind(local, name, trampoline(evalExpr(nth(pair, 1), local))); })(); } } } return local; @@ -1722,7 +1727,7 @@ PRIMITIVES["dispatch-html-form"] = dispatchHtmlForm; // render-lambda-html var renderLambdaHtml = function(f, args, env) { return (function() { var local = envMerge(lambdaClosure(f), env); - forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f)); + forEachIndexed(function(i, p) { return envBind(local, p, nth(args, i)); }, lambdaParams(f)); return renderToHtml(lambdaBody(f), local); })(); }; PRIMITIVES["render-lambda-html"] = renderLambdaHtml; @@ -1741,9 +1746,9 @@ PRIMITIVES["render-lambda-html"] = renderLambdaHtml; })(); }, {["i"]: 0, ["skip"]: false}, args); return (function() { var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(comp))) { - envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); + envBind(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); } return renderToHtml(componentBody(comp), local); })(); @@ -1834,9 +1839,9 @@ PRIMITIVES["render-html-marsh"] = renderHtmlMarsh; return (function() { var local = envMerge(componentClosure(island), env); var islandName = componentName(island); - { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(island))) { - envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); + envBind(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)))); } return (function() { var bodyHtml = renderToHtml(componentBody(island), local); @@ -1987,7 +1992,7 @@ return result; }, args); var coll = trampoline(evalExpr(nth(args, 1), env)); return map(function(item) { return (isSxTruthy(isLambda(f)) ? (function() { var local = envMerge(lambdaClosure(f), env); - envSet(local, first(lambdaParams(f)), item); + envBind(local, first(lambdaParams(f)), item); return aser(lambdaBody(f), local); })() : cekCall(f, [item])); }, coll); })() : (isSxTruthy((name == "map-indexed")) ? (function() { @@ -1995,8 +2000,8 @@ return result; }, args); var coll = trampoline(evalExpr(nth(args, 1), env)); return mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? (function() { var local = envMerge(lambdaClosure(f), env); - envSet(local, first(lambdaParams(f)), i); - envSet(local, nth(lambdaParams(f), 1), item); + envBind(local, first(lambdaParams(f)), i); + envBind(local, nth(lambdaParams(f), 1), item); return aser(lambdaBody(f), local); })() : cekCall(f, [i, item])); }, coll); })() : (isSxTruthy((name == "for-each")) ? (function() { @@ -2005,7 +2010,7 @@ return result; }, args); var results = []; { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (isSxTruthy(isLambda(f)) ? (function() { var local = envMerge(lambdaClosure(f), env); - envSet(local, first(lambdaParams(f)), item); + envBind(local, first(lambdaParams(f)), item); return append_b(results, aser(lambdaBody(f), local)); })() : cekCall(f, [item])); } } return (isSxTruthy(isEmpty(results)) ? NIL : results); @@ -2143,7 +2148,7 @@ PRIMITIVES["render-dom-element"] = renderDomElement; })(); }, {["i"]: 0, ["skip"]: false}, args); return (function() { var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(comp))) { (function() { var childFrag = createFragment(); @@ -2151,7 +2156,7 @@ PRIMITIVES["render-dom-element"] = renderDomElement; var result = renderToDom(c, env, ns); return (isSxTruthy(!isSxTruthy(isSpread(result))) ? domAppend(childFrag, result) : NIL); })(); } } - return envSet(local, "children", childFrag); + return envBind(local, "children", childFrag); })(); } return renderToDom(componentBody(comp), local, ns); @@ -2355,7 +2360,7 @@ PRIMITIVES["dispatch-render-form"] = dispatchRenderForm; // render-lambda-dom var renderLambdaDom = function(f, args, env, ns) { return (function() { var local = envMerge(lambdaClosure(f), env); - forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f)); + forEachIndexed(function(i, p) { return envBind(local, p, nth(args, i)); }, lambdaParams(f)); return renderToDom(lambdaBody(f), local, ns); })(); }; PRIMITIVES["render-lambda-dom"] = renderLambdaDom; @@ -2375,12 +2380,12 @@ PRIMITIVES["render-lambda-dom"] = renderLambdaDom; return (function() { var local = envMerge(componentClosure(island), env); var islandName = componentName(island); - { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + { var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } if (isSxTruthy(componentHasChildren(island))) { (function() { var childFrag = createFragment(); { var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(childFrag, renderToDom(c, env, ns)); } } - return envSet(local, "children", childFrag); + return envBind(local, "children", childFrag); })(); } return (function() { @@ -3657,9 +3662,9 @@ PRIMITIVES["bind-sse-swap"] = bindSseSwap; var exprs = sxParse(body); return domListen(el, eventName, function(e) { return (function() { var handlerEnv = envExtend({}); - envSet(handlerEnv, "event", e); - envSet(handlerEnv, "this", el); - envSet(handlerEnv, "detail", eventDetail(e)); + envBind(handlerEnv, "event", e); + envBind(handlerEnv, "this", el); + envBind(handlerEnv, "detail", eventDetail(e)); return forEach(function(expr) { return evalExpr(expr, handlerEnv); }, exprs); })(); }); })()) : NIL); @@ -3921,7 +3926,7 @@ PRIMITIVES["sx-hydrate-islands"] = sxHydrateIslands; var kwargs = sxOr(first(sxParse(stateSx)), {}); var disposers = []; var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } return (function() { var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); }); domSetTextContent(el, ""); @@ -4957,7 +4962,7 @@ PRIMITIVES["step-sf-reset"] = stepSfReset; var k = makeCekContinuation(captured, restKont); return (function() { var shiftEnv = envExtend(env); - envSet(shiftEnv, kName, k); + envBind(shiftEnv, kName, k); return makeCekState(body, shiftEnv, restKont); })(); })(); @@ -5087,7 +5092,7 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach; var remaining = get(frame, "remaining"); var body = get(frame, "body"); var local = get(frame, "env"); - envSet(local, name, value); + envBind(local, name, value); return (isSxTruthy(isEmpty(remaining)) ? stepSfBegin(body, local, restK) : (function() { var nextBinding = first(remaining); var vname = (isSxTruthy((typeOf(first(nextBinding)) == "symbol")) ? symbolName(first(nextBinding)) : first(nextBinding)); @@ -5101,13 +5106,13 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach; if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) { value.name = name; } - envSet(fenv, name, value); + envBind(fenv, name, value); if (isSxTruthy(hasEffects)) { (function() { var effectNames = (isSxTruthy((typeOf(effectList) == "list")) ? map(function(e) { return (isSxTruthy((typeOf(e) == "symbol")) ? symbolName(e) : (String(e))); }, effectList) : [(String(effectList))]); var effectAnns = (isSxTruthy(envHas(fenv, "*effect-annotations*")) ? envGet(fenv, "*effect-annotations*") : {}); effectAnns[name] = effectNames; - return envSet(fenv, "*effect-annotations*", effectAnns); + return envBind(fenv, "*effect-annotations*", effectAnns); })(); } return makeCekValue(value, fenv, restK); @@ -5274,15 +5279,15 @@ PRIMITIVES["step-continue"] = stepContinue; })() : (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? makeCekValue(apply(f, args), env, kont) : (isSxTruthy(isLambda(f)) ? (function() { var params = lambdaParams(f); var local = envMerge(lambdaClosure(f), env); - return (isSxTruthy((len(args) > len(params))) ? error((String(sxOr(lambdaName(f), "lambda")) + String(" expects ") + String(len(params)) + String(" args, got ") + String(len(args)))) : (forEach(function(pair) { return envSet(local, first(pair), nth(pair, 1)); }, zip(params, args)), forEach(function(p) { return envSet(local, p, NIL); }, slice(params, len(args))), makeCekState(lambdaBody(f), local, kont))); + return (isSxTruthy((len(args) > len(params))) ? error((String(sxOr(lambdaName(f), "lambda")) + String(" expects ") + String(len(params)) + String(" args, got ") + String(len(args)))) : (forEach(function(pair) { return envBind(local, first(pair), nth(pair, 1)); }, zip(params, args)), forEach(function(p) { return envBind(local, p, NIL); }, slice(params, len(args))), makeCekState(lambdaBody(f), local, kont))); })() : (isSxTruthy(sxOr(isComponent(f), isIsland(f))) ? (function() { var parsed = parseKeywordArgs(rawArgs, env); var kwargs = first(parsed); var children = nth(parsed, 1); var local = envMerge(componentClosure(f), env); - { var _c = componentParams(f); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, sxOr(dictGet(kwargs, p), NIL)); } } + { var _c = componentParams(f); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, sxOr(dictGet(kwargs, p), NIL)); } } if (isSxTruthy(componentHasChildren(f))) { - envSet(local, "children", children); + envBind(local, "children", children); } return makeCekState(componentBody(f), local, kont); })() : error((String("Not callable: ") + String(inspect(f)))))))); }; diff --git a/spec/cek.sx b/spec/cek.sx index b93437f..fe319f2 100644 --- a/spec/cek.sx +++ b/spec/cek.sx @@ -396,7 +396,7 @@ (let ((k (make-cek-continuation captured rest-kont))) ;; Evaluate shift body with k bound, continuation goes to rest-kont (let ((shift-env (env-extend env))) - (env-set! shift-env k-name k) + (env-bind! shift-env k-name k) (make-cek-state body shift-env rest-kont)))))) @@ -604,7 +604,7 @@ (body (get frame "body")) (local (get frame "env"))) ;; Bind the value - (env-set! local name value) + (env-bind! local name value) ;; More bindings? (if (empty? remaining) ;; All bindings done — evaluate body @@ -628,7 +628,7 @@ (effect-list (get frame "effect-list"))) (when (and (lambda? value) (nil? (lambda-name value))) (set-lambda-name! value name)) - (env-set! fenv name value) + (env-bind! fenv name value) ;; Effect annotation (when has-effects (let ((effect-names (if (= (type-of effect-list) "list") @@ -640,7 +640,7 @@ (env-get fenv "*effect-annotations*") (dict)))) (dict-set! effect-anns name effect-names) - (env-set! fenv "*effect-annotations*" effect-anns))) + (env-bind! fenv "*effect-annotations*" effect-anns))) (make-cek-value value fenv rest-k)) ;; --- SetFrame: value evaluated --- @@ -969,10 +969,10 @@ " expects " (len params) " args, got " (len args))) (do (for-each - (fn (pair) (env-set! local (first pair) (nth pair 1))) + (fn (pair) (env-bind! local (first pair) (nth pair 1))) (zip params args)) (for-each - (fn (p) (env-set! local p nil)) + (fn (p) (env-bind! local p nil)) (slice params (len args))) (make-cek-state (lambda-body f) local kont)))) @@ -983,10 +983,10 @@ (children (nth parsed 1)) (local (env-merge (component-closure f) env))) (for-each - (fn (p) (env-set! local p (or (dict-get kwargs p) nil))) + (fn (p) (env-bind! local p (or (dict-get kwargs p) nil))) (component-params f)) (when (component-has-children? f) - (env-set! local "children" children)) + (env-bind! local "children" children)) (make-cek-state (component-body f) local kont)) :else (error (str "Not callable: " (inspect f)))))) diff --git a/spec/eval.sx b/spec/eval.sx index 6429880..2329963 100644 --- a/spec/eval.sx +++ b/spec/eval.sx @@ -229,10 +229,10 @@ (do ;; Bind params — provided args first, then nil for missing (for-each - (fn (pair) (env-set! local (first pair) (nth pair 1))) + (fn (pair) (env-bind! local (first pair) (nth pair 1))) (zip params args)) (for-each - (fn (p) (env-set! local p nil)) + (fn (p) (env-bind! local p nil)) (slice params (len args))) ;; Return thunk for TCO (make-thunk (lambda-body f) local)))))) @@ -247,11 +247,11 @@ (local (env-merge (component-closure comp) env))) ;; Bind keyword params (for-each - (fn (p) (env-set! local p (or (dict-get kwargs p) nil))) + (fn (p) (env-bind! local p (or (dict-get kwargs p) nil))) (component-params comp)) ;; Bind children if component accepts them (when (component-has-children? comp) - (env-set! local "children" children)) + (env-bind! local "children" children)) ;; Return thunk — body evaluated in local env (make-thunk (component-body comp) local)))) @@ -423,7 +423,7 @@ (let ((vname (if (= (type-of (first binding)) "symbol") (symbol-name (first binding)) (first binding)))) - (env-set! local vname (trampoline (eval-expr (nth binding 1) local))))) + (env-bind! local vname (trampoline (eval-expr (nth binding 1) local))))) bindings) ;; Clojure-style (let ((i 0)) @@ -433,7 +433,7 @@ (symbol-name (nth bindings (* pair-idx 2))) (nth bindings (* pair-idx 2)))) (val-expr (nth bindings (inc (* pair-idx 2))))) - (env-set! local vname (trampoline (eval-expr val-expr local))))) + (env-bind! local vname (trampoline (eval-expr val-expr local))))) nil (range 0 (/ (len bindings) 2))))) ;; Evaluate body — last expression in tail position @@ -480,7 +480,7 @@ (loop-fn (make-lambda params loop-body env))) ;; Self-reference: loop can call itself by name (set-lambda-name! loop-fn loop-name) - (env-set! (lambda-closure loop-fn) loop-name loop-fn) + (env-bind! (lambda-closure loop-fn) loop-name loop-fn) ;; Evaluate initial values in enclosing env, then call (let ((init-vals (map (fn (e) (trampoline (eval-expr e env))) inits))) (call-lambda loop-fn init-vals env)))))) @@ -522,7 +522,7 @@ (value (trampoline (eval-expr (nth args val-idx) env)))) (when (and (lambda? value) (nil? (lambda-name value))) (set-lambda-name! value (symbol-name name-sym))) - (env-set! env (symbol-name name-sym) value) + (env-bind! env (symbol-name name-sym) value) ;; Store effect annotation if declared (when has-effects (let ((effects-raw (nth args 2)) @@ -535,7 +535,7 @@ (env-get env "*effect-annotations*") (dict)))) (dict-set! effect-anns (symbol-name name-sym) effect-list) - (env-set! env "*effect-annotations*" effect-anns))) + (env-bind! env "*effect-annotations*" effect-anns))) value))) @@ -570,8 +570,8 @@ (env-get env "*effect-annotations*") (dict)))) (dict-set! effect-anns (symbol-name name-sym) effect-list) - (env-set! env "*effect-annotations*" effect-anns))) - (env-set! env (symbol-name name-sym) comp) + (env-bind! env "*effect-annotations*" effect-anns))) + (env-bind! env (symbol-name name-sym) comp) comp)))) (define defcomp-kwarg @@ -646,7 +646,7 @@ (params (first parsed)) (has-children (nth parsed 1))) (let ((island (make-island comp-name params has-children body env))) - (env-set! env (symbol-name name-sym) island) + (env-bind! env (symbol-name name-sym) island) island)))) @@ -659,7 +659,7 @@ (params (first parsed)) (rest-param (nth parsed 1))) (let ((mac (make-macro params rest-param body env (symbol-name name-sym)))) - (env-set! env (symbol-name name-sym) mac) + (env-bind! env (symbol-name name-sym) mac) mac)))) (define parse-macro-params @@ -688,7 +688,7 @@ ;; (defstyle name expr) — bind name to evaluated expr (string, function, etc.) (let ((name-sym (first args)) (value (trampoline (eval-expr (nth args 1) env)))) - (env-set! env (symbol-name name-sym) value) + (env-bind! env (symbol-name name-sym) value) value))) @@ -749,7 +749,7 @@ (dict)))) (dict-set! registry type-name (make-type-def type-name type-params body)) - (env-set! env "*type-registry*" registry) + (env-bind! env "*type-registry*" registry) nil)))) @@ -764,7 +764,7 @@ (list)))) (when (not (contains? registry effect-name)) (append! registry effect-name)) - (env-set! env "*effect-registry*" registry) + (env-bind! env "*effect-registry*" registry) nil))) @@ -879,7 +879,7 @@ (first binding)))) (append! names vname) (append! val-exprs (nth binding 1)) - (env-set! local vname nil))) + (env-bind! local vname nil))) bindings) ;; Clojure-style (reduce @@ -890,21 +890,21 @@ (val-expr (nth bindings (inc (* pair-idx 2))))) (append! names vname) (append! val-exprs val-expr) - (env-set! local vname nil))) + (env-bind! local vname nil))) nil (range 0 (/ (len bindings) 2)))) ;; Second pass: evaluate values (they can see each other's names) (let ((values (map (fn (e) (trampoline (eval-expr e local))) val-exprs))) ;; Bind final values (for-each - (fn (pair) (env-set! local (first pair) (nth pair 1))) + (fn (pair) (env-bind! local (first pair) (nth pair 1))) (zip names values)) ;; Patch lambda closures so they see the final bindings (for-each (fn (val) (when (lambda? val) (for-each - (fn (n) (env-set! (lambda-closure val) n (env-get local n))) + (fn (n) (env-bind! (lambda-closure val) n (env-get local n))) names))) values)) ;; Evaluate body @@ -998,14 +998,14 @@ ;; Bind positional params (unevaluated) (for-each (fn (pair) - (env-set! local (first pair) + (env-bind! local (first pair) (if (< (nth pair 1) (len raw-args)) (nth raw-args (nth pair 1)) nil))) (map-indexed (fn (i p) (list p i)) (macro-params mac))) ;; Bind &rest param (when (macro-rest-param mac) - (env-set! local (macro-rest-param mac) + (env-bind! local (macro-rest-param mac) (slice raw-args (len (macro-params mac))))) ;; Evaluate body → new AST (trampoline (eval-expr (macro-body mac) local))))) @@ -1153,7 +1153,8 @@ ;; Environment: ;; (env-has? env name) → boolean ;; (env-get env name) → value -;; (env-set! env name val) → void (mutating) +;; (env-bind! env name val) → void (create binding on THIS env, no chain walk) +;; (env-set! env name val) → void (mutate existing binding, walks scope chain) ;; (env-extend env) → new env inheriting from env ;; (env-merge base overlay) → new env with overlay on top ;; diff --git a/spec/render.sx b/spec/render.sx index 0293da5..d05516c 100644 --- a/spec/render.sx +++ b/spec/render.sx @@ -184,7 +184,7 @@ (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)))))) + (env-bind! local name (trampoline (eval-expr (nth pair 1) local)))))) bindings) local))) diff --git a/web/adapter-async.sx b/web/adapter-async.sx index 0a87d5f..bb7ae7f 100644 --- a/web/adapter-async.sx +++ b/web/adapter-async.sx @@ -229,7 +229,7 @@ ;; Build env: closure + caller env + params (let ((local (env-merge (component-closure comp) env))) (for-each - (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (fn (p) (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) ;; Pre-render children to raw HTML (when (component-has-children? comp) @@ -237,7 +237,7 @@ (for-each (fn (c) (append! parts (async-render c env ctx))) children) - (env-set! local "children" + (env-bind! local "children" (make-raw-html (join "" parts))))) (async-render (component-body comp) local ctx))))) @@ -254,7 +254,7 @@ (let ((local (env-merge (component-closure island) env)) (island-name (component-name island))) (for-each - (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (fn (p) (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params island)) ;; Pre-render children (when (component-has-children? island) @@ -262,7 +262,7 @@ (for-each (fn (c) (append! parts (async-render c env ctx))) children) - (env-set! local "children" + (env-bind! local "children" (make-raw-html (join "" parts))))) (let ((body-html (async-render (component-body island) local ctx)) (state-json (serialize-island-state kwargs))) @@ -283,7 +283,7 @@ (fn ((f :as lambda) (args :as list) (env :as dict) ctx) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed - (fn (i p) (env-set! local p (nth args i))) + (fn (i p) (env-bind! local p (nth args i))) (lambda-params f)) (async-render (lambda-body f) local ctx)))) @@ -517,7 +517,7 @@ (let ((name (if (= (type-of (first pair)) "symbol") (symbol-name (first pair)) (str (first pair))))) - (env-set! local name (async-eval (nth pair 1) local ctx))))) + (env-bind! local name (async-eval (nth pair 1) local ctx))))) bindings) ;; Clojure-style: (name val name val ...) (async-process-bindings-flat bindings local ctx))) @@ -538,7 +538,7 @@ (symbol-name item) (str item)))) (when (< (inc i) (len bindings)) - (env-set! local name + (env-bind! local name (async-eval (nth bindings (inc i)) local ctx)))) (set! skip true) (set! i (inc i))))) @@ -735,7 +735,7 @@ (lambda? f) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed - (fn (i p) (env-set! local p (nth evaled-args i))) + (fn (i p) (env-bind! local p (nth evaled-args i))) (lambda-params f)) (async-aser (lambda-body f) local ctx)) (component? f) @@ -807,7 +807,7 @@ (async-parse-aser-kw-args args kwargs children env ctx) (let ((local (env-merge (component-closure comp) env))) (for-each - (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (fn (p) (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) (when (component-has-children? comp) (let ((child-parts (list))) @@ -823,7 +823,7 @@ (when (not (nil? result)) (append! child-parts (serialize result)))))) children) - (env-set! local "children" + (env-bind! local "children" (make-sx-expr (str "(<> " (join " " child-parts) ")"))))) (async-aser (component-body comp) local ctx))))) @@ -1033,7 +1033,7 @@ ;; set! (= name "set!") (let ((value (async-eval (nth args 1) env ctx))) - (env-set! env (symbol-name (first args)) value) + (env-bind! env (symbol-name (first args)) value) value) ;; map @@ -1197,7 +1197,7 @@ (lambda? f) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed - (fn (i p) (env-set! local p (nth args i))) + (fn (i p) (env-bind! local p (nth args i))) (lambda-params f)) (async-eval (lambda-body f) local ctx)) :else @@ -1217,7 +1217,7 @@ (fn (item) (if (lambda? f) (let ((local (env-merge (lambda-closure f) env))) - (env-set! local (first (lambda-params f)) item) + (env-bind! local (first (lambda-params f)) item) (append! results (async-aser (lambda-body f) local ctx))) (append! results (async-invoke f item)))) coll) @@ -1234,8 +1234,8 @@ (fn (item) (if (lambda? f) (let ((local (env-merge (lambda-closure f) env))) - (env-set! local (first (lambda-params f)) i) - (env-set! local (nth (lambda-params f) 1) item) + (env-bind! local (first (lambda-params f)) i) + (env-bind! local (nth (lambda-params f) 1) item) (append! results (async-aser (lambda-body f) local ctx))) (append! results (async-invoke f i item))) (set! i (inc i))) @@ -1252,7 +1252,7 @@ (fn (item) (if (lambda? f) (let ((local (env-merge (lambda-closure f) env))) - (env-set! local (first (lambda-params f)) item) + (env-bind! local (first (lambda-params f)) item) (append! results (async-aser (lambda-body f) local ctx))) (append! results (async-invoke f item)))) coll) diff --git a/web/adapter-dom.sx b/web/adapter-dom.sx index 7feffd0..11fcc63 100644 --- a/web/adapter-dom.sx +++ b/web/adapter-dom.sx @@ -307,7 +307,7 @@ ;; Bind params from kwargs (for-each (fn (p) - (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) ;; If component accepts children, pre-render them to a fragment @@ -320,7 +320,7 @@ (when (not (spread? result)) (dom-append child-frag result)))) children) - (env-set! local "children" child-frag))) + (env-bind! local "children" child-frag))) (render-to-dom (component-body comp) local ns))))) @@ -687,7 +687,7 @@ (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed (fn (i p) - (env-set! local p (nth args i))) + (env-bind! local p (nth args i))) (lambda-params f)) (render-to-dom (lambda-body f) local ns)))) @@ -734,7 +734,7 @@ ;; Bind params from kwargs (for-each (fn (p) - (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params island)) ;; If island accepts children, pre-render them to a fragment @@ -743,7 +743,7 @@ (for-each (fn (c) (dom-append child-frag (render-to-dom c env ns))) children) - (env-set! local "children" child-frag))) + (env-bind! local "children" child-frag))) ;; Create the island container element (let ((container (dom-create-element "span" nil)) diff --git a/web/adapter-html.sx b/web/adapter-html.sx index 843f2a6..156b783 100644 --- a/web/adapter-html.sx +++ b/web/adapter-html.sx @@ -277,7 +277,7 @@ (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed (fn (i p) - (env-set! local p (nth args i))) + (env-bind! local p (nth args i))) (lambda-params f)) (render-to-html (lambda-body f) local)))) @@ -315,11 +315,11 @@ ;; Bind params from kwargs (for-each (fn (p) - (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) ;; If component accepts children, pre-render them to raw HTML (when (component-has-children? comp) - (env-set! local "children" + (env-bind! local "children" (make-raw-html (join "" (map (fn (c) (render-to-html c env)) children))))) (render-to-html (component-body comp) local))))) @@ -481,12 +481,12 @@ ;; Bind params from kwargs (for-each (fn (p) - (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params island)) ;; If island accepts children, pre-render them to raw HTML (when (component-has-children? island) - (env-set! local "children" + (env-bind! local "children" (make-raw-html (join "" (map (fn (c) (render-to-html c env)) children))))) ;; Render the island body as HTML diff --git a/web/adapter-sx.sx b/web/adapter-sx.sx index 9c6493c..d3a3656 100644 --- a/web/adapter-sx.sx +++ b/web/adapter-sx.sx @@ -289,7 +289,7 @@ (map (fn (item) (if (lambda? f) (let ((local (env-merge (lambda-closure f) env))) - (env-set! local (first (lambda-params f)) item) + (env-bind! local (first (lambda-params f)) item) (aser (lambda-body f) local)) (cek-call f (list item)))) coll)) @@ -301,8 +301,8 @@ (map-indexed (fn (i item) (if (lambda? f) (let ((local (env-merge (lambda-closure f) env))) - (env-set! local (first (lambda-params f)) i) - (env-set! local (nth (lambda-params f) 1) item) + (env-bind! local (first (lambda-params f)) i) + (env-bind! local (nth (lambda-params f) 1) item) (aser (lambda-body f) local)) (cek-call f (list i item)))) coll)) @@ -315,7 +315,7 @@ (for-each (fn (item) (if (lambda? f) (let ((local (env-merge (lambda-closure f) env))) - (env-set! local (first (lambda-params f)) item) + (env-bind! local (first (lambda-params f)) item) (append! results (aser (lambda-body f) local))) (cek-call f (list item)))) coll) diff --git a/web/boot.sx b/web/boot.sx index 72d7c00..0014c07 100644 --- a/web/boot.sx +++ b/web/boot.sx @@ -361,7 +361,7 @@ ;; Bind params from kwargs (for-each (fn ((p :as string)) - (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) + (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) ;; Render the island body in a reactive scope diff --git a/web/forms.sx b/web/forms.sx index 1bef80b..dae9d12 100644 --- a/web/forms.sx +++ b/web/forms.sx @@ -98,7 +98,7 @@ (params (get parsed "params")) (body (get parsed "body"))) (let ((hdef (make-handler-def name params body env opts))) - (env-set! env (str "handler:" name) hdef) + (env-bind! env (str "handler:" name) hdef) hdef)))) @@ -117,7 +117,7 @@ (doc (if has-doc (nth args 2) "")) (body (if has-doc (nth args 3) (nth args 2)))) (let ((qdef (make-query-def name params doc body env))) - (env-set! env (str "query:" name) qdef) + (env-bind! env (str "query:" name) qdef) qdef)))) @@ -135,7 +135,7 @@ (doc (if has-doc (nth args 2) "")) (body (if has-doc (nth args 3) (nth args 2)))) (let ((adef (make-action-def name params doc body env))) - (env-set! env (str "action:" name) adef) + (env-bind! env (str "action:" name) adef) adef)))) @@ -163,7 +163,7 @@ (nth args (+ idx 1)))))) (range 1 max-i 2))) (let ((pdef (make-page-def name slots env))) - (env-set! env (str "page:" name) pdef) + (env-bind! env (str "page:" name) pdef) pdef)))) @@ -266,7 +266,7 @@ (bindings (stream-chunk-bindings chunk))) (for-each (fn ((key :as string)) - (env-set! env (normalize-binding-key key) + (env-bind! env (normalize-binding-key key) (get bindings key))) (keys bindings)) env))) diff --git a/web/orchestration.sx b/web/orchestration.sx index 3875f95..e8f7784 100644 --- a/web/orchestration.sx +++ b/web/orchestration.sx @@ -1103,9 +1103,9 @@ (dom-listen el event-name (fn (e) (let ((handler-env (env-extend (dict)))) - (env-set! handler-env "event" e) - (env-set! handler-env "this" el) - (env-set! handler-env "detail" (event-detail e)) + (env-bind! handler-env "event" e) + (env-bind! handler-env "this" el) + (env-bind! handler-env "detail" (event-detail e)) (for-each (fn (expr) (eval-expr expr handler-env)) exprs)))))))))) diff --git a/web/tests/test-cek-reactive.sx b/web/tests/test-cek-reactive.sx index 7d8ef41..9a63344 100644 --- a/web/tests/test-cek-reactive.sx +++ b/web/tests/test-cek-reactive.sx @@ -39,7 +39,7 @@ (defsuite "deref signal without reactive-reset" (deftest "deref signal returns current value" (let ((s (signal 99))) - (env-set! (test-env) "test-sig" s) + (env-bind! (test-env) "test-sig" s) (let ((result (eval-expr-cek (sx-parse-one "(deref test-sig)") (test-env)))) @@ -47,7 +47,7 @@ (deftest "deref signal in expression returns computed value" (let ((s (signal 10))) - (env-set! (test-env) "test-sig" s) + (env-bind! (test-env) "test-sig" s) (let ((result (eval-expr-cek (sx-parse-one "(+ 5 (deref test-sig))") (test-env)))) @@ -67,7 +67,7 @@ (make-cek-state (sx-parse-one "(deref test-sig)") (let ((e (env-extend (test-env)))) - (env-set! e "test-sig" s) + (env-bind! e "test-sig" s) e) (list (make-reactive-reset-frame (test-env) @@ -83,7 +83,7 @@ ;; Set up reactive-reset with tracking update-fn (scope-push! "sx-island-scope" nil) (let ((e (env-extend (test-env)))) - (env-set! e "test-sig" s) + (env-bind! e "test-sig" s) (cek-run (make-cek-state (sx-parse-one "(deref test-sig)") @@ -107,7 +107,7 @@ (update-calls (list))) (scope-push! "sx-island-scope" nil) (let ((e (env-extend (test-env)))) - (env-set! e "test-sig" s) + (env-bind! e "test-sig" s) ;; (str "val=" (deref test-sig)) — continuation captures (str "val=" [HOLE]) (let ((result (cek-run (make-cek-state @@ -137,7 +137,7 @@ ;; Create island scope with collector that accumulates disposers (scope-push! "sx-island-scope" (fn (d) (append! disposers d))) (let ((e (env-extend (test-env)))) - (env-set! e "test-sig" s) + (env-bind! e "test-sig" s) (cek-run (make-cek-state (sx-parse-one "(deref test-sig)") @@ -266,7 +266,7 @@ (deftest "for-each through CEK" (let ((log (list))) - (env-set! (test-env) "test-log" log) + (env-bind! (test-env) "test-log" log) (eval-expr-cek (sx-parse-one "(for-each (fn (x) (append! test-log x)) (list 1 2 3))") (test-env))