Unify scoped effects: scope as general primitive, provide as sugar
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 12m54s

- Add `scope` special form to eval.sx: (scope name body...) or
  (scope name :value v body...) — general dynamic scope primitive
- `provide` becomes sugar: (provide name value body...) calls scope
- Rename provide-push!/provide-pop! to scope-push!/scope-pop! throughout
  all adapters (async, dom, html, sx) and platform implementations
- Update boundary.sx: Tier 5 now "Scoped effects" with scope-push!/
  scope-pop! as primary, provide-push!/provide-pop! as aliases
- Add scope form handling to async adapter and aser wire format
- Update sx-browser.js, sx_ref.py (bootstrapped output)
- Add scopes.sx docs page, update provide/spreads/demo docs
- Update nav-data, page-functions, docs page definitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 17:30:34 +00:00
parent 6ca46bb295
commit 11fdd1a840
23 changed files with 869 additions and 285 deletions

View File

@@ -162,6 +162,7 @@
(= name "reset") (sf-reset args env)
(= name "shift") (sf-shift args env)
(= name "dynamic-wind") (sf-dynamic-wind args env)
(= name "scope") (sf-scope args env)
(= name "provide") (sf-provide args env)
;; Higher-order forms
@@ -951,11 +952,35 @@
;; --------------------------------------------------------------------------
;; 6a2. provide — render-time dynamic scope
;; 6a2. scope — unified render-time dynamic scope primitive
;; --------------------------------------------------------------------------
;;
;; (provide name value body...) — push a named scope with value and empty
;; accumulator, evaluate body, pop scope. Returns last body result.
;; (scope name body...) or (scope name :value v body...)
;; Push a named scope with optional value and empty accumulator,
;; evaluate body, pop scope. Returns last body result.
;;
;; `provide` is sugar: (provide name value body...) = (scope name :value value body...)
(define sf-scope
(fn ((args :as list) (env :as dict))
(let ((name (trampoline (eval-expr (first args) env)))
(rest (slice args 1))
(val nil)
(body-exprs nil))
;; Check for :value keyword
(if (and (>= (len rest) 2) (= (type-of (first rest)) "keyword") (= (keyword-name (first rest)) "value"))
(do (set! val (trampoline (eval-expr (nth rest 1) env)))
(set! body-exprs (slice rest 2)))
(set! body-exprs rest))
(scope-push! name val)
(let ((result nil))
(for-each (fn (e) (set! result (trampoline (eval-expr e env)))) body-exprs)
(scope-pop! name)
result))))
;; provide — sugar for scope with a value
;; (provide name value body...) → (scope name :value value body...)
(define sf-provide
(fn ((args :as list) (env :as dict))
@@ -963,9 +988,9 @@
(val (trampoline (eval-expr (nth args 1) env)))
(body-exprs (slice args 2))
(result nil))
(provide-push! name val)
(scope-push! name val)
(for-each (fn (e) (set! result (trampoline (eval-expr e env)))) body-exprs)
(provide-pop! name)
(scope-pop! name)
result)))