Data-first HO forms, fix plan pages, aser error handling (1080/1080)
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled

Evaluator: data-first higher-order forms — ho-swap-args auto-detects
(map coll fn) vs (map fn coll), both work. Threading + HO: (-> data
(map fn)) dispatches through CEK HO machinery via quoted-value splice.
17 new tests in test-cek-advanced.sx.

Fix plan pages: add mother-language, isolated-evaluator, rust-wasm-host
to page-functions.sx plan() — were in defpage but missing from URL router.

Aser error handling: pages.py now catches EvalError separately, renders
visible error banner instead of silently sending empty content. All
except blocks include traceback in logs.

Scope primitives: register collect!/collected/clear-collected!/emitted/
emit!/context in shared/sx/primitives.py so hand-written _aser can
resolve them (fixes ~cssx/flush expansion failure).

New test file: shared/sx/tests/test_aser_errors.py — 19 pytest tests
for error propagation through all aser control flow forms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 18:05:00 +00:00
parent bdbf594bc8
commit 3a268e7277
10 changed files with 615 additions and 51 deletions

View File

@@ -1684,62 +1684,91 @@
;; (no nested eval-expr calls). When all args are evaluated, the
;; HoSetupFrame dispatch in step-continue sets up the iteration frame.
;; ho-form-name? — is this symbol name a higher-order special form?
(define ho-form-name?
(fn (name)
(or (= name "map") (= name "map-indexed") (= name "filter")
(= name "reduce") (= name "some") (= name "every?")
(= name "for-each"))))
;; ho-fn? — is this value usable as a HO callback?
(define ho-fn?
(fn (v) (or (callable? v) (lambda? v))))
;; ho-swap-args: normalise data-first arg order
;; 2-arg forms: (coll fn) → (fn coll)
;; 3-arg reduce: (coll fn init) → (fn init coll)
(define ho-swap-args
(fn (ho-type evaled)
(if (= ho-type "reduce")
(let ((a (first evaled))
(b (nth evaled 1)))
(if (and (not (ho-fn? a)) (ho-fn? b))
(list b (nth evaled 2) a)
evaled))
(let ((a (first evaled))
(b (nth evaled 1)))
(if (and (not (ho-fn? a)) (ho-fn? b))
(list b a)
evaled)))))
;; ho-setup-dispatch: all HO args evaluated, set up iteration
(define ho-setup-dispatch
(fn (ho-type evaled env kont)
(let ((f (first evaled)))
(let ((ordered (ho-swap-args ho-type evaled)))
(let ((f (first ordered)))
(cond
(= ho-type "map")
(let ((coll (nth evaled 1)))
(let ((coll (nth ordered 1)))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-map-frame f (rest coll) (list) env) kont))))
(= ho-type "map-indexed")
(let ((coll (nth evaled 1)))
(let ((coll (nth ordered 1)))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list 0 (first coll)) env (list)
(kont-push (make-map-indexed-frame f (rest coll) (list) env) kont))))
(= ho-type "filter")
(let ((coll (nth evaled 1)))
(let ((coll (nth ordered 1)))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-filter-frame f (rest coll) (list) (first coll) env) kont))))
(= ho-type "reduce")
(let ((init (nth evaled 1))
(coll (nth evaled 2)))
(let ((init (nth ordered 1))
(coll (nth ordered 2)))
(if (empty? coll)
(make-cek-value init env kont)
(continue-with-call f (list init (first coll)) env (list)
(kont-push (make-reduce-frame f (rest coll) env) kont))))
(= ho-type "some")
(let ((coll (nth evaled 1)))
(let ((coll (nth ordered 1)))
(if (empty? coll)
(make-cek-value false env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-some-frame f (rest coll) env) kont))))
(= ho-type "every")
(let ((coll (nth evaled 1)))
(let ((coll (nth ordered 1)))
(if (empty? coll)
(make-cek-value true env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-every-frame f (rest coll) env) kont))))
(= ho-type "for-each")
(let ((coll (nth evaled 1)))
(let ((coll (nth ordered 1)))
(if (empty? coll)
(make-cek-value nil env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-for-each-frame f (rest coll) env) kont))))
:else (error (str "Unknown HO type: " ho-type))))))
:else (error (str "Unknown HO type: " ho-type)))))))
(define step-ho-map
(fn (args env kont)
@@ -1965,24 +1994,36 @@
(make-cek-value value fenv rest-k)
;; Apply next form to value
(let ((form (first remaining))
(rest-forms (rest remaining)))
(let ((result (if (= (type-of form) "list")
(let ((f (trampoline (eval-expr (first form) fenv)))
(rargs (map (fn (a) (trampoline (eval-expr a fenv))) (rest form)))
(all-args (cons value rargs)))
(cond
(and (callable? f) (not (lambda? f))) (apply f all-args)
(lambda? f) (trampoline (call-lambda f all-args fenv))
:else (error (str "-> form not callable: " (inspect f)))))
(let ((f (trampoline (eval-expr form fenv))))
(cond
(and (callable? f) (not (lambda? f))) (f value)
(lambda? f) (trampoline (call-lambda f (list value) fenv))
:else (error (str "-> form not callable: " (inspect f))))))))
(if (empty? rest-forms)
(make-cek-value result fenv rest-k)
(make-cek-value result fenv
(kont-push (make-thread-frame rest-forms fenv) rest-k)))))))
(rest-forms (rest remaining))
(new-kont (if (empty? (rest remaining)) rest-k
(kont-push (make-thread-frame (rest remaining) fenv) rest-k))))
;; Check if form is a HO call like (map fn)
(if (and (= (type-of form) "list")
(not (empty? form))
(= (type-of (first form)) "symbol")
(ho-form-name? (symbol-name (first form))))
;; HO form — splice value as quoted arg, dispatch via CEK
(make-cek-state
(cons (first form) (cons (list 'quote value) (rest form)))
fenv new-kont)
;; Normal: tree-walk eval + apply
(let ((result (if (= (type-of form) "list")
(let ((f (trampoline (eval-expr (first form) fenv)))
(rargs (map (fn (a) (trampoline (eval-expr a fenv))) (rest form)))
(all-args (cons value rargs)))
(cond
(and (callable? f) (not (lambda? f))) (apply f all-args)
(lambda? f) (trampoline (call-lambda f all-args fenv))
:else (error (str "-> form not callable: " (inspect f)))))
(let ((f (trampoline (eval-expr form fenv))))
(cond
(and (callable? f) (not (lambda? f))) (f value)
(lambda? f) (trampoline (call-lambda f (list value) fenv))
:else (error (str "-> form not callable: " (inspect f))))))))
(if (empty? rest-forms)
(make-cek-value result fenv rest-k)
(make-cek-value result fenv
(kont-push (make-thread-frame rest-forms fenv) rest-k))))))))
;; --- ArgFrame: head or arg evaluated ---
(= ft "arg")

