diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 93f8d5b..25394b7 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-16T09:43:09Z"; + var SX_VERSION = "2026-03-16T10:42:47Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -2030,262 +2030,12 @@ PRIMITIVES["trampoline"] = trampoline; // === Transpiled from stdlib (library functions from former primitives) === - // not - var not = function(x) { return (isSxTruthy(x) ? false : true); }; -PRIMITIVES["not"] = not; - - // != - var != = function(a, b) { return !isSxTruthy((a == b)); }; -PRIMITIVES["!="] = !=; - - // <= - var <= = function(a, b) { return sxOr((a < b), (a == b)); }; -PRIMITIVES["<="] = <=; - - // >= - var >= = function(a, b) { return sxOr((a > b), (a == b)); }; -PRIMITIVES[">="] = >=; - - // eq? - var eq_p = =; -PRIMITIVES["eq?"] = eq_p; - - // eqv? - var eqv_p = =; -PRIMITIVES["eqv?"] = eqv_p; - - // equal? - var equal_p = =; -PRIMITIVES["equal?"] = equal_p; - - // nil? - var isNil = function(x) { return (typeOf(x) == "nil"); }; -PRIMITIVES["nil?"] = isNil; - - // boolean? - var boolean_p = function(x) { return (typeOf(x) == "boolean"); }; -PRIMITIVES["boolean?"] = boolean_p; - - // number? - var isNumber = function(x) { return (typeOf(x) == "number"); }; -PRIMITIVES["number?"] = isNumber; - - // string? - var isString = function(x) { return (typeOf(x) == "string"); }; -PRIMITIVES["string?"] = isString; - - // list? - var isList = function(x) { return (typeOf(x) == "list"); }; -PRIMITIVES["list?"] = isList; - - // dict? - var isDict = function(x) { return (typeOf(x) == "dict"); }; -PRIMITIVES["dict?"] = isDict; - - // continuation? - var continuation_p = function(x) { return (typeOf(x) == "continuation"); }; -PRIMITIVES["continuation?"] = continuation_p; - - // zero? - var isZero = function(n) { return (n == 0); }; -PRIMITIVES["zero?"] = isZero; - - // odd? - var isOdd = function(n) { return ((n % 2) == 1); }; -PRIMITIVES["odd?"] = isOdd; - - // even? - var isEven = function(n) { return ((n % 2) == 0); }; -PRIMITIVES["even?"] = isEven; - - // inc - var inc = function(n) { return (n + 1); }; -PRIMITIVES["inc"] = inc; - - // dec - var dec = function(n) { return (n - 1); }; -PRIMITIVES["dec"] = dec; - - // abs - var abs = function(x) { return (isSxTruthy((x < 0)) ? (-x) : x); }; -PRIMITIVES["abs"] = abs; - - // ceil - var ceil = function(x) { return (function() { - var f = floor(x); - return (isSxTruthy((x == f)) ? f : (f + 1)); -})(); }; -PRIMITIVES["ceil"] = ceil; - - // round - var round = function(x, ndigits) { return (isSxTruthy(isNil(ndigits)) ? floor((x + 0.5)) : (function() { - var f = pow(10, ndigits); - return (floor(((x * f) + 0.5)) / f); -})()); }; -PRIMITIVES["round"] = round; - - // min - var min = function(a, b) { return (isSxTruthy((a < b)) ? a : b); }; -PRIMITIVES["min"] = min; - - // max - var max = function(a, b) { return (isSxTruthy((a > b)) ? a : b); }; -PRIMITIVES["max"] = max; - - // clamp - var clamp = function(x, lo, hi) { return max(lo, min(hi, x)); }; -PRIMITIVES["clamp"] = clamp; - - // first - var first = function(coll) { return (isSxTruthy((isSxTruthy(coll) && (len(coll) > 0))) ? get(coll, 0) : NIL); }; -PRIMITIVES["first"] = first; - - // last - var last = function(coll) { return (isSxTruthy((isSxTruthy(coll) && (len(coll) > 0))) ? get(coll, (len(coll) - 1)) : NIL); }; -PRIMITIVES["last"] = last; - - // rest - var rest = function(coll) { return (isSxTruthy(coll) ? slice(coll, 1) : []); }; -PRIMITIVES["rest"] = rest; - - // nth - var nth = function(coll, n) { return (isSxTruthy((isSxTruthy(coll) && isSxTruthy((n >= 0)) && (n < len(coll)))) ? get(coll, n) : NIL); }; -PRIMITIVES["nth"] = nth; - - // empty? - var isEmpty = function(coll) { return sxOr(isNil(coll), (len(coll) == 0)); }; -PRIMITIVES["empty?"] = isEmpty; - - // cons - var cons = function(x, coll) { return concat([x], sxOr(coll, [])); }; -PRIMITIVES["cons"] = cons; - - // append - var append = function(coll, x) { return (isSxTruthy(isList(x)) ? concat(coll, x) : concat(coll, [x])); }; -PRIMITIVES["append"] = append; - - // reverse - var reverse = function(coll) { return (function() { - var result = []; - var i = (len(coll) - 1); - (function() { - [i(i)]; - return (isSxTruthy((i >= 0)) ? (append_b(result, get(coll, i)), loop((i - 1))) : NIL); -})(); - return result; -})(); }; -PRIMITIVES["reverse"] = reverse; - - // flatten - var flatten = function(coll) { return (function() { - var result = []; - { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; (isSxTruthy(isList(x)) ? forEach(function(y) { return append_b(result, y); }, x) : append_b(result, x)); } } - return result; -})(); }; -PRIMITIVES["flatten"] = flatten; - - // range - var range = function(start, end, step) { return (function() { - var s = (isSxTruthy(isNil(step)) ? 1 : step); - var result = []; - (function() { - [i(start)]; - return (isSxTruthy((i < end)) ? (append_b(result, i), loop((i + s))) : NIL); -})(); - return result; -})(); }; -PRIMITIVES["range"] = range; - - // chunk-every - var chunkEvery = function(coll, n) { return (function() { - var result = []; - var clen = len(coll); - (function() { - [i(0)]; - return (isSxTruthy((i < clen)) ? (append_b(result, slice(coll, i, min((i + n), clen))), loop((i + n))) : NIL); -})(); - return result; -})(); }; -PRIMITIVES["chunk-every"] = chunkEvery; - - // zip-pairs - var zipPairs = function(coll) { return (function() { - var result = []; - var clen = len(coll); - (function() { - [i(0)]; - return (isSxTruthy((i < (clen - 1))) ? (append_b(result, [get(coll, i), get(coll, (i + 1))]), loop((i + 1))) : NIL); -})(); - return result; -})(); }; -PRIMITIVES["zip-pairs"] = zipPairs; - - // vals - var vals = function(d) { return (function() { - var result = []; - { var _c = keys(d); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; result.push(get(d, k)); } } - return result; -})(); }; -PRIMITIVES["vals"] = vals; - - // has-key? - var dictHas = function(d, key) { return some(function(k) { return (k == key); }, keys(d)); }; -PRIMITIVES["has-key?"] = dictHas; - - // merge - var merge = function(a, b) { return (function() { - var result = {}; - if (isSxTruthy(a)) { - { var _c = keys(a); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; result[k] = get(a, k); } } -} - if (isSxTruthy(b)) { - { var _c = keys(b); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; result[k] = get(b, k); } } -} - return result; -})(); }; -PRIMITIVES["merge"] = merge; - - // assoc - var assoc = function(d, key, val) { return (function() { - var result = {}; - if (isSxTruthy(d)) { - { var _c = keys(d); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; result[k] = get(d, k); } } -} - result[key] = val; - return result; -})(); }; -PRIMITIVES["assoc"] = assoc; - - // dissoc - var dissoc = function(d, key) { return (function() { - var result = {}; - { var _c = keys(d); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; if (isSxTruthy((k != key))) { - result[k] = get(d, k); -} } } - return result; -})(); }; -PRIMITIVES["dissoc"] = dissoc; - - // into - var into = function(target, coll) { return (isSxTruthy(isList(target)) ? (isSxTruthy(isList(coll)) ? concat(coll, []) : (function() { - var result = []; - { var _c = keys(coll); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; result.push([k, get(coll, k)]); } } - return result; -})()) : (isSxTruthy(isDict(target)) ? (function() { - var result = {}; - { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy(isList(pair)) && (len(pair) >= 2)))) { - result[get(pair, 0)] = get(pair, 1); -} } } - return result; -})() : target)); }; -PRIMITIVES["into"] = into; - // upcase - var upcase = upper; + var upcase = function(s) { return upper(s); }; PRIMITIVES["upcase"] = upcase; // downcase - var downcase = lower; + var downcase = function(s) { return lower(s); }; PRIMITIVES["downcase"] = downcase; // string-length @@ -2312,44 +2062,20 @@ PRIMITIVES["starts-with?"] = startsWith; })(); }; PRIMITIVES["ends-with?"] = endsWith; - // split - var split = function(s, sep) { return (function() { - var separator = (isSxTruthy(isNil(sep)) ? " " : sep); - var result = []; - var slen = len(s); - var seplen = len(separator); - return (function() { - [start(0)]; - return (function() { - var idx = indexOf_(s, separator, start); - return (isSxTruthy((idx == -1)) ? (append_b(result, slice(s, start)), result) : (append_b(result, slice(s, start, idx)), loop((idx + seplen)))); -})(); -})(); -})(); }; -PRIMITIVES["split"] = split; - - // join - var join = function(sep, coll) { return (function() { - var result = ""; - { var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; result = (isSxTruthy((result == "")) ? (String(x)) : (String(result) + String(sep) + String(x))); } } - return result; -})(); }; -PRIMITIVES["join"] = join; - - // replace - var replace_ = function(s, old, new_) { return join(new_, split(s, old)); }; -PRIMITIVES["replace"] = replace_; - - // contains? - var contains = function(coll, key) { return (isSxTruthy(isString(coll)) ? (indexOf_(coll, (String(key))) != -1) : (isSxTruthy(isDict(coll)) ? dictHas(coll, key) : (isSxTruthy(isList(coll)) ? some(function(x) { return (x == key); }, coll) : false))); }; -PRIMITIVES["contains?"] = contains; - // pluralize var pluralize = function(count, singular, plural) { return (isSxTruthy((count == 1)) ? sxOr(singular, "") : sxOr(plural, "s")); }; PRIMITIVES["pluralize"] = pluralize; // escape - var escape = function(s) { return >((String(s)), replace_("&", "&"), replace_("<", "<"), replace_(">", ">"), replace_("\"", """), replace_("'", "'")); }; + var escape = function(s) { return (function() { + var r = (String(s)); + r = replace_(r, "&", "&"); + r = replace_(r, "<", "<"); + r = replace_(r, ">", ">"); + r = replace_(r, "\"", """); + r = replace_(r, "'", "'"); + return r; +})(); }; PRIMITIVES["escape"] = escape; // parse-datetime @@ -2574,7 +2300,14 @@ PRIMITIVES["is-render-expr?"] = isRenderExpr; PRIMITIVES["merge-spread-attrs"] = mergeSpreadAttrs; // escape-html - var escapeHtml = function(s) { return >((String(s)), replace_("&", "&"), replace_("<", "<"), replace_(">", ">"), replace_("\"", """)); }; + var escapeHtml = function(s) { return (function() { + var r = (String(s)); + r = replace_(r, "&", "&"); + r = replace_(r, "<", "<"); + r = replace_(r, ">", ">"); + r = replace_(r, "\"", """); + return r; +})(); }; PRIMITIVES["escape-html"] = escapeHtml; // escape-attr diff --git a/spec/render.sx b/spec/render.sx index 4112096..9b48609 100644 --- a/spec/render.sx +++ b/spec/render.sx @@ -249,11 +249,12 @@ (define escape-html (fn (s) - (-> (str s) - (replace "&" "&") - (replace "<" "<") - (replace ">" ">") - (replace "\"" """)))) + (let ((r (str s))) + (set! r (replace r "&" "&")) + (set! r (replace r "<" "<")) + (set! r (replace r ">" ">")) + (set! r (replace r "\"" """)) + r))) (define escape-attr (fn (s) diff --git a/spec/stdlib.sx b/spec/stdlib.sx index dd7495d..00112d4 100644 --- a/spec/stdlib.sx +++ b/spec/stdlib.sx @@ -1,292 +1,27 @@ ;; ========================================================================== ;; stdlib.sx — Standard library functions ;; -;; Every function here CAN be expressed in SX using the irreducible -;; primitive set. They are library functions, not primitives. +;; Functions expressed in SX using the irreducible primitive set. ;; -;; These were previously platform-provided primitives. Moving them to -;; SX tightens the strange loop — less out-of-band, more auditable, -;; portable, and verifiable. +;; CONSTRAINT: Replacing a native callable (PRIMITIVES entry) with a +;; transpiled SX function can break the transpiled evaluator when: +;; 1. The function is called inside shift/reset (changes CEK capture) +;; 2. The function is used by platform internals (circular dependency) +;; 3. The transpiler doesn't support named-let (loop patterns) ;; -;; Depends on: evaluator.sx (special forms) -;; Must load before: render.sx, freeze.sx, types.sx, user code -;; -;; Irreducible primitives (the ones that CANNOT be written in SX): -;; Arithmetic: + - * / mod floor pow sqrt -;; Comparison: = < > -;; Types: type-of symbol-name keyword-name -;; Strings: str slice index-of upper lower trim char-from-code -;; Collections: list dict concat get len keys dict-set! append! -;; I/O & host: random-int json-encode format-date parse-int -;; format-decimal strip-tags sx-parse error apply +;; Only functions safe from all three constraints are moved here. +;; The rest remain as host-provided PRIMITIVES for now. ;; ========================================================================== ;; -------------------------------------------------------------------------- -;; Logic +;; String predicates and aliases ;; -------------------------------------------------------------------------- -(define not - (fn (x) (if x false true))) - - -;; -------------------------------------------------------------------------- -;; Comparison -;; -------------------------------------------------------------------------- - -(define != - (fn (a b) (not (= a b)))) - -(define <= - (fn (a b) (or (< a b) (= a b)))) - -(define >= - (fn (a b) (or (> a b) (= a b)))) - -;; Aliases — SX uses structural equality for all three -(define eq? =) -(define eqv? =) -(define equal? =) - - -;; -------------------------------------------------------------------------- -;; Type predicates -;; -------------------------------------------------------------------------- - -(define nil? - (fn (x) (= (type-of x) "nil"))) - -(define boolean? - (fn (x) (= (type-of x) "boolean"))) - -(define number? - (fn (x) (= (type-of x) "number"))) - -(define string? - (fn (x) (= (type-of x) "string"))) - -(define list? - (fn (x) (= (type-of x) "list"))) - -(define dict? - (fn (x) (= (type-of x) "dict"))) - -(define continuation? - (fn (x) (= (type-of x) "continuation"))) - - -;; -------------------------------------------------------------------------- -;; Numeric predicates -;; -------------------------------------------------------------------------- - -(define zero? - (fn (n) (= n 0))) - -(define odd? - (fn (n) (= (mod n 2) 1))) - -(define even? - (fn (n) (= (mod n 2) 0))) - - -;; -------------------------------------------------------------------------- -;; Arithmetic -;; -------------------------------------------------------------------------- - -(define inc - (fn (n) (+ n 1))) - -(define dec - (fn (n) (- n 1))) - -(define abs - (fn (x) (if (< x 0) (- x) x))) - -(define ceil - (fn (x) - (let ((f (floor x))) - (if (= x f) f (+ f 1))))) - -(define round - (fn (x ndigits) - (if (nil? ndigits) - (floor (+ x 0.5)) - (let ((f (pow 10 ndigits))) - (/ (floor (+ (* x f) 0.5)) f))))) - -(define min - (fn (a b) (if (< a b) a b))) - -(define max - (fn (a b) (if (> a b) a b))) - -(define clamp - (fn (x lo hi) (max lo (min hi x)))) - - -;; -------------------------------------------------------------------------- -;; Collection accessors -;; -------------------------------------------------------------------------- - -(define first - (fn (coll) - (if (and coll (> (len coll) 0)) (get coll 0) nil))) - -(define last - (fn (coll) - (if (and coll (> (len coll) 0)) - (get coll (- (len coll) 1)) - nil))) - -(define rest - (fn (coll) (if coll (slice coll 1) (list)))) - -(define nth - (fn (coll n) - (if (and coll (>= n 0) (< n (len coll))) - (get coll n) - nil))) - -(define empty? - (fn (coll) (or (nil? coll) (= (len coll) 0)))) - -(define cons - (fn (x coll) (concat (list x) (or coll (list))))) - -(define append - (fn (coll x) - (if (list? x) (concat coll x) (concat coll (list x))))) - - -;; -------------------------------------------------------------------------- -;; Collection transforms -;; -------------------------------------------------------------------------- - -(define reverse - (fn (coll) - (let ((result (list)) - (i (- (len coll) 1))) - (let loop ((i i)) - (when (>= i 0) - (append! result (get coll i)) - (loop (- i 1)))) - result))) - -(define flatten - (fn (coll) - (let ((result (list))) - (for-each - (fn (x) - (if (list? x) - (for-each (fn (y) (append! result y)) x) - (append! result x))) - coll) - result))) - -(define range - (fn (start end step) - (let ((s (if (nil? step) 1 step)) - (result (list))) - (let loop ((i start)) - (when (< i end) - (append! result i) - (loop (+ i s)))) - result))) - -(define chunk-every - (fn (coll n) - (let ((result (list)) - (clen (len coll))) - (let loop ((i 0)) - (when (< i clen) - (append! result (slice coll i (min (+ i n) clen))) - (loop (+ i n)))) - result))) - -(define zip-pairs - (fn (coll) - (let ((result (list)) - (clen (len coll))) - (let loop ((i 0)) - (when (< i (- clen 1)) - (append! result (list (get coll i) (get coll (+ i 1)))) - (loop (+ i 1)))) - result))) - - -;; -------------------------------------------------------------------------- -;; Dict operations -;; -------------------------------------------------------------------------- - -(define vals - (fn (d) - (let ((result (list))) - (for-each (fn (k) (append! result (get d k))) (keys d)) - result))) - -(define has-key? - (fn (d key) - (some (fn (k) (= k key)) (keys d)))) - -(define merge - (fn (a b) - (let ((result (dict))) - (when a - (for-each (fn (k) (dict-set! result k (get a k))) (keys a))) - (when b - (for-each (fn (k) (dict-set! result k (get b k))) (keys b))) - result))) - -(define assoc - (fn (d key val) - (let ((result (dict))) - (when d - (for-each (fn (k) (dict-set! result k (get d k))) (keys d))) - (dict-set! result key val) - result))) - -(define dissoc - (fn (d key) - (let ((result (dict))) - (for-each - (fn (k) - (when (!= k key) - (dict-set! result k (get d k)))) - (keys d)) - result))) - -(define into - (fn (target coll) - (cond - (list? target) - (if (list? coll) - (concat coll (list)) - (let ((result (list))) - (for-each (fn (k) (append! result (list k (get coll k)))) (keys coll)) - result)) - (dict? target) - (let ((result (dict))) - (for-each - (fn (pair) - (when (and (list? pair) (>= (len pair) 2)) - (dict-set! result (get pair 0) (get pair 1)))) - coll) - result) - :else target))) - - -;; -------------------------------------------------------------------------- -;; String operations -;; -------------------------------------------------------------------------- - -(define upcase upper) -(define downcase lower) - -(define string-length - (fn (s) (len s))) - -(define substring - (fn (s start end) (slice s start end))) +(define upcase (fn (s) (upper s))) +(define downcase (fn (s) (lower s))) +(define string-length (fn (s) (len s))) +(define substring (fn (s start end) (slice s start end))) (define string-contains? (fn (s needle) (!= (index-of s needle) -1))) @@ -301,42 +36,6 @@ (if (< slen plen) false (= (slice s (- slen plen)) suffix))))) -(define split - (fn (s sep) - (let ((separator (if (nil? sep) " " sep)) - (result (list)) - (slen (len s)) - (seplen (len separator))) - (let loop ((start 0)) - (let ((idx (index-of s separator start))) - (if (= idx -1) - (do (append! result (slice s start)) result) - (do (append! result (slice s start idx)) - (loop (+ idx seplen))))))))) - -(define join - (fn (sep coll) - (let ((result "")) - (for-each - (fn (x) - (set! result (if (= result "") - (str x) - (str result sep x)))) - coll) - result))) - -(define replace - (fn (s old new) - (join new (split s old)))) - -(define contains? - (fn (coll key) - (cond - (string? coll) (!= (index-of coll (str key)) -1) - (dict? coll) (has-key? coll key) - (list? coll) (some (fn (x) (= x key)) coll) - :else false))) - ;; -------------------------------------------------------------------------- ;; Text utilities @@ -350,12 +49,13 @@ (define escape (fn (s) - (-> (str s) - (replace "&" "&") - (replace "<" "<") - (replace ">" ">") - (replace "\"" """) - (replace "'" "'")))) + (let ((r (str s))) + (set! r (replace r "&" "&")) + (set! r (replace r "<" "<")) + (set! r (replace r ">" ">")) + (set! r (replace r "\"" """)) + (set! r (replace r "'" "'")) + r))) (define parse-datetime (fn (s) (if s (str s) nil)))