Files
rose-ash/lib/host/execute.sx
giles e12e314bc3 host: factor the shared composition CORE — one fold, N domains (composition step 8)
The roadmap's capstone: now that two folds exist (render, execute), extract the machinery
they share. host/comp-fold (compose.sx) is the reusable core — the seq/alt/each combinator
dispatch + the `when` predicate set (host/comp--pred?) + the context-environment + the `each`
source (host/comp--source) + recursion + the depth guard, ALL in one place. A domain plugs in
via a small dict {:empty :combine :leaf :overflow}; only its leaves and how results combine
differ:
  render  = {:empty ""     :combine str    …}  leaf -> markup (+ row/grid layout combinators)
  execute = {:empty (list) :combine concat …}  leaf -> effect

host/comp-render and host/exec-run are now one-liners over host/comp-fold with their domain.
execute.sx shed its own seq/alt/each dispatch — it's just a dict + a leaf. A THIRD domain
(eval/reduce/extent over the same algebra) is now only a new dict + leaf, no new control flow.

Both folds went through the core with ZERO behaviour change: new tests/compose.sx exercises
the core + render domain directly (17/17 — leaves, seq, row, alt+when (has/eq/not), each
(items/query/empty), tmpl recursion over a (children) tree + depth guard, ref transclude, one
object two contexts); execute 13/13; blog 162/164 (2 pre-existing relate-picker fails). Full
host conformance 388/390. Wired tests/compose.sx into conformance.

plans/composition-objects.md roadmap steps 1-8 COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 23:53:56 +00:00

41 lines
2.2 KiB
Plaintext

;; lib/host/execute.sx — the EXECUTE-fold: a SECOND domain over the SAME composition core
;; as the render-fold (lib/host/compose.sx), proving the algebra is domain-agnostic
;; (plans/composition-objects.md steps 7-8). Now that the core (host/comp-fold: the seq/alt/
;; each dispatch + when-predicates + each-source + context-environment + recursion) is shared,
;; a whole new domain is just a DOMAIN DICT + a leaf function:
;;
;; render {:empty "" :combine str …} leaf -> markup; fold -> HTML string
;; execute {:empty (list) :combine concat …} leaf -> effect; fold -> effect log
;;
;; seq = steps in order, alt+when = branch, each = for-each — all from the core, unchanged.
;; Only the leaf semantics (effect vs markup) and the accumulator (list vs string) are new.
;; So the behaviour model (Slice 9) is "an execute-fold over a composition object", not a
;; separate system — the same structure an author edits as a document.
;; resolve an effect argument against the context: (field K) reads the :item/ctx value via
;; the SAME resolver the render-fold uses; anything else is a literal.
(define host/exec--arg
(fn (a ctx)
(if (and (= (type-of a) "list") (= (str (first a)) "field"))
(host/comp--field (first (rest a)) ctx)
a)))
;; the execute-fold's LEAF: an (effect VERB ARG…) node records one effect {:verb :args};
;; anything else contributes no effects. (The core handles seq/alt/each.)
(define host/exec--leaf
(fn (node ctx dom)
(if (not (= (type-of node) "list"))
(list)
(let ((h (str (first node))) (args (rest node)))
(if (= h "effect")
(list {:verb (str (first args)) :args (map (fn (a) (host/exec--arg a ctx)) (rest args))})
(list))))))
;; the execute DOMAIN: effects concatenate into a log; the depth guard yields a max-depth
;; effect. host/comp-fold (compose.sx) supplies the seq/alt/each walk + when + each source.
(define host/exec--dom
{:empty (list) :combine concat :overflow (list {:verb "max-depth" :args (list)}) :leaf host/exec--leaf})
;; public entry: execute a composition node against a context -> the effect log (the run).
(define host/exec-run (fn (node ctx) (host/comp-fold node ctx host/exec--dom)))