diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 0f89bc3..92d79c9 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-23T18:18:45Z"; + var SX_VERSION = "2026-03-23T18:52:19Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } diff --git a/spec/compiler.sx b/spec/compiler.sx index b40db91..7163292 100644 --- a/spec/compiler.sx +++ b/spec/compiler.sx @@ -264,7 +264,7 @@ (= name "defeffect") (emit-op em 2) (= name "defisland") (compile-defcomp em args scope) (= name "quasiquote") (compile-quasiquote em (first args) scope) - (= name "letrec") (compile-let em args scope tail?) + (= name "letrec") (compile-letrec em args scope tail?) ;; Default — function call :else (compile-call em head args scope tail?))))))) @@ -387,6 +387,39 @@ (compile-begin em body let-scope tail?)))) +(define compile-letrec + (fn (em args scope tail?) + "Compile letrec: all names visible during value compilation. + 1. Define all local slots (initialized to nil). + 2. Compile each value and assign — names are already in scope + so mutually recursive functions can reference each other." + (let ((bindings (first args)) + (body (rest args)) + (let-scope (make-scope scope))) + (dict-set! let-scope "next-slot" (get scope "next-slot")) + ;; Phase 1: define all slots (push nil for each) + (let ((slots (map (fn (binding) + (let ((name (if (= (type-of (first binding)) "symbol") + (symbol-name (first binding)) + (first binding)))) + (let ((slot (scope-define-local let-scope name))) + (emit-op em 2) ;; OP_NIL + (emit-op em 17) ;; OP_LOCAL_SET + (emit-byte em slot) + slot))) + bindings))) + ;; Phase 2: compile values and assign (all names in scope) + (for-each (fn (pair) + (let ((binding (first pair)) + (slot (nth pair 1))) + (compile-expr em (nth binding 1) let-scope false) + (emit-op em 17) ;; OP_LOCAL_SET + (emit-byte em slot))) + (map (fn (i) (list (nth bindings i) (nth slots i))) + (range 0 (len bindings))))) + ;; Compile body + (compile-begin em body let-scope tail?)))) + (define compile-lambda (fn (em args scope) (let ((params (first args))