diff --git a/lib/flow/scoreboard.json b/lib/flow/scoreboard.json index 20bfa126..2d128269 100644 --- a/lib/flow/scoreboard.json +++ b/lib/flow/scoreboard.json @@ -1,10 +1,10 @@ { - "total": 30, - "passed": 30, + "total": 36, + "passed": 36, "failed": 0, "suites": { "basic": { "passed": 18, "total": 18 }, - "control": { "passed": 12, "total": 12 } + "control": { "passed": 18, "total": 18 } }, "phases": { "phase1": "done", "phase2": "in-progress" } } diff --git a/lib/flow/scoreboard.md b/lib/flow/scoreboard.md index 8382091a..e72e5f88 100644 --- a/lib/flow/scoreboard.md +++ b/lib/flow/scoreboard.md @@ -1,6 +1,6 @@ # flow-on-sx Scoreboard -**All tests pass: 30 / 30 across 2 suites.** +**All tests pass: 36 / 36 across 2 suites.** `bash lib/flow/conformance.sh` @@ -9,7 +9,7 @@ | Suite | Passing | Covers | |-------|--------:|--------| | basic | 18 | Phase 1: single nodes, linear sequence, data-flow threading, defflow, parallel fan/join, nested composition, publish-shaped flow | -| control | 12 | Phase 2: `branch` conditional (6); error model `fail`/`failed?`/`fail-reason` failure values that flow as data (6) | +| control | 18 | Phase 2: `branch` (6); error model `fail`/`failed?`/`fail-reason` (6); `try-catch` reifying raised exceptions (6) | ## Architecture @@ -36,6 +36,6 @@ capture the flow continuation directly. ## Phases - [x] Phase 1 — Declarative DAG + sequential execution (combinators + 18 tests, `flow/start`) -- [~] Phase 2 — Control flow + error handling (`branch`, error model done) +- [~] Phase 2 — Control flow + error handling (`branch`, error model, `try-catch` done) - [ ] Phase 3 — Suspend / resume (the showcase) - [ ] Phase 4 — Distributed nodes via fed-sx diff --git a/lib/flow/spec.sx b/lib/flow/spec.sx index 874f0399..5b5a473e 100644 --- a/lib/flow/spec.sx +++ b/lib/flow/spec.sx @@ -22,6 +22,8 @@ ;; (fail reason) — make an explicit failure value (data, not an exception) ;; (failed? x) — is x a failure value? ;; (fail-reason x) — the reason carried by a failure value +;; (try-catch node handler) — run node; if it raises, call (handler error) +;; with the reified error and return the handler's value (define flow-combinators-src @@ -29,7 +31,7 @@ (define flow-control-src - "(define (branch pred then else)\n (lambda (input) (if (pred input) (then input) (else input))))\n (define (fail reason) (list (quote flow-fail) reason))\n (define (failed? x) (and (pair? x) (eq? (car x) (quote flow-fail))))\n (define (fail-reason x) (car (cdr x)))") + "(define (branch pred then else)\n (lambda (input) (if (pred input) (then input) (else input))))\n (define (fail reason) (list (quote flow-fail) reason))\n (define (failed? x) (and (pair? x) (eq? (car x) (quote flow-fail))))\n (define (fail-reason x) (car (cdr x)))\n (define (try-catch node handler)\n (lambda (input) (guard (e (#t (handler e))) (node input))))") (define flow-load-combinators! diff --git a/lib/flow/tests/control.sx b/lib/flow/tests/control.sx index 403241fa..1d8df9a1 100644 --- a/lib/flow/tests/control.sx +++ b/lib/flow/tests/control.sx @@ -78,4 +78,34 @@ "(defflow guarded (sequence (lambda (s) (if (>= (string-length s) 3) (string-length s) (fail (quote too-short)))) (branch failed? (lambda (f) (list (quote recovered) (fail-reason f))) (lambda (n) (list (quote ok) n))))) (flow/start guarded \"hi\")") (list "recovered" "too-short")) +;; ── try-catch — reify raised exceptions ───────────────────────── +(flow-ctl-test + "try-catch: no exception returns node result" + (flow-c "(flow/start (try-catch (lambda (x) (* x 2)) (lambda (e) -1)) 5)") + 10) +(flow-ctl-test + "try-catch: handler runs on raise" + (flow-c + "(flow/start (try-catch (lambda (x) (raise (quote boom))) (flow-const 99)) 1)") + 99) +(flow-ctl-test + "try-catch: handler receives the reified error" + (flow-c "(flow/start (try-catch (lambda (x) (raise 42)) (lambda (e) e)) 0)") + 42) +(flow-ctl-test + "try-catch: catches exception from deep inside a sequence" + (flow-c + "(flow/start (try-catch (sequence (lambda (x) (+ x 1)) (lambda (x) (raise (quote deep)))) (flow-const -99)) 5)") + -99) +(flow-ctl-test + "try-catch: handler may convert to a failure value" + (flow-c + "(failed? (flow/start (try-catch (lambda (x) (raise (quote bad))) (lambda (e) (fail e))) 0))") + true) +(flow-ctl-test + "try-catch: composes — recover then continue" + (flow-c + "(flow/start (sequence (try-catch (lambda (x) (raise (quote x))) (flow-const 10)) (lambda (n) (* n 5))) 0)") + 50) + (define flow-ctl-tests-run! (fn () {:total (+ flow-ctl-pass flow-ctl-fail) :passed flow-ctl-pass :failed flow-ctl-fail :fails flow-ctl-fails})) diff --git a/plans/flow-on-sx.md b/plans/flow-on-sx.md index da3ae225..968dcbdf 100644 --- a/plans/flow-on-sx.md +++ b/plans/flow-on-sx.md @@ -16,7 +16,7 @@ federation extension via fed-sx for remote-node execution. ## Status (rolling) -`bash lib/flow/conformance.sh` → **30/30** (Phase 1 done; Phase 2 in progress) +`bash lib/flow/conformance.sh` → **36/36** (Phase 1 done; Phase 2 in progress) ## Ground rules @@ -79,7 +79,8 @@ lib/flow/spec.sx lib/flow/runtime.sx lib/flow/store.sx Scheme special form). `(branch pred then else)` — 6 tests. - [ ] `retry n [backoff]` — re-runs node up to n times on exception - [ ] `timeout ms` — bounds node execution -- [ ] `try-catch` — exception handler with reified error +- [x] `try-catch` — exception handler with reified error: `(try-catch node handler)` + runs node; on raise, calls `(handler error)` and returns its value. 6 tests. - [x] error model — exceptions vs explicit `(fail reason)` results: `fail`/`failed?`/ `fail-reason` produce/inspect failure values that flow downstream as data (distinct from raised exceptions caught by retry/try-catch). 6 tests.