diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index dbc2a17..398f705 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-07T00:04:07Z"; + var SX_VERSION = "2026-03-07T00:20:00Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -177,7 +177,8 @@ function envExtend(env) { return merge(env); } function envMerge(base, overlay) { return merge(base, overlay); } - function dictSet(d, k, v) { d[k] = v; } + function dictSet(d, k, v) { d[k] = v; return v; } + PRIMITIVES["dict-set!"] = dictSet; function dictGet(d, k) { var v = d[k]; return v !== undefined ? v : NIL; } // Render-expression detection — lets the evaluator delegate to the active adapter. @@ -452,6 +453,7 @@ var range = PRIMITIVES["range"]; function zip(a, b) { var r = []; for (var i = 0; i < Math.min(a.length, b.length); i++) r.push([a[i], b[i]]); return r; } function append_b(arr, x) { arr.push(x); return arr; } + PRIMITIVES["append!"] = append_b; var apply = function(f, args) { return f.apply(null, args); }; // Additional primitive aliases used by adapter/engine transpiled code diff --git a/shared/sx/primitives.py b/shared/sx/primitives.py index cfc6d3d..cbd356d 100644 --- a/shared/sx/primitives.py +++ b/shared/sx/primitives.py @@ -386,6 +386,11 @@ def prim_cons(x: Any, coll: Any) -> list: def prim_append(coll: Any, x: Any) -> list: return list(coll) + [x] if coll else [x] +@register_primitive("append!") +def prim_append_mut(coll: Any, x: Any) -> list: + coll.append(x) + return coll + @register_primitive("chunk-every") def prim_chunk_every(coll: Any, n: Any) -> list: n = int(n) @@ -439,6 +444,13 @@ def prim_dissoc(d: Any, *keys_to_remove: Any) -> dict: result.pop(key, None) return result +@register_primitive("dict-set!") +def prim_dict_set_mut(d: Any, key: Any, val: Any) -> Any: + if isinstance(key, Keyword): + key = key.name + d[key] = val + return val + @register_primitive("into") def prim_into(target: Any, coll: Any) -> Any: if isinstance(target, list): diff --git a/shared/sx/ref/bootstrap_js.py b/shared/sx/ref/bootstrap_js.py index e1704ea..739b9a3 100644 --- a/shared/sx/ref/bootstrap_js.py +++ b/shared/sx/ref/bootstrap_js.py @@ -1711,7 +1711,8 @@ PLATFORM_JS_PRE = ''' function envExtend(env) { return merge(env); } function envMerge(base, overlay) { return merge(base, overlay); } - function dictSet(d, k, v) { d[k] = v; } + function dictSet(d, k, v) { d[k] = v; return v; } + PRIMITIVES["dict-set!"] = dictSet; function dictGet(d, k) { var v = d[k]; return v !== undefined ? v : NIL; } // Render-expression detection — lets the evaluator delegate to the active adapter. @@ -1791,6 +1792,7 @@ PLATFORM_JS_POST = ''' var range = PRIMITIVES["range"]; function zip(a, b) { var r = []; for (var i = 0; i < Math.min(a.length, b.length); i++) r.push([a[i], b[i]]); return r; } function append_b(arr, x) { arr.push(x); return arr; } + PRIMITIVES["append!"] = append_b; var apply = function(f, args) { return f.apply(null, args); }; // Additional primitive aliases used by adapter/engine transpiled code diff --git a/shared/sx/ref/primitives.sx b/shared/sx/ref/primitives.sx index 91e2904..6035a66 100644 --- a/shared/sx/ref/primitives.sx +++ b/shared/sx/ref/primitives.sx @@ -384,6 +384,11 @@ :returns "list" :doc "Append x to end of coll (returns new list).") +(define-primitive "append!" + :params (coll x) + :returns "list" + :doc "Mutate coll by appending x in-place. Returns coll.") + (define-primitive "chunk-every" :params (coll n) :returns "list" @@ -426,6 +431,11 @@ :returns "dict" :doc "Return new dict with keys removed.") +(define-primitive "dict-set!" + :params (d key val) + :returns "any" + :doc "Mutate dict d by setting key to val in-place. Returns val.") + (define-primitive "into" :params (target coll) :returns "any" diff --git a/shared/sx/tests/test_page_data.py b/shared/sx/tests/test_page_data.py index d67d0ad..7cd2280 100644 --- a/shared/sx/tests/test_page_data.py +++ b/shared/sx/tests/test_page_data.py @@ -305,16 +305,6 @@ class TestDataCache: self._time = current_time_ms env["now-ms"] = lambda: self._time - # Mutating primitives needed by cache (available in JS, not bare Python) - def _dict_set(d, k, v): - d[k] = v - return v - def _append_b(lst, item): - lst.append(item) - return lst - env["dict-set!"] = _dict_set - env["append!"] = _append_b - # Define the cache functions from orchestration.sx cache_src = """ (define _page-data-cache (dict))