View File

@@ -598,3 +598,100 @@
n
(+ (fib (- n 1)) (fib (- n 2))))))
(fib 7))"))))
;; --------------------------------------------------------------------------
;; 8. Data-first higher-order forms
;; --------------------------------------------------------------------------
(defsuite "data-first-ho"
(deftest "map — data-first arg order"
(assert-equal (list 2 4 6)
(map (list 1 2 3) (fn (x) (* x 2)))))
(deftest "filter — data-first arg order"
(assert-equal (list 3 4 5)
(filter (list 1 2 3 4 5) (fn (x) (> x 2)))))
(deftest "reduce — data-first arg order"
(assert-equal 10
(reduce (list 1 2 3 4) + 0)))
(deftest "some — data-first arg order"
(assert-true
(some (list 1 2 3) (fn (x) (> x 2))))
(assert-false
(some (list 1 2 3) (fn (x) (> x 5)))))
(deftest "every? — data-first arg order"
(assert-true
(every? (list 2 4 6) (fn (x) (> x 1))))
(assert-false
(every? (list 2 4 6) (fn (x) (> x 3)))))
(deftest "for-each — data-first arg order"
(let ((acc (list)))
(for-each (list 10 20 30)
(fn (x) (set! acc (append acc (list x)))))
(assert-equal (list 10 20 30) acc)))
(deftest "map-indexed — data-first arg order"
(assert-equal (list "0:a" "1:b" "2:c")
(map-indexed (list "a" "b" "c")
(fn (i v) (str i ":" v)))))
(deftest "fn-first still works — map"
(assert-equal (list 2 4 6)
(map (fn (x) (* x 2)) (list 1 2 3))))
(deftest "fn-first still works — reduce"
(assert-equal 10
(reduce + 0 (list 1 2 3 4)))))
;; --------------------------------------------------------------------------
;; 9. Threading with HO forms
;; --------------------------------------------------------------------------
(defsuite "thread-ho"
(deftest "-> map"
(assert-equal (list 2 4 6)
(-> (list 1 2 3) (map (fn (x) (* x 2))))))
(deftest "-> filter"
(assert-equal (list 3 4 5)
(-> (list 1 2 3 4 5) (filter (fn (x) (> x 2))))))
(deftest "-> filter then map pipeline"
(assert-equal (list 30 40 50)
(-> (list 1 2 3 4 5)
(filter (fn (x) (> x 2)))
(map (fn (x) (* x 10))))))
(deftest "-> reduce"
(assert-equal 15
(-> (list 1 2 3 4 5) (reduce + 0))))
(deftest "-> map then reduce"
(assert-equal 12
(-> (list 1 2 3)
(map (fn (x) (* x 2)))
(reduce + 0))))
(deftest "-> some"
(assert-true
(-> (list 1 2 3) (some (fn (x) (> x 2)))))
(assert-false
(-> (list 1 2 3) (some (fn (x) (> x 5))))))
(deftest "-> every?"
(assert-true
(-> (list 2 4 6) (every? (fn (x) (> x 1))))))
(deftest "-> full pipeline: map filter reduce"
;; Double each, keep > 4, sum
(assert-equal 24
(-> (list 1 2 3 4 5)
(map (fn (x) (* x 2)))
(filter (fn (x) (> x 4)))
(reduce + 0)))))