docs: language specification RFC + spec skeleton with normative exemplars

RFC 0001 proposes a normative, implementation-independent Clojure language
spec (the reader, evaluation model, special forms, data types, seq/laziness
contracts, namespaces/vars, and the portable clojure.core surface) to the
standard of R7RS/Racket — Clojure has none, and every alternative
implementation re-derives semantics from the reference and folklore. The
spec is executable-first: every numbered normative statement cites its
conformance test or is marked UNVERIFIED.

docs/spec/ carries the front matter (conformance terms, entry format, host
classification), the special-form catalog with worked normative entries for
if and let*, the core-library entry format with worked entries for first,
reduce, and parse-uuid, and a generated coverage dashboard over the 694-var
ClojureDocs inventory (tools/spec_coverage.py cross-references the surface
against jolt's interned+resolvable vars and the test suites).

Measured baseline: 380 implemented+tested, 154 implemented-untested, 35
portable-but-missing (filed), 22 resolvable-but-not-interned (filed — seed
fns invisible to resolve/ns-publics), rest classified host/JVM/concurrency.
This commit is contained in:
Yogthos 2026-06-10 10:53:44 -04:00
parent bd5142abac
commit 7003926eda
7 changed files with 1478 additions and 0 deletions

View file

@ -0,0 +1,143 @@
# §3 Special Forms
**Status**: catalog complete; normative exemplars for `if` and `let*`; the
remaining entries follow the same format (tracked in `coverage.md`).
A *special form* is a form whose head symbol is evaluated by rule rather than
by function application or macroexpansion. The special forms of Clojure are:
> `def` · `if` · `do` · `let*` · `fn*` · `loop*` · `recur` · `quote` · `var`
> · `throw` · `try`/`catch`/`finally` · `set!` · `monitor-enter` ·
> `monitor-exit` (host) · the interop forms `.` and `new` (host)
`let`, `fn`, `loop`, `and`, `or`, `when`, … are **macros** over these (§8);
implementations MUST treat them as redefinable macros, not additional special
forms. `monitor-enter`/`monitor-exit`, `.` and `new` are host forms: their
syntax is specified here, their behavior is host-defined.
Special-form head symbols are not shadowable: a binding named `if` does not
change the meaning of `(if ...)` in operator position. ⚠ This matches the
reference; it differs from Scheme.
---
### if — since 1.0
```
(if test then)
(if test then else)
```
**Semantics**
- S1. `test` MUST be evaluated first, exactly once.
- S2. Every value other than `nil` and `false` is *logically true*. If the
value of `test` is logically true, `then` MUST be evaluated and its value
returned; otherwise `else` (or `nil` when absent) MUST be evaluated and its
value returned.
- S3. The branch not taken MUST NOT be evaluated.
- S4. `if` MUST be usable in tail position with respect to `recur` (§3
`recur`): an `if` whose branch is a `recur` form is a valid recur target
path.
**Edge cases**
- E1. `(if test then)` with a logically false `test` evaluates to `nil`.
- E2. The empty collections (`()`, `[]`, `{}`, `#{}`), the number `0`, and
the empty string `""` are logically **true** (only `nil`/`false` are
false). ⚠ This differs from several Lisps and is a frequent divergence
source in alternative implementations.
**Errors**
- X1. `(if)` and `(if test)` with fewer than two argument forms, or more
than three, MUST be a compile-time error.
**Examples**
```clojure
(if 0 :t :f) ;=> :t
(if "" :t :f) ;=> :t
(if nil :t :f) ;=> :f
(if false :t) ;=> nil
```
**Conformance**
S1S3, E1E2 → jolt `forms-spec` "if/do/def" group; truthiness group in
`truthiness-spec`; clojure-test-suite `core_test/if.cljc`. S4 → `forms-spec`
fn/loop recur cases. X1 → UNVERIFIED (arity-error case to add).
---
### let* — since 1.0
```
(let* [sym₁ init₁ … symₙ initₙ] body…)
```
`let*` is the primitive sequential-binding form. The user-facing `let` macro
adds destructuring and expands to `let*` (§8); `let*` itself accepts **only
simple symbols** in binding positions.
**Semantics**
- S1. Each `initᵢ` MUST be evaluated in order, exactly once, in an
environment where `sym₁…symᵢ₋₁` are bound to their values (sequential
scope, as Scheme `let*`).
- S2. The body forms MUST be evaluated in order with all bindings in scope;
the value of the last body form is the value of the `let*` form. An empty
body evaluates to `nil`.
- S3. A later binding MAY rebind the same symbol; each binding creates a new
lexical binding visible from the next init onward (no mutation of the
earlier binding is implied).
- S4. Bindings are lexical and immutable: there is no form that assigns to a
`let*`-bound local. (Closures capture bindings by value; see §3 `fn*`.)
- S5. The binding vector MUST be a vector literal with an even number of
forms.
**Edge cases**
- E1. `(let* [] body)` is valid and equivalent to `(do body…)`.
- E2. Binding a symbol that names a var shadows the var for the lexical
extent of the body; `(var sym)` within that extent still denotes the var.
**Errors**
- X1. An odd number of binding forms MUST be a compile-time error.
- X2. A non-symbol in a binding position (e.g. a destructuring pattern) MUST
be a compile-time error for `let*` — destructuring belongs to the `let`
macro. ("Bad binding form, expected symbol" in the reference.)
**Examples**
```clojure
(let* [a 1 b (+ a 1)] (* a b)) ;=> 2
(let* [x 1 x (inc x)] x) ;=> 2
(let* [] 42) ;=> 42
```
**Conformance**
S1S3, E1 → jolt `forms-spec` let group; clojure-test-suite
`core_test/let.cljc`; jank corpus `form/let/*`. X2 → jolt
`destructuring-spec` "primitives reject patterns". S4, X1 → UNVERIFIED
(cases to add).
---
## Remaining entries (format above; status in coverage.md)
| Form | Notes for the entry author |
|---|---|
| `def` | var creation vs re-binding; metadata on the name; `(def x)` unbound; return value is the var |
| `do` | empty `(do)` → nil; top-level `do` splices for compilation units (important and under-documented) |
| `fn*` | arities, variadic `&`, closure capture, self-name, simple-symbol params only, recur target |
| `loop*` | recur arity must match bindings; recur rebinds in place |
| `recur` | tail-position rule (normative definition of tail position needed), across `if`/`do`/`let*`/`try` interactions |
| `quote` | self-evaluation table: which literals are self-evaluating unquoted |
| `var` | `#'` reader sugar; resolution at compile time |
| `throw` | any value vs Throwable — host question; jolt/cljs allow data, reference requires Throwable → classification needed |
| `try/catch/finally` | catch dispatch order, `:default`-style catch-all is a dialect extension (⚠ divergence note), finally evaluation guarantees, value of try |
| `set!` | host-dependent (dynamic vars + host fields) |
| `.` / `new` | syntax only; behavior host-defined |