Frame-based dynamic scope: 870/870 — all tests passing
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 13m34s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 13m34s
provide/context and scope/emit!/emitted now use CEK continuation frames instead of an imperative global stack. Scope state is part of the continuation — captured by shift, restored by k invocation. New frame types: - ProvideFrame: holds name + value, consumed when body completes - ScopeAccFrame: holds name + mutable emitted list New CEK special forms: - context: walks kont for nearest ProvideFrame, returns value - emit!: walks kont for nearest ScopeAccFrame, appends to emitted - emitted: walks kont for nearest ScopeAccFrame, returns list Kont walkers: kont-find-provide, kont-find-scope-acc This fixes the last 2 test failures: - provide survives resume: scope captured by shift, restored by k - scope and emit across shift: accumulator preserved in continuation JS Full: 870/870 (100%) JS Standard: 747/747 (100%) Python: 679/679 (100%) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -152,11 +152,22 @@
|
||||
(fn (f remaining env)
|
||||
{:type "every" :f f :remaining remaining :env env}))
|
||||
|
||||
;; ScopeFrame: scope-pop! when frame pops
|
||||
;; ScopeFrame: remaining body expressions for scope special form
|
||||
(define make-scope-frame
|
||||
(fn (name remaining env)
|
||||
{:type "scope" :name name :remaining remaining :env env}))
|
||||
|
||||
;; ProvideFrame: dynamic variable binding (context reads this from kont)
|
||||
(define make-provide-frame
|
||||
(fn (name value remaining env)
|
||||
{:type "provide" :name name :value value :remaining remaining :env env}))
|
||||
|
||||
;; ScopeAccFrame: accumulator scope (emit! appends, emitted reads)
|
||||
(define make-scope-acc-frame
|
||||
(fn (name value remaining env)
|
||||
{:type "scope-acc" :name name :value (or value nil)
|
||||
:emitted (list) :remaining remaining :env env}))
|
||||
|
||||
;; ResetFrame: delimiter for shift/reset continuations
|
||||
(define make-reset-frame
|
||||
(fn (env)
|
||||
@@ -253,6 +264,26 @@
|
||||
(scan (rest k) (append captured (list frame))))))))
|
||||
(scan kont (list))))
|
||||
|
||||
;; Walk kont for nearest ProvideFrame with matching name
|
||||
(define kont-find-provide
|
||||
(fn (kont name)
|
||||
(if (empty? kont) nil
|
||||
(let ((frame (first kont)))
|
||||
(if (and (= (frame-type frame) "provide")
|
||||
(= (get frame "name") name))
|
||||
frame
|
||||
(kont-find-provide (rest kont) name))))))
|
||||
|
||||
;; Walk kont for nearest ScopeAccFrame with matching name
|
||||
(define kont-find-scope-acc
|
||||
(fn (kont name)
|
||||
(if (empty? kont) nil
|
||||
(let ((frame (first kont)))
|
||||
(if (and (= (frame-type frame) "scope-acc")
|
||||
(= (get frame "name") name))
|
||||
frame
|
||||
(kont-find-scope-acc (rest kont) name))))))
|
||||
|
||||
;; Check if a ReactiveResetFrame exists anywhere in the continuation
|
||||
(define has-reactive-reset-frame?
|
||||
(fn (kont)
|
||||
@@ -1254,9 +1285,12 @@
|
||||
;; Reactive deref-as-shift
|
||||
(= name "deref") (step-sf-deref args env kont)
|
||||
|
||||
;; Scoped effects
|
||||
;; Scoped effects — frame-based dynamic scope
|
||||
(= name "scope") (step-sf-scope args env kont)
|
||||
(= name "provide") (step-sf-provide args env kont)
|
||||
(= name "context") (step-sf-context args env kont)
|
||||
(= name "emit!") (step-sf-emit args env kont)
|
||||
(= name "emitted") (step-sf-emitted args env kont)
|
||||
|
||||
;; Dynamic wind
|
||||
(= name "dynamic-wind") (make-cek-value (sf-dynamic-wind args env) env kont)
|
||||
@@ -1460,15 +1494,83 @@
|
||||
(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 name body...) or (scope name :value v body...)
|
||||
(define step-sf-scope
|
||||
(fn (args env kont)
|
||||
;; Delegate to existing sf-scope for now — scope involves mutation
|
||||
(make-cek-value (sf-scope args env) env kont)))
|
||||
(let ((name (trampoline (eval-expr (first args) env)))
|
||||
(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)))))))
|
||||
|
||||
;; provide: delegate to existing handler
|
||||
;; provide: push ProvideFrame, evaluate body. context walks kont to read.
|
||||
;; (provide name value body...)
|
||||
(define step-sf-provide
|
||||
(fn (args env kont)
|
||||
(make-cek-value (sf-provide args env) 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)))))))
|
||||
|
||||
;; context: walk kont for nearest ProvideFrame with matching name
|
||||
(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))
|
||||
(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)))))))
|
||||
|
||||
;; emit!: walk kont for nearest ScopeAccFrame, append value
|
||||
(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))))))
|
||||
|
||||
;; emitted: walk kont for nearest ScopeAccFrame, return accumulated list
|
||||
(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))))))
|
||||
|
||||
;; reset: push ResetFrame, evaluate body
|
||||
(define step-sf-reset
|
||||
@@ -2013,6 +2115,41 @@
|
||||
(make-scope-frame name (rest remaining) fenv)
|
||||
rest-k))))
|
||||
|
||||
;; --- ProvideFrame: body expression evaluated ---
|
||||
(= ft "provide")
|
||||
(let ((remaining (get frame "remaining"))
|
||||
(fenv (get frame "env")))
|
||||
(if (empty? remaining)
|
||||
;; Body done — return value, frame consumed
|
||||
(make-cek-value value fenv rest-k)
|
||||
;; More body expressions — keep frame on kont
|
||||
(make-cek-state
|
||||
(first remaining) fenv
|
||||
(kont-push
|
||||
(make-provide-frame
|
||||
(get frame "name") (get frame "value")
|
||||
(rest remaining) fenv)
|
||||
rest-k))))
|
||||
|
||||
;; --- ScopeAccFrame: body expression evaluated ---
|
||||
(= ft "scope-acc")
|
||||
(let ((remaining (get frame "remaining"))
|
||||
(fenv (get frame "env")))
|
||||
(if (empty? remaining)
|
||||
;; Body done — return value, frame consumed
|
||||
(make-cek-value value fenv rest-k)
|
||||
;; More body expressions — carry emitted list forward
|
||||
(make-cek-state
|
||||
(first remaining) fenv
|
||||
(kont-push
|
||||
(let ((new-frame (make-scope-acc-frame
|
||||
(get frame "name") (get frame "value")
|
||||
(rest remaining) fenv)))
|
||||
;; Preserve accumulated emitted from current frame
|
||||
(dict-set! new-frame "emitted" (get frame "emitted"))
|
||||
new-frame)
|
||||
rest-k))))
|
||||
|
||||
;; --- MapFrame: callback result for map/map-indexed ---
|
||||
(= ft "map")
|
||||
(let ((f (get frame "f"))
|
||||
|
||||
Reference in New Issue
Block a user