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:
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user