Unify scope mechanism: one world (hashtable stacks everywhere)
Replace continuation-based scope frames with hashtable stacks for all scope operations. The CEK evaluator's scope/provide/context/emit!/emitted now use scope-push!/pop!/peek/emit! primitives (registered in sx_primitives table) instead of walking continuation frames. This eliminates the two-world problem where the aser used hashtable stacks (scope-push!/pop!) but eval-expr used continuation frames (ScopeFrame/ScopeAccFrame). Now both paths share the same mechanism. Benefits: - scope/context works inside eval-expr calls (e.g. (str ... (context x))) - O(1) scope lookup vs O(n) continuation walking - Simpler — no ScopeFrame/ScopeAccFrame/ProvideFrame creation/dispatch - VM-compiled code and CEK code both see the same scope state Also registers scope-push!/pop!/peek/emit!/collect!/collected/ clear-collected! as real primitives (sx_primitives table) so the transpiled evaluator can call them directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1432,7 +1432,11 @@
|
||||
(make-cek-value (sf-lambda args env) env kont)))
|
||||
|
||||
;; scope: evaluate name, then push ScopeFrame
|
||||
;; scope: push ScopeAccFrame, evaluate body. emit!/emitted walk kont.
|
||||
;; scope/provide/context/emit!/emitted — ALL use hashtable stacks.
|
||||
;; One world: the aser and CEK share the same scope mechanism.
|
||||
;; No continuation frame walking — scope-push!/pop!/peek are the primitives.
|
||||
|
||||
;; scope: push scope, evaluate body, pop scope.
|
||||
;; (scope name body...) or (scope name :value v body...)
|
||||
(define step-sf-scope
|
||||
(fn (args env kont)
|
||||
@@ -1440,85 +1444,54 @@
|
||||
(rest-args (slice args 1))
|
||||
(val nil)
|
||||
(body nil))
|
||||
;; Check for :value keyword
|
||||
(if (and (>= (len rest-args) 2)
|
||||
(= (type-of (first rest-args)) "keyword")
|
||||
(= (keyword-name (first rest-args)) "value"))
|
||||
(do (set! val (trampoline (eval-expr (nth rest-args 1) env)))
|
||||
(set! body (slice rest-args 2)))
|
||||
(set! body rest-args))
|
||||
;; Push ScopeAccFrame and start evaluating body
|
||||
(if (empty? body)
|
||||
(make-cek-value nil env kont)
|
||||
(if (= (len body) 1)
|
||||
(make-cek-state (first body) env
|
||||
(kont-push (make-scope-acc-frame name val (list) env) kont))
|
||||
(make-cek-state (first body) env
|
||||
(kont-push
|
||||
(make-scope-acc-frame name val (rest body) env)
|
||||
kont)))))))
|
||||
(scope-push! name val)
|
||||
(let ((result nil))
|
||||
(for-each (fn (expr) (set! result (trampoline (eval-expr expr env)))) body)
|
||||
(scope-pop! name)
|
||||
(make-cek-value result env kont)))))
|
||||
|
||||
;; provide: push ProvideFrame, evaluate body. context walks kont to read.
|
||||
;; (provide name value body...)
|
||||
;; provide: sugar for scope with value.
|
||||
(define step-sf-provide
|
||||
(fn (args env kont)
|
||||
(let ((name (trampoline (eval-expr (first args) env)))
|
||||
(val (trampoline (eval-expr (nth args 1) env)))
|
||||
(body (slice args 2)))
|
||||
;; Push ProvideFrame and start evaluating body
|
||||
(if (empty? body)
|
||||
(make-cek-value nil env kont)
|
||||
(if (= (len body) 1)
|
||||
(make-cek-state (first body) env
|
||||
(kont-push (make-provide-frame name val (list) env) kont))
|
||||
(make-cek-state (first body) env
|
||||
(kont-push
|
||||
(make-provide-frame name val (rest body) env)
|
||||
kont)))))))
|
||||
(scope-push! name val)
|
||||
(let ((result nil))
|
||||
(for-each (fn (expr) (set! result (trampoline (eval-expr expr env)))) body)
|
||||
(scope-pop! name)
|
||||
(make-cek-value result env kont)))))
|
||||
|
||||
;; context: check hashtable scope stacks first (set by aser's scope-push!),
|
||||
;; then walk kont for nearest ProvideFrame with matching name.
|
||||
;; The hashtable check is needed because aser renders scopes via scope-push!/pop!
|
||||
;; but inner eval-expr calls (e.g. inside (str ...)) use the CEK continuation.
|
||||
;; context: read from scope stack.
|
||||
(define step-sf-context
|
||||
(fn (args env kont)
|
||||
(let ((name (trampoline (eval-expr (first args) env)))
|
||||
(default-val (if (>= (len args) 2)
|
||||
(trampoline (eval-expr (nth args 1) env))
|
||||
nil)))
|
||||
;; Check hashtable scope stacks first (aser rendering path)
|
||||
(let ((stack-val (if (primitive? "scope-peek")
|
||||
((get-primitive "scope-peek") name)
|
||||
nil)))
|
||||
(if (not (nil? stack-val))
|
||||
(make-cek-value stack-val env kont)
|
||||
;; Fall back to continuation-based lookup
|
||||
(let ((frame (kont-find-provide kont name)))
|
||||
(if frame
|
||||
(make-cek-value (get frame "value") env kont)
|
||||
(if (>= (len args) 2)
|
||||
(make-cek-value default-val env kont)
|
||||
(error (str "No provider for: " name))))))))))
|
||||
nil))
|
||||
(val (scope-peek name)))
|
||||
(make-cek-value (if (nil? val) default-val val) env kont))))
|
||||
|
||||
;; emit!: walk kont for nearest ScopeAccFrame, append value
|
||||
;; emit!: append to scope accumulator.
|
||||
(define step-sf-emit
|
||||
(fn (args env kont)
|
||||
(let ((name (trampoline (eval-expr (first args) env)))
|
||||
(val (trampoline (eval-expr (nth args 1) env)))
|
||||
(frame (kont-find-scope-acc kont name)))
|
||||
(if frame
|
||||
(do (append! (get frame "emitted") val)
|
||||
(make-cek-value nil env kont))
|
||||
(error (str "No scope for emit!: " name))))))
|
||||
(val (trampoline (eval-expr (nth args 1) env))))
|
||||
(scope-emit! name val)
|
||||
(make-cek-value nil env kont))))
|
||||
|
||||
;; emitted: walk kont for nearest ScopeAccFrame, return accumulated list
|
||||
;; emitted: read accumulated scope values.
|
||||
(define step-sf-emitted
|
||||
(fn (args env kont)
|
||||
(let ((name (trampoline (eval-expr (first args) env)))
|
||||
(frame (kont-find-scope-acc kont name)))
|
||||
(if frame
|
||||
(make-cek-value (get frame "emitted") env kont)
|
||||
(error (str "No scope for emitted: " name))))))
|
||||
(val (scope-peek name)))
|
||||
(make-cek-value (if (nil? val) (list) val) env kont))))
|
||||
|
||||
;; reset: push ResetFrame, evaluate body
|
||||
(define step-sf-reset
|
||||
|
||||
Reference in New Issue
Block a user