;; lib/host/execute.sx — the EXECUTE-fold: a SECOND interpreter over the SAME composition ;; algebra (seq/alt/each) as the render-fold (lib/host/compose.sx), proving the algebra is ;; domain-agnostic (plans/composition-objects.md step 7 — "prove universality with a second ;; fold"). What changes between folds is only what the combinators + leaves MEAN: ;; ;; domain fold seq alt+when each leaf ;; content render -> block order choose map items markup -> HTML string ;; behaviour execute -> steps in order branch for-each effect -> effect log ;; ;; Crucially this REUSES compose.sx's shared machinery — the `when` predicate set ;; (host/comp--pred?), the field/value resolver (host/comp--field), and the `each` source ;; (host/comp--source). So the predicate set, the context-environment, and the iteration ;; source are domain-agnostic; ONLY the leaf semantics (effect vs markup) and the fold's ;; accumulator (a list of effects vs a string) are new. The behaviour model (Slice 9) is ;; therefore "an execute-fold over a composition object", not a separate system. ;; 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))) ;; a leaf effect: (effect VERB ARG…) -> one effect record {:verb :args}. The execute-fold's ;; analogue of a render leaf — it performs (records) an effect rather than emitting markup. (define host/exec--effect (fn (verb args ctx) (list {:verb (str verb) :args (map (fn (a) (host/exec--arg a ctx)) args)}))) ;; seq: run every step IN ORDER, concatenating their effects (the sequential strategy). (define host/exec--run-all (fn (nodes ctx) (reduce (fn (acc n) (concat acc (host/exec--run n ctx))) (list) nodes))) ;; alt: BRANCH — run the FIRST branch whose `when` holds (reusing the render-fold's ;; predicate host/comp--pred?), else `else`. This is if/cond for the behaviour domain. (define host/exec--alt (fn (branches ctx) (if (empty? branches) (list) (let ((br (first branches)) (bh (str (first (first branches))))) (cond ((= bh "else") (host/exec--run (first (rest br)) ctx)) ((= bh "when") (if (host/comp--pred? (first (rest br)) ctx) (host/exec--run (first (rest (rest br))) ctx) (host/exec--alt (rest branches) ctx))) (else (host/exec--alt (rest branches) ctx))))))) ;; each: FOR-EACH — run the body per item from the (reused) source, :item bound, in order; ;; depth guard backstops runaway recursion, same as the render-fold. (define host/exec--each (fn (src body ctx) (let ((depth (or (get ctx "depth") 0))) (if (> depth 40) (list {:verb "max-depth" :args (list)}) (reduce (fn (acc item) (concat acc (host/exec--run body (merge ctx {"item" item "depth" (+ depth 1)})))) (list) (host/comp--source src ctx)))))) ;; the execute-fold (the interpreter): same combinator dispatch shape as host/comp--render, ;; but leaves are effects and the accumulator is an effect log. (define host/exec--run (fn (node ctx) (if (not (= (type-of node) "list")) (list) (let ((h (str (first node))) (args (rest node))) (cond ((= h "seq") (host/exec--run-all args ctx)) ((= h "alt") (host/exec--alt args ctx)) ((= h "each") (host/exec--each (first args) (first (rest args)) ctx)) ((= h "effect") (host/exec--effect (first args) (rest args) ctx)) (else (list))))))) ;; public entry: execute a composition node against a context -> the effect log (the run). (define host/exec-run (fn (node ctx) (host/exec--run node ctx)))