diff --git a/docs/grammar.ebnf b/docs/grammar.ebnf index ba29ca7..113eff5 100644 --- a/docs/grammar.ebnf +++ b/docs/grammar.ebnf @@ -50,11 +50,12 @@ collection = list | vector | map ; nil = "nil" ; boolean = "true" | "false" ; -(* Numbers. Jolt accepts Clojure's numeric literal syntaxes, but — since Jolt - numbers are Janet ints/doubles — it has no distinct bignum, ratio or - BigDecimal types: the BigInt suffix N and BigDecimal suffix M are read as the - plain number, a ratio a/b is read as its double quotient, and radixed - integers are computed by base. The symbolic floats ##Inf/##-Inf/##NaN are +(* Numbers. Jolt carries a real numeric tower (JVM parity): an integer literal + reads as an exact integer (arbitrary precision), a ratio a/b as an exact + Ratio, a decimal/exponent literal as a double. The BigDecimal suffix M reads + as a real BigDecimal (unscaled x 10^-scale) — 1.5M, 0.0M, 3M; class is + java.math.BigDecimal. The BigInt suffix N reads as an exact integer. Radixed + integers are computed by base; the symbolic floats ##Inf/##-Inf/##NaN are also read. (No octal-with-leading-0 literal.) *) number = symbolic-value | [ sign ] , ( radix-int | ratio | hex-int | decimal ) ; @@ -62,10 +63,10 @@ sign = "+" | "-" ; integer = digit , { digit } ; hex-int = "0" , ( "x" | "X" ) , hex-digit , { hex-digit } , [ "N" ] ; radix-int = integer , ( "r" | "R" ) , alnum , { alnum } ; (* base 2..36: 2r1010, 16rFF, 36rZ *) -ratio = integer , "/" , integer ; (* read as a double quotient *) +ratio = integer , "/" , integer ; (* exact Ratio *) decimal = integer , [ "." , digit , { digit } ] , [ exponent ] , [ num-suffix ] ; exponent = ( "e" | "E" ) , [ sign ] , digit , { digit } ; -num-suffix = "N" | "M" ; (* BigInt / BigDecimal in Clojure; plain number in Jolt *) +num-suffix = "N" | "M" ; (* N = exact integer (BigInt); M = BigDecimal *) symbolic-value = "##Inf" | "##-Inf" | "##NaN" ; digit = "0".."9" ; hex-digit = digit | "a".."f" | "A".."F" ; diff --git a/docs/spec/02-reader.md b/docs/spec/02-reader.md index 6d7fb70..88e782b 100644 --- a/docs/spec/02-reader.md +++ b/docs/spec/02-reader.md @@ -43,7 +43,10 @@ exponent := [eE] ['+'|'-'] digits - S5. Trailing `N` (BigInt) and `M` (BigDecimal) suffixes are part of the grammar; their value semantics are the §4 numeric-tower question. Implementations without those towers SHOULD read them as the nearest - numeric type and MUST document the choice. + numeric type and MUST document the choice. The Chez host carries the full + tower: `N` reads as an exact integer (arbitrary precision) and `M` as a real + BigDecimal — `1.5M`, `0.0M`, `3M` — with value equality ignoring scale + (`1.0M = 1.00M`), `(class 1.5M)` ⇒ `java.math.BigDecimal`, and `decimal?` true. ### Symbols and keywords diff --git a/docs/spec/03-special-forms.md b/docs/spec/03-special-forms.md index e90a02d..2bb4f5b 100644 --- a/docs/spec/03-special-forms.md +++ b/docs/spec/03-special-forms.md @@ -17,7 +17,17 @@ 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. +reference; it differs from Scheme. A local *may* legally be named like a special +form and used in value position (`(let [if 5] if)` ⇒ `5`); only operator +position is reserved. **Macros, unlike special forms, ARE shadowable** by a local +(`(let [when (fn ...)] (when 1 2))` calls the local). + +A list form in operator position is resolved in this order (the canonical +read → **macroexpand** → analyze pipeline): a local binding shadows everything; +otherwise a macro head is expanded and the result re-analyzed; otherwise a +special-form head is parsed by rule; otherwise the form is a function +application. Macroexpansion therefore happens *before* special-form dispatch, so +a macro is never mistaken for a special form (and vice versa). --- @@ -135,10 +145,11 @@ S1–S3, E1 → jolt `forms-spec` let group; clojure-test-suite | `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 | +| `letfn` | mutually-recursive local fns (`letrec*` semantics — a fn body sees every binding, not only earlier ones). jolt treats `letfn` as a primitive special, not the reference's `letfn` macro → `letfn*` indirection; behavior is identical | | `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) | +| `set!` | `(set! *var* val)` sets the var's innermost thread binding, else its root, and returns val (implemented); a local (deftype mutable field) or host `(.-field obj)` target is host-dependent | | `.` / `new` | syntax only; behavior host-defined |