diff --git a/lib/kernel/runtime.sx b/lib/kernel/runtime.sx index 442748b0..59e36601 100644 --- a/lib/kernel/runtime.sx +++ b/lib/kernel/runtime.sx @@ -288,6 +288,38 @@ (c (knl-cond-eval-body (rest args) dyn-env)) (:else nil)))))))) +;; $and? — short-circuit AND. Operative (not applicative) so untaken +;; clauses are NOT evaluated. Empty $and? returns true (the identity). +(define knl-and?-impl + (fn (args dyn-env) + (cond + ((or (nil? args) (= (length args) 0)) true) + ((= (length args) 1) (kernel-eval (first args) dyn-env)) + (:else + (let ((v (kernel-eval (first args) dyn-env))) + (cond + (v (knl-and?-impl (rest args) dyn-env)) + (:else v))))))) + +(define kernel-and?-operative + (kernel-make-primitive-operative knl-and?-impl)) + +;; $or? — short-circuit OR. Operative; untaken clauses NOT evaluated. +;; Empty $or? returns false (the identity). +(define knl-or?-impl + (fn (args dyn-env) + (cond + ((or (nil? args) (= (length args) 0)) false) + ((= (length args) 1) (kernel-eval (first args) dyn-env)) + (:else + (let ((v (kernel-eval (first args) dyn-env))) + (cond + (v v) + (:else (knl-or?-impl (rest args) dyn-env)))))))) + +(define kernel-or?-operative + (kernel-make-primitive-operative knl-or?-impl)) + ;; $unless COND BODY... — evaluate body iff COND is falsy; else nil. (define kernel-unless-operative (kernel-make-primitive-operative @@ -581,6 +613,8 @@ (kernel-env-bind! env "$cond" kernel-cond-operative) (kernel-env-bind! env "$when" kernel-when-operative) (kernel-env-bind! env "$unless" kernel-unless-operative) + (kernel-env-bind! env "$and?" kernel-and?-operative) + (kernel-env-bind! env "$or?" kernel-or?-operative) (kernel-env-bind! env "eval" kernel-eval-applicative) (kernel-env-bind! env diff --git a/lib/kernel/tests/standard.sx b/lib/kernel/tests/standard.sx index 408f24ec..ee70ea58 100644 --- a/lib/kernel/tests/standard.sx +++ b/lib/kernel/tests/standard.sx @@ -319,4 +319,22 @@ (ks-test "unless: skips body when true" (ks-eval "($unless #t nope)") nil) +;; ── $and? / $or? short-circuit ────────────────────────────────── +(ks-test "and: empty returns true" (ks-eval "($and?)") true) +(ks-test "and: single returns value" (ks-eval "($and? 42)") 42) +(ks-test "and: all true returns last" + (ks-eval "($and? 1 2 3)") 3) +(ks-test "and: first false short-circuits" + (ks-eval "($and? #f nope)") false) +(ks-test "and: false in middle short-circuits" + (ks-eval "($and? 1 #f nope)") false) +(ks-test "or: empty returns false" (ks-eval "($or?)") false) +(ks-test "or: single returns value" (ks-eval "($or? 42)") 42) +(ks-test "or: first truthy short-circuits" + (ks-eval "($or? 99 nope)") 99) +(ks-test "or: all false returns last" + (ks-eval "($or? #f #f #f)") false) +(ks-test "or: middle truthy" + (ks-eval "($or? #f 42 nope)") 42) + (define ks-tests-run! (fn () {:total (+ ks-test-pass ks-test-fail) :passed ks-test-pass :failed ks-test-fail :fails ks-test-fails})) diff --git a/plans/kernel-on-sx.md b/plans/kernel-on-sx.md index 1528d01a..f8d43307 100644 --- a/plans/kernel-on-sx.md +++ b/plans/kernel-on-sx.md @@ -108,6 +108,12 @@ When the second consumer arrives, the extraction work is: rename `kernel-*` → **May propose:** `lib/guest/reflective/` sub-layer — environment manipulation, evaluator-as-value, applicative/operative dispatch protocols. +**Proposed `lib/guest/reflective/short-circuit.sx` API** (from $and?/$or? chiselling — pending second consumer): +- `(refl-short-and? ARGS DYN-ENV)` — recursive walker; evaluates each in DYN-ENV, returns first falsy value or last truthy. Identity is `true`. +- `(refl-short-or? ARGS DYN-ENV)` — symmetric; returns first truthy or last falsy. Identity is `false`. +- Both must be defined as operatives in any reflective Lisp because short-circuit semantics require staged evaluation — an applicative would force every argument before any decision could be made. +- Driving insight: short-circuit booleans are a forcing function for "operative semantics matter". Languages that lack first-class operatives have to special-case these as keywords; languages with operatives get them for free, in user code. + **Proposed `lib/guest/reflective/quoting.sx` API** (from quasiquote chiselling — pending second consumer): - `(refl-quasi-walk FORM ENV)` — top-level entry. Recursively walks FORM; an `$unquote` sub-expression is evaluated in ENV and replaces itself in the result. - `(refl-quasi-walk-list FORMS ENV)` — walks a list of forms, splicing `$unquote-splicing` results inline. @@ -154,6 +160,7 @@ The motivation is that SX's host `make-env` family is registered only in HTTP/si ## Progress log +- 2026-05-11 — `$and?` / `$or?` short-circuit booleans. Operatives (not applicatives) so untaken arguments are NOT evaluated. Identity values: `$and?` empty = true, `$or?` empty = false. Returns the last evaluated value (Kernel convention — not coerced to bool). 10 new tests including the short-circuit verification (`($and? #f nope)` returns false without evaluating `nope`). chisel: shapes-reflective. Sketched `lib/guest/reflective/short-circuit.sx` API; the protocol is identical across reflective Lisps because short-circuit FORCES operative semantics — an applicative variant would defeat the purpose. 252 tests total. - 2026-05-11 — `$cond` / `$when` / `$unless`. Standard Kernel control flow added: `$cond` walks clauses in order, evaluates first truthy test, runs that clause's body in sequence; `else` is the catch-all symbol; empty cond and no-match cond return nil. `$when` and `$unless` are simple conditional execution. All three preserve hygiene (clauses not taken are NOT evaluated). 12 new tests in `tests/standard.sx`. chisel: nothing. 242 tests total. (Third `nothing` in a row but allowable here — these are textbook Kernel idioms with no novel reflective angle.) - 2026-05-11 — `$quasiquote` runtime. The parser's reader macros (Phase 1.5) produced unevaluated `$quasiquote`/`$unquote`/`$unquote-splicing` forms; the runtime side now interprets them. `kernel-quasiquote-operative` walks the template via mutual recursion `knl-quasi-walk` ↔ `knl-quasi-walk-list`: atoms and empty lists pass through; an `($unquote X)` head form returns `(kernel-eval X dyn-env)`; an `($unquote-splicing X)` *inside* a list evaluates X and splices its list result via `knl-list-concat`. Nesting depth (`` `\`...\` ``) is not tracked — for Phase-1.5 simplicity, nested quasiquotes flatten. 8 new tests in `tests/standard.sx`. chisel: shapes-reflective. The quoting walker shape is universal across reflective Lisps; sketched the `lib/guest/reflective/quoting.sx` candidate API (`refl-quasi-walk`, `refl-quasi-walk-list`, `refl-list-concat`). 230 tests total. - 2026-05-11 — Multi-expression body for `$vau`/`$lambda`. Both forms now accept `(formals env-param body1 body2 ...)` / `(formals body1 body2 ...)`. Implementation: `:body` slot now holds a LIST of forms (was a single expression); `kernel-call-operative` calls a new `knl-eval-body` that evaluates each in sequence, returning the last. No dependency on `$sequence` being in static-env — the iteration lives at the host level. 5 new tests in `tests/vau.sx` (multi-body lambda, multi-body vau, sequenced `$define!`, zero-arg multi-body). chisel: nothing (Kernel-internal improvement; doesn't change the reflective API surface). 223 tests total.