Skip to content

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