Standards Conformance¶
Kaappi's core language is a complete implementation of R7RS-small. On top of that, a small set of runtime subsystems — fibers, an I/O reactor, cross-thread channels — deliberately extend beyond what R7RS-small specifies, since the standard has no concurrency model at all. This page states both plainly: where the core language has known gaps against the spec, and where Kaappi goes beyond the spec's scope on purpose.
Other Schemes document this distinction the same way — Guile's "R7RS Incompatibilities", Gauche's "Standard conformance" chapter, CHICKEN's "Deviations from the standard" — and this page follows the same shape.
Core language¶
Every identifier in R7RS Appendix A is implemented: 719 built-in procedures, 32 syntax forms, and all 16 standard libraries. The R7RS conformance test suite passes in full. See CONFORMANCE.md in the core repo for exact counts and per-SRFI coverage detail — this page doesn't restate numbers that live there, so it can't drift out of sync with them.
Known gaps against R7RS-small¶
| Gap | Detail |
|---|---|
syntax-case |
Not implemented — R7RS-small itself deliberately standardizes only syntax-rules; syntax-case is R6RS/implementation-specific territory (Chez, Racket), not a Kaappi omission against the spec it targets. For procedural macros beyond syntax-rules, Kaappi ships er-macro-transformer instead — see Explicit Renaming Macros. |
call/cc across REPL top-level forms |
A continuation captured in one top-level REPL expression can't re-enter a later one. Shared behavior with Guile, Chibi, Chicken, Chez, and Racket — not a Kaappi-specific limitation. |
| Fiber parking inside custom-port callbacks | A fiber may park — on an empty channel, a timer, or blocking I/O — inside a callback driven by any built-in higher-order procedure: map and for-each, SRFI-1's fold/filter/find/any/every, assoc/member with a custom predicate, hash-table-walk, string-index, sort, dynamic-wind, with-exception-handler, and code running under eval all resume correctly (the native drivers became resumable in v0.25.0). The one place a blocking primitive is refused is inside a SRFI 181 custom-port callback (a read!/write!/flush procedure), where it raises a catchable error instead of parking — those callbacks must be synchronous, non-blocking code. See Concurrency. |
| SRFI coverage | 181 SRFIs supported; a handful of optional or non-mutating-only procedures aren't implemented (linear-update list variants, string-xcopy!, and similar). See SRFI Support and CONFORMANCE.md for the exact list. |
Extensions beyond R7RS-small's scope¶
R7RS-small has no concurrency model — no threads, no I/O multiplexing, no shared memory. Everything below is deliberate, documented extension into that unspecified territory, tracked as Kaappi Enhancement Proposals (KEPs). The one language-surface entry — explicit renaming macros — extends beyond the spec the same way: on purpose, KEP-tracked, and detectable from code.
| Subsystem | KEP | Status | Detect from code |
|---|---|---|---|
| Fibers + I/O reactor | KEP-0001 | Final — shipped | (cond-expand (kaappi-fibers ...)), (cond-expand (kaappi-reactor ...)) |
| SRFI-18 OS threads | — (R7RS-small doesn't cover threads; SRFI-18 predates this project) | Shipped | (cond-expand (kaappi-threads ...)), or (cond-expand ((library (srfi 18)) ...)) |
| Cross-thread channels | KEP-0002 | Accepted — core mechanism shipped; the two wakeup-path correctness issues open at 0.14.x (#1487, #1489) were fixed in v0.15.0 | (cond-expand (kaappi-shared-channels ...)) (KEP-0004 Phase 2; absent on wasm32-wasi, which has no OS threads, and on builds up to v0.25.0, which predate the identifier — there kaappi features still tells you what your build has) |
| Explicit renaming macros | KEP-0006 | Final — shipped in v0.22.0; documented in Explicit Renaming Macros | (cond-expand ((library (srfi 211 explicit-renaming)) ...)) |
| Native subprocesses | KEP-0022 | Accepted — shipped after v0.25.0 | (cond-expand ((library (kaappi process)) ...)) — there is deliberately no kaappi-process identifier. Absent on wasm32-wasi, which has no process model, and under --sandbox, which excludes the library. See Running External Programs |
| Shared flat numeric buffers | KEP-0003 | Draft — unimplemented, gated behind KEP-0002 usage data | None — doesn't exist yet |
See Concurrency for how to use fibers, the reactor, and threads today, Explicit Renaming Macros for the procedural macro system, and Running External Programs for subprocesses.
Detecting subsystems from code¶
cond-expand (SRFI 0) is the
portable way to branch on what a given Kaappi build actually has, rather than
probing behavior at runtime:
(import (scheme base) (kaappi fibers))
(cond-expand
(kaappi-reactor
;; a multiplexed async I/O reactor is compiled in (true on every
;; target today, including wasm32-wasi)
(define (serve conn) (spawn (lambda () (handle conn)))))
(else
;; fall back to a single-connection blocking loop
(define (serve conn) (handle conn))))
A library that wants to degrade gracefully on a build where a subsystem is absent can check for the library itself instead of (or in addition to) the bare feature identifier:
(cond-expand
((library (kaappi fibers)) (import (kaappi fibers)))
(else (define (spawn thunk) (thunk)))) ; synchronous fallback
SRFI availability has the same two spellings: ((library (srfi <n>)) ...)
probes the import, and the shorthand srfi-<n> identifiers — e.g.
(cond-expand (srfi-1 ...)) — answer through the same check, so both
always agree with what (import (srfi <n>)) would do, including under
--sandbox and on WASM builds. They work in expression- and
define-library-level cond-expand alike. Like (library ...)
requirements, srfi-<n> is a derived probe resolved on demand rather
than a bare feature identifier, so it does not appear in (features).
| Identifier | True when |
|---|---|
kaappi-fibers |
(kaappi fibers) is compiled in — every target, including wasm32-wasi |
kaappi-reactor |
An OS-level I/O multiplexing reactor (kqueue/epoll/poll_oneoff) is compiled in |
kaappi-threads |
SRFI-18 OS threads are compiled in — every target except wasm32-wasi |
kaappi-diagnostics |
(kaappi diagnostics) (stable KP error codes on error objects) is compiled in — every target, including wasm32-wasi |
posix |
The build targets a POSIX-flavored platform — every target except Windows, including wasm32-wasi |
windows |
The build targets Windows — present in place of posix, so portable code can branch with (cond-expand (windows ...) (else ...)) |
srfi-<n> |
SRFI n is importable on this build — the derived probe described above, equivalent to ((library (srfi <n>)) ...) |
--sandbox mode and a WASI host's actual (as opposed to compiled-in) I/O
capability are runtime facts that cond-expand — an expand-time construct —
cannot express; see KEP-0004
for the open question on whether a companion runtime predicate is worth
adding.
Discovering capabilities from the command line¶
cond-expand answers these questions inside a program. kaappi features
answers them at the command line — for a person, or an agent, sizing up a build
before running anything:
$ kaappi features --json
{"version":"0.15.0", … ,"features":["r7rs","kaappi", … ,"kaappi-fibers",
"kaappi-reactor","kaappi-diagnostics","kaappi-threads"],
"srfis":{"builtin":[1,9,13, … ],"portable":[0,2,4, … ]},
"limits":{"initial_frame_capacity":480, … }}
It reports the version and git build id, target triple, build mode, the same
compiled-in subsystem identifiers as the table above (whether --sandbox is
available among them), and the built-in vs portable SRFIs — all derived from the
one table that drives cond-expand, so the CLI and the language can never
disagree about what a build has. Run kaappi features with no --json for a
human-readable table. See the
kaappi features reference
for the full JSON shape.
Further reading¶
- CONFORMANCE.md — exact R7RS and per-SRFI coverage
- README "Known limitations" — the source this page's gaps table summarizes
- Kaappi Enhancement Proposals — design rationale for everything in the Extensions table above
- Stability — what's guaranteed not to break, independent of spec conformance