Unify CEK callable dispatch, add named-let transpiler, full stdlib

Three changes that together enable the full 46-function stdlib migration:

1. CEK callable unification (spec/evaluator.sx):
   cek-call now routes both native callables and SX lambdas through
   continue-with-call, so replacing a native function with an SX lambda
   doesn't change shift/reset behavior.

2. Named-let transpiler support (hosts/javascript/transpiler.sx):
   (let loop ((i 0)) body...) now transpiles to a named IIFE:
   (function loop(i) { body })(0)
   This was the cause of the 3 test regressions (produced [object Object]).

3. Full stdlib via runtime eval (hosts/javascript/bootstrap.py):
   stdlib.sx is eval'd at runtime (not transpiled) so its defines go
   into PRIMITIVES without shadowing module-scope variables that the
   transpiled evaluator uses directly.

stdlib.sx now contains all 46 library functions:
  Logic: not
  Comparison: != <= >= eq? eqv? equal?
  Predicates: boolean? number? string? list? dict? continuation?
    zero? odd? even? empty?
  Arithmetic: inc dec abs ceil round min max clamp
  Collections: first last rest nth cons append reverse flatten
    range chunk-every zip-pairs
  Dict: vals has-key? assoc dissoc into
  Strings: upcase downcase string-length substring string-contains?
    starts-with? ends-with? split join replace contains?
  Text: pluralize escape parse-datetime assert

All hosts: JS 957+1080, Python 744, OCaml 952 — zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-17 17:11:09 +00:00
parent 4c54843542
commit f9e65e1d17
6 changed files with 324 additions and 92 deletions

View File

@@ -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