diff --git a/hosts/javascript/bootstrap.py b/hosts/javascript/bootstrap.py index 57b5a8f..b7d1a95 100644 --- a/hosts/javascript/bootstrap.py +++ b/hosts/javascript/bootstrap.py @@ -131,7 +131,8 @@ def compile_ref_to_js( # evaluator.sx = merged frames + eval utilities + CEK machine sx_files = [ ("evaluator.sx", "evaluator (frames + eval + CEK)"), - ("stdlib.sx", "stdlib (library functions from former primitives)"), + # stdlib.sx is loaded at runtime via eval, not transpiled — + # transpiling it would shadow native PRIMITIVES in module scope. ("freeze.sx", "freeze (serializable state boundaries)"), ("content.sx", "content (content-addressed computation)"), ("render.sx", "render (core)"), @@ -233,6 +234,28 @@ def compile_ref_to_js( if has_cek: parts.append(CEK_FIXUPS_JS) + # Load stdlib.sx via eval (NOT transpiled) so defines go into the eval + # env, not the module scope. This prevents stdlib functions from + # shadowing native PRIMITIVES aliases used by transpiled evaluator code. + stdlib_path = _find_sx("stdlib.sx") + if stdlib_path: + with open(stdlib_path) as f: + stdlib_src = f.read() + # Escape for JS string literal + stdlib_escaped = stdlib_src.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + parts.append(f'\n // === stdlib.sx (eval\'d at runtime, not transpiled) ===') + parts.append(f' (function() {{') + parts.append(f' var src = "{stdlib_escaped}";') + parts.append(f' var forms = sxParse(src);') + parts.append(f' var tmpEnv = merge({{}}, PRIMITIVES);') + parts.append(f' for (var i = 0; i < forms.length; i++) {{') + parts.append(f' trampoline(evalExpr(forms[i], tmpEnv));') + parts.append(f' }}') + parts.append(f' for (var k in tmpEnv) {{') + parts.append(f' if (!PRIMITIVES[k]) PRIMITIVES[k] = tmpEnv[k];') + parts.append(f' }}') + parts.append(f' }})();\n') + for name in ("dom", "engine", "orchestration", "boot"): if name in adapter_set and name in adapter_platform: parts.append(adapter_platform[name]) diff --git a/hosts/javascript/platform.py b/hosts/javascript/platform.py index f242aee..81627fe 100644 --- a/hosts/javascript/platform.py +++ b/hosts/javascript/platform.py @@ -1412,6 +1412,11 @@ PLATFORM_JS_POST = ''' var get = PRIMITIVES["get"]; var assoc = PRIMITIVES["assoc"]; var range = PRIMITIVES["range"]; + var floor = PRIMITIVES["floor"]; + var pow = PRIMITIVES["pow"]; + var mod = PRIMITIVES["mod"]; + var indexOf_ = PRIMITIVES["index-of"]; + var hasKey = PRIMITIVES["has-key?"]; 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; } var apply = function(f, args) { @@ -1802,7 +1807,7 @@ PLATFORM_DOM_JS = """ // Wire CEK render hooks — evaluator checks _renderCheck/_renderFn instead of // the old renderActiveP()/isRenderExpr()/renderExpr() triple. - _renderCheck = function(expr, env) { return isRenderExpr(expr); }; + _renderCheck = function(expr, env) { return _renderMode && isRenderExpr(expr); }; _renderFn = function(expr, env) { return renderToDom(expr, env, null); }; var SVG_NS = "http://www.w3.org/2000/svg"; diff --git a/hosts/javascript/transpiler.sx b/hosts/javascript/transpiler.sx index 90f82c9..05bd381 100644 --- a/hosts/javascript/transpiler.sx +++ b/hosts/javascript/transpiler.sx @@ -109,6 +109,9 @@ "dom-document" "domDocument" "dom-window" "domWindow" "dom-head" "domHead" + "!=" "notEqual_" + "<=" "lte_" + ">=" "gte_" "*batch-depth*" "_batchDepth" "*batch-queue*" "_batchQueue" "*store-registry*" "_storeRegistry" @@ -1112,19 +1115,50 @@ (define js-emit-let (fn (expr) - (let ((bindings (nth expr 1)) - (body (rest (rest expr)))) - (let ((binding-lines (js-parse-let-bindings bindings)) - (body-strs (list))) - (begin - (for-each (fn (b) (append! body-strs (str " " (js-statement b)))) - (slice body 0 (- (len body) 1))) - (append! body-strs (str " return " (js-expr (last body)) ";")) - (str "(function() {\n" - (join "\n" binding-lines) - (if (empty? binding-lines) "" "\n") - (join "\n" body-strs) - "\n})()")))))) + ;; Detect named let: (let name ((x init) ...) body...) + (if (= (type-of (nth expr 1)) "symbol") + (js-emit-named-let expr) + (let ((bindings (nth expr 1)) + (body (rest (rest expr)))) + (let ((binding-lines (js-parse-let-bindings bindings)) + (body-strs (list))) + (begin + (for-each (fn (b) (append! body-strs (str " " (js-statement b)))) + (slice body 0 (- (len body) 1))) + (append! body-strs (str " return " (js-expr (last body)) ";")) + (str "(function() {\n" + (join "\n" binding-lines) + (if (empty? binding-lines) "" "\n") + (join "\n" body-strs) + "\n})()"))))))) + +;; Named let: (let loop-name ((param init) ...) body...) +;; Emits a named IIFE: (function loop(p1, p2) { body })(init1, init2) +(define js-emit-named-let + (fn (expr) + (let ((loop-name (symbol-name (nth expr 1))) + (bindings (nth expr 2)) + (body (slice expr 3)) + (params (list)) + (inits (list))) + ;; Parse bindings — Scheme-style ((name val) ...) + (for-each + (fn (b) + (let ((pname (if (= (type-of (first b)) "symbol") + (symbol-name (first b)) + (str (first b))))) + (append! params (js-mangle pname)) + (append! inits (js-expr (nth b 1))))) + bindings) + ;; Emit body statements + return last + (let ((body-strs (list)) + (mangled-name (js-mangle loop-name))) + (for-each (fn (b) (append! body-strs (str " " (js-statement b)))) + (slice body 0 (- (len body) 1))) + (append! body-strs (str " return " (js-expr (last body)) ";")) + (str "(function " mangled-name "(" (join ", " params) ") {\n" + (join "\n" body-strs) + "\n})(" (join ", " inits) ")"))))) (define js-parse-let-bindings (fn (bindings) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 25394b7..ab9d512 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-16T10:42:47Z"; + var SX_VERSION = "2026-03-17T17:08:05Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -563,6 +563,11 @@ var get = PRIMITIVES["get"]; var assoc = PRIMITIVES["assoc"]; var range = PRIMITIVES["range"]; + var floor = PRIMITIVES["floor"]; + var pow = PRIMITIVES["pow"]; + var mod = PRIMITIVES["mod"]; + var indexOf_ = PRIMITIVES["index-of"]; + var hasKey = PRIMITIVES["has-key?"]; 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; } var apply = function(f, args) { @@ -1635,7 +1640,7 @@ PRIMITIVES["step-sf-deref"] = stepSfDeref; // cek-call var cekCall = function(f, args) { return (function() { var a = (isSxTruthy(isNil(args)) ? [] : args); - return (isSxTruthy(isNil(f)) ? NIL : (isSxTruthy(isLambda(f)) ? cekRun(continueWithCall(f, a, {}, a, [])) : (isSxTruthy(isCallable(f)) ? apply(f, a) : NIL))); + return (isSxTruthy(isNil(f)) ? NIL : (isSxTruthy(sxOr(isLambda(f), isCallable(f))) ? cekRun(continueWithCall(f, a, {}, a, [])) : NIL)); })(); }; PRIMITIVES["cek-call"] = cekCall; @@ -2028,68 +2033,6 @@ PRIMITIVES["eval-expr"] = evalExpr; PRIMITIVES["trampoline"] = trampoline; - // === Transpiled from stdlib (library functions from former primitives) === - - // upcase - var upcase = function(s) { return upper(s); }; -PRIMITIVES["upcase"] = upcase; - - // downcase - var downcase = function(s) { return lower(s); }; -PRIMITIVES["downcase"] = downcase; - - // string-length - var stringLength = function(s) { return len(s); }; -PRIMITIVES["string-length"] = stringLength; - - // substring - var substring = function(s, start, end) { return slice(s, start, end); }; -PRIMITIVES["substring"] = substring; - - // string-contains? - var stringContains_p = function(s, needle) { return (indexOf_(s, needle) != -1); }; -PRIMITIVES["string-contains?"] = stringContains_p; - - // starts-with? - var startsWith = function(s, prefix) { return (indexOf_(s, prefix) == 0); }; -PRIMITIVES["starts-with?"] = startsWith; - - // ends-with? - var endsWith = function(s, suffix) { return (function() { - var slen = len(s); - var plen = len(suffix); - return (isSxTruthy((slen < plen)) ? false : (slice(s, (slen - plen)) == suffix)); -})(); }; -PRIMITIVES["ends-with?"] = endsWith; - - // 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 (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 - var parseDatetime = function(s) { return (isSxTruthy(s) ? (String(s)) : NIL); }; -PRIMITIVES["parse-datetime"] = parseDatetime; - - // assert - var assert = function(condition, message) { if (isSxTruthy(!isSxTruthy(condition))) { - error(sxOr(message, "Assertion failed")); -} -return true; }; -PRIMITIVES["assert"] = assert; - - // === Transpiled from freeze (serializable state boundaries) === // freeze-registry @@ -5901,6 +5844,20 @@ PRIMITIVES["resource"] = resource; }; + // === stdlib.sx (eval'd at runtime, not transpiled) === + (function() { + var src = ";; ==========================================================================\n;; stdlib.sx — Standard library functions\n;;\n;; Every function here is expressed in SX using the irreducible primitive\n;; set. They are library functions — in band, auditable, portable.\n;;\n;; Depends on: evaluator.sx (special forms)\n;; Must load before: render.sx, freeze.sx, types.sx, user code\n;; ==========================================================================\n\n\n;; Logic + comparison: not, !=, <=, >= stay as primitives.\n;; Replacing them with SX lambdas changes behavior inside shift/reset\n;; because the transpiled evaluator code uses them directly.\n\n(define eq? (fn (a b) (= a b)))\n(define eqv? (fn (a b) (= a b)))\n(define equal? (fn (a b) (= a b)))\n\n\n;; --------------------------------------------------------------------------\n;; Type predicates\n;; --------------------------------------------------------------------------\n\n;; nil? stays as primitive — host's type-of uses it internally.\n\n(define boolean?\n (fn (x) (= (type-of x) \"boolean\")))\n\n(define number?\n (fn (x) (= (type-of x) \"number\")))\n\n(define string?\n (fn (x) (= (type-of x) \"string\")))\n\n(define list?\n (fn (x) (= (type-of x) \"list\")))\n\n(define dict?\n (fn (x) (= (type-of x) \"dict\")))\n\n(define continuation?\n (fn (x) (= (type-of x) \"continuation\")))\n\n(define zero?\n (fn (n) (= n 0)))\n\n(define odd?\n (fn (n) (= (mod n 2) 1)))\n\n(define even?\n (fn (n) (= (mod n 2) 0)))\n\n(define empty?\n (fn (coll) (or (nil? coll) (= (len coll) 0))))\n\n\n;; --------------------------------------------------------------------------\n;; Arithmetic\n;; --------------------------------------------------------------------------\n\n;; inc and dec stay as primitives — used inside continuation contexts.\n\n(define abs\n (fn (x) (if (< x 0) (- x) x)))\n\n(define ceil\n (fn (x)\n (let ((f (floor x)))\n (if (= x f) f (+ f 1)))))\n\n(define round\n (fn (x ndigits)\n (if (nil? ndigits)\n (floor (+ x 0.5))\n (let ((f (pow 10 ndigits)))\n (/ (floor (+ (* x f) 0.5)) f)))))\n\n(define min\n (fn (a b) (if (< a b) a b)))\n\n(define max\n (fn (a b) (if (> a b) a b)))\n\n(define clamp\n (fn (x lo hi) (max lo (min hi x))))\n\n\n;; --------------------------------------------------------------------------\n;; Collection accessors\n;; --------------------------------------------------------------------------\n\n(define first\n (fn (coll)\n (if (and coll (> (len coll) 0)) (get coll 0) nil)))\n\n(define last\n (fn (coll)\n (if (and coll (> (len coll) 0))\n (get coll (- (len coll) 1))\n nil)))\n\n(define rest\n (fn (coll) (if coll (slice coll 1) (list))))\n\n(define nth\n (fn (coll n)\n (if (and coll (>= n 0) (< n (len coll)))\n (get coll n)\n nil)))\n\n(define cons\n (fn (x coll) (concat (list x) (or coll (list)))))\n\n(define append\n (fn (coll x)\n (if (list? x) (concat coll x) (concat coll (list x)))))\n\n\n;; --------------------------------------------------------------------------\n;; Collection transforms\n;; --------------------------------------------------------------------------\n\n(define reverse\n (fn (coll)\n (reduce (fn (acc x) (cons x acc)) (list) coll)))\n\n(define flatten\n (fn (coll)\n (reduce\n (fn (acc x)\n (if (list? x) (concat acc x) (concat acc (list x))))\n (list) coll)))\n\n(define range\n (fn (start end step)\n (let ((s (if (nil? step) 1 step))\n (result (list)))\n (let loop ((i start))\n (when (< i end)\n (append! result i)\n (loop (+ i s))))\n result)))\n\n(define chunk-every\n (fn (coll n)\n (let ((result (list))\n (clen (len coll)))\n (let loop ((i 0))\n (when (< i clen)\n (append! result (slice coll i (min (+ i n) clen)))\n (loop (+ i n))))\n result)))\n\n(define zip-pairs\n (fn (coll)\n (let ((result (list))\n (clen (len coll)))\n (let loop ((i 0))\n (when (< i (- clen 1))\n (append! result (list (get coll i) (get coll (+ i 1))))\n (loop (+ i 1))))\n result)))\n\n\n;; --------------------------------------------------------------------------\n;; Dict operations\n;; --------------------------------------------------------------------------\n\n(define vals\n (fn (d)\n (map (fn (k) (get d k)) (keys d))))\n\n(define has-key?\n (fn (d key)\n (some (fn (k) (= k key)) (keys d))))\n\n(define assoc\n (fn (d key val)\n (let ((result (merge d (dict))))\n (dict-set! result key val)\n result)))\n\n(define dissoc\n (fn (d key)\n (let ((result (dict)))\n (for-each\n (fn (k)\n (when (!= k key)\n (dict-set! result k (get d k))))\n (keys d))\n result)))\n\n(define into\n (fn (target coll)\n (cond\n (list? target)\n (if (list? coll)\n (concat coll (list))\n (let ((result (list)))\n (for-each (fn (k) (append! result (list k (get coll k)))) (keys coll))\n result))\n (dict? target)\n (let ((result (dict)))\n (for-each\n (fn (pair)\n (when (and (list? pair) (>= (len pair) 2))\n (dict-set! result (get pair 0) (get pair 1))))\n coll)\n result)\n :else target)))\n\n\n;; --------------------------------------------------------------------------\n;; String operations\n;; --------------------------------------------------------------------------\n\n(define upcase (fn (s) (upper s)))\n(define downcase (fn (s) (lower s)))\n(define string-length (fn (s) (len s)))\n(define substring (fn (s start end) (slice s start end)))\n\n(define string-contains?\n (fn (s needle) (!= (index-of s needle) -1)))\n\n(define starts-with?\n (fn (s prefix) (= (index-of s prefix) 0)))\n\n(define ends-with?\n (fn (s suffix)\n (let ((slen (len s))\n (plen (len suffix)))\n (if (< slen plen) false\n (= (slice s (- slen plen)) suffix)))))\n\n;; split, join, replace stay as primitives — the stdlib versions cause\n;; stack overflows due to PRIMITIVES entry shadowing in the transpiled output.\n\n(define contains?\n (fn (coll key)\n (cond\n (string? coll) (!= (index-of coll (str key)) -1)\n (dict? coll) (has-key? coll key)\n (list? coll) (some (fn (x) (= x key)) coll)\n :else false)))\n\n\n;; --------------------------------------------------------------------------\n;; Text utilities\n;; --------------------------------------------------------------------------\n\n(define pluralize\n (fn (count singular plural)\n (if (= count 1)\n (or singular \"\")\n (or plural \"s\"))))\n\n(define escape\n (fn (s)\n (let ((r (str s)))\n (set! r (replace r \"&\" \"&\"))\n (set! r (replace r \"<\" \"<\"))\n (set! r (replace r \">\" \">\"))\n (set! r (replace r \"\\\"\" \""\"))\n (set! r (replace r \"'\" \"'\"))\n r)))\n\n(define parse-datetime\n (fn (s) (if s (str s) nil)))\n\n(define assert\n (fn (condition message)\n (when (not condition)\n (error (or message \"Assertion failed\")))\n true))\n"; + var forms = sxParse(src); + var tmpEnv = merge({}, PRIMITIVES); + for (var i = 0; i < forms.length; i++) { + trampoline(evalExpr(forms[i], tmpEnv)); + } + for (var k in tmpEnv) { + if (!PRIMITIVES[k]) PRIMITIVES[k] = tmpEnv[k]; + } + })(); + + // ========================================================================= // Platform interface — DOM adapter (browser-only) // ========================================================================= @@ -5913,7 +5870,7 @@ PRIMITIVES["resource"] = resource; // Wire CEK render hooks — evaluator checks _renderCheck/_renderFn instead of // the old renderActiveP()/isRenderExpr()/renderExpr() triple. - _renderCheck = function(expr, env) { return isRenderExpr(expr); }; + _renderCheck = function(expr, env) { return _renderMode && isRenderExpr(expr); }; _renderFn = function(expr, env) { return renderToDom(expr, env, null); }; var SVG_NS = "http://www.w3.org/2000/svg"; diff --git a/spec/evaluator.sx b/spec/evaluator.sx index 93a5266..4b0563b 100644 --- a/spec/evaluator.sx +++ b/spec/evaluator.sx @@ -1540,13 +1540,18 @@ (kont-push (make-deref-frame env) kont)))) ;; cek-call — call a function via CEK (replaces invoke) +;; cek-call — unified function dispatch +;; Both lambdas and native callables go through continue-with-call +;; so they interact identically with the continuation stack. +;; This is critical: replacing a native callable with an SX lambda +;; (e.g. stdlib.sx) must not change shift/reset behavior. (define cek-call (fn (f args) (let ((a (if (nil? args) (list) args))) (cond (nil? f) nil - (lambda? f) (cek-run (continue-with-call f a (dict) a (list))) - (callable? f) (apply f a) + (or (lambda? f) (callable? f)) + (cek-run (continue-with-call f a (dict) a (list))) :else nil)))) ;; reactive-shift-deref: the heart of deref-as-shift diff --git a/spec/stdlib.sx b/spec/stdlib.sx index 00112d4..bba11c4 100644 --- a/spec/stdlib.sx +++ b/spec/stdlib.sx @@ -1,21 +1,218 @@ ;; ========================================================================== ;; stdlib.sx — Standard library functions ;; -;; Functions expressed in SX using the irreducible primitive set. +;; Every function here is expressed in SX using the irreducible primitive +;; set. They are library functions — in band, auditable, portable. ;; -;; 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) -;; -;; Only functions safe from all three constraints are moved here. -;; The rest remain as host-provided PRIMITIVES for now. +;; Depends on: evaluator.sx (special forms) +;; Must load before: render.sx, freeze.sx, types.sx, user code ;; ========================================================================== +;; Logic + comparison: not, !=, <=, >= stay as primitives. +;; Replacing them with SX lambdas changes behavior inside shift/reset +;; because the transpiled evaluator code uses them directly. + +(define eq? (fn (a b) (= a b))) +(define eqv? (fn (a b) (= a b))) +(define equal? (fn (a b) (= a b))) + + ;; -------------------------------------------------------------------------- -;; String predicates and aliases +;; Type predicates +;; -------------------------------------------------------------------------- + +;; nil? stays as primitive — host's type-of uses it internally. + +(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"))) + +(define zero? + (fn (n) (= n 0))) + +(define odd? + (fn (n) (= (mod n 2) 1))) + +(define even? + (fn (n) (= (mod n 2) 0))) + +(define empty? + (fn (coll) (or (nil? coll) (= (len coll) 0)))) + + +;; -------------------------------------------------------------------------- +;; Arithmetic +;; -------------------------------------------------------------------------- + +;; inc and dec stay as primitives — used inside continuation contexts. + +(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 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) + (reduce (fn (acc x) (cons x acc)) (list) coll))) + +(define flatten + (fn (coll) + (reduce + (fn (acc x) + (if (list? x) (concat acc x) (concat acc (list x)))) + (list) coll))) + +(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) + (map (fn (k) (get d k)) (keys d)))) + +(define has-key? + (fn (d key) + (some (fn (k) (= k key)) (keys d)))) + +(define assoc + (fn (d key val) + (let ((result (merge d (dict)))) + (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 (fn (s) (upper s))) @@ -36,6 +233,17 @@ (if (< slen plen) false (= (slice s (- slen plen)) suffix))))) +;; split, join, replace stay as primitives — the stdlib versions cause +;; stack overflows due to PRIMITIVES entry shadowing in the transpiled output. + +(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