Merge pull request #173 from jolt-lang/spike/chez-bootstrap

jolt build: standalone binaries (Phase 4 stages 1-2 + 4a)
This commit is contained in:
Dmitri Sotnikov 2026-06-23 03:29:44 +00:00 committed by GitHub
commit b59e5ecc42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
120 changed files with 1588 additions and 1655 deletions

View file

@ -86,7 +86,7 @@ reader/analyzer/IR/backend (`jolt-core/jolt/`) and `clojure.core` in
dependency-ordered tiers (`jolt-core/clojure/core/NN-*.clj`, loaded in order:
00-syntax, 00-kernel, 10-seq, 20-coll, 25-sorted, 30-macros, 40-lazy, 50-io).
The stdlib namespaces (`clojure.string`/`set`/`walk`/`edn`/`pprint`/…) are
portable Clojure under `src/jolt/clojure/`.
portable Clojure under `stdlib/clojure/`.
`bin/joltc` (`host/chez/cli.ss`) loads the checked-in seed
(`host/chez/seed/{prelude,image}.ss`) + the spine and compiles+evals on Chez
@ -114,3 +114,19 @@ Issue tracking and design notes live in beads (`bd prime`, `bd memories`).
is the contract.
- **Gate every change**: `make test` with a real exit code (self-host fixpoint,
corpus floor, unit, cli smoke, certify). Re-mint if a seed source changed.
## Writing style (comments, docstrings, docs)
Write like a human maintainer of a serious open-source project. Plain, terse,
factual. Document how the code works *now* — what it does and why it matters.
- No LLM tells: drop "Note that", "It's worth noting", "Importantly", "simply",
"essentially", "in order to", "under the hood", and marketing words
("comprehensive", "robust", "seamless", "leverage", "powerful").
- No historical exposition (how the code used to work, porting notes, "the prior
X"), no internal issue IDs (`jolt-xxxx`) or milestone tags ("Phase N") in
comments or docstrings. The git history and beads hold that.
- Keep genuine semantic contrasts with JVM Clojure — those document real,
user-visible behavior.
- Don't restate the code; explain the non-obvious. Match the surrounding file's
comment density and tone.

View file

@ -4,7 +4,7 @@
# build step. `make test` is the full gate. `make remint` rebuilds the seed after a
# source change.
.PHONY: test ci values corpus unit smoke selfhost sci certify ffi transient remint
.PHONY: test ci values corpus unit smoke buildsmoke selfhost sci certify ffi transient remint
# Full gate (dev machine). Includes the self-host byte-fixpoint, which only holds
# on the same Chez that minted the seed.
@ -15,7 +15,7 @@ test: selfhost ci
# lockfile) — it RUNS correctly on any Chez, but `selfhost` rebuilds it and a
# different Chez version may emit byte-different (gensym/order) output, so the
# byte-fixpoint is a dev-machine check, not a CI one (jolt-8479).
ci: values corpus unit smoke sci ffi transient certify
ci: values corpus unit smoke buildsmoke sci ffi transient certify
@echo "OK: CI gates passed"
# Self-host fixpoint: bootstrap.ss rebuild == checked-in seed.
@ -38,6 +38,10 @@ unit:
smoke:
@sh host/chez/smoke.sh
# `jolt build` produces a working standalone binary.
buildsmoke:
@sh host/chez/build-smoke.sh
# SCI conformance: load borkdude/sci's source through joltc (floor-gated).
sci:
@chez --script host/chez/run-sci.ss

View file

@ -48,11 +48,16 @@ $ bin/joltc -e '(/ 1 2)'
## Architecture
A small Chez runtime (`host/chez/*.ss`: value model, persistent collections, seqs,
vars/namespaces, host interop) hosts a portable Clojure overlay (`jolt-core/`): the
reader/analyzer/IR/backend (`jolt-core/jolt/`) and `clojure.core` in
dependency-ordered tiers (`jolt-core/clojure/core/NN-*.clj`). The stdlib namespaces
(`clojure.string`/`set`/`walk`/`edn`/`pprint`/…) are portable Clojure under
`src/jolt/clojure/`.
vars/namespaces, host interop) hosts a portable Clojure overlay split across two
source roots by *when* they load:
- **`jolt-core/`** is baked into the seed — the compiler (`jolt-core/jolt/`:
reader/analyzer/IR/backend, plus `jolt.main`/`jolt.deps`) and `clojure.core` in
dependency-ordered tiers (`jolt-core/clojure/core/NN-*.clj`). Changing anything
here means re-minting the seed.
- **`stdlib/`** loads lazily at runtime off the source roots — the rest of the
standard library (`clojure.string`/`set`/`walk`/`edn`/`pprint`/…) plus the
`jolt.ffi` host library. Editing these needs no re-mint.
`bin/joltc` loads the checked-in seed and the spine, then compiles and evaluates on
Chez (read → analyze → IR → emit → eval). `host/chez/bootstrap.ss` rebuilds that
@ -61,31 +66,34 @@ reproduces the checked-in seed byte-for-byte).
## Differences from Clojure
Jolt targets Clojure semantics but runs on Chez, not the JVM.
Jolt targets Clojure semantics but runs on Chez, not the JVM. Most portable
Clojure runs unchanged — persistent collections (32-way-trie vectors, HAMT
maps/sets), the numeric tower (exact integers, bignums, ratios, doubles), lazy
and infinite sequences, transducers, destructuring, multimethods with
hierarchies, protocols/records (`deftype`/`defrecord`/`reify`/`extend-protocol`),
metadata, namespaces, atoms, `future`/`promise`/`agent`/`pmap`,
`clojure.core.async`, runtime `eval`/`load-string`/`defmacro`, and the full
reader (`#()`, `#_`, `#?`, tagged literals, `#"…"`) all behave as on the JVM.
`=` is category-aware (`(= 3 3.0)``false`) and `==` is value-equality, as in
Clojure. The genuine divergences:
- **Host platform.** No JVM and no Java interop — `import`, `gen-class`, `proxy` of
Java classes, and `java.*` are unavailable. A class token resolves to a name; a
small set of host classes is recognized for `instance?`.
- **Numbers.** The full Scheme numeric tower, matching the JVM: exact integers and
bignums, exact ratios (`(/ 1 2)``1/2`), and flonum doubles. `=` is
category-aware (`(= 3 3.0)``false`); `==` is value-equality (`(== 3 3.0)`
`true`). `integer?`/`int?` are exact integers, `float?`/`double?` are flonums,
`ratio?` is an exact non-integer. No `BigDecimal` (`decimal?` is always false).
- **Concurrency.** `future`/`promise`/`agent`/`pmap` run on real OS threads over a
**shared heap**, matching JVM semantics (not isolated-heap snapshots). Atoms use a
per-atom mutex with JVM-style CAS. `clojure.core.async` provides blocking channels
and `go`/`<!`/`>!`/`alts!`/`timeout`.
- **Regex.** Backed by [irregex](https://github.com/ashinn/irregex) (vendored),
PCRE/Java-style patterns.
- **Collections.** Immutable persistent vectors, cons lists, and HAMT maps/sets.
Hash-map/hash-set iteration order is unspecified — use `sorted-map`/`sorted-set`
when order matters. Transients are real mutable scratch collections.
Supported and Clojure-compatible: lazy/infinite sequences, transducers,
destructuring, multimethods with hierarchies, protocols/records
(`deftype`/`defrecord`/`reify`/`extend-protocol`), metadata, namespaces, runtime
`eval`/`load-string`/`defmacro`, and the reader (`#()`, `#_`, `#?`, tagged literals,
`#"…"`).
- **No JVM, no Java interop.** No reflection, no `gen-class`/`proxy`. Interop
syntax (`Class.`, `Class/static`, `.method`) resolves only against a shimmed
subset of the `java.*` standard library; a class token is a name, not a loaded
class. See [docs/host-interop.md](docs/host-interop.md). To call C libraries
directly, use the `jolt.ffi` foreign-function interface (how the db and
http-client libraries bind SQLite/libpq and sockets/OpenSSL/zlib).
- **No `BigDecimal`.** `decimal?` is always false and there is no `M` literal;
the rest of the numeric tower matches the JVM.
- **No STM.** No `ref`/`dosync`/`alter`/`commute` — coordinated shared state uses
atoms (per-atom mutex, JVM-style CAS). The concurrency primitives above are
otherwise present and run on a shared heap.
- **Regex engine.** Patterns compile through
[irregex](https://github.com/ashinn/irregex) (vendored), not
`java.util.regex`; common patterns work, Java-specific features can differ.
- **Coverage.** `clojure.core` is implemented function by function against the
JVM-sourced conformance corpus — broad but not total; a namespace can load with
most functions working and a few not yet implemented.
## Test

View file

@ -17,12 +17,12 @@ absolute reference.
| Benchmark | Axis | Pass it exercises | Source |
|---|---|---|---|
| `binary-trees` | allocation / GC pressure (escaping short-lived records) | jolt-15jq scalar-replace, jolt-8flj escape analysis | CLBG |
| `dispatch` | polymorphic (**megamorphic**) protocol dispatch | jolt-41m devirt, inline-cache | AWFY-style |
| `mono-dispatch` | **monomorphic** protocol dispatch (devirt/inline-cache *can* fire) | jolt-41m devirt, jolt-ez5h inline-cache | AWFY-style |
| `collections` | persistent map/vector churn (HAMT / 32-way tries) | persistent structures (jolt-684u/0hbr), transients | CLBG k-nucleotide-style |
| `mandelbrot` | pure float compute (tight arith loops, no alloc/dispatch) | jolt-3pl native arith, loop codegen | CLBG |
| `fib` | recursion: function-call + integer-arith overhead | jolt-3pl native arith, jolt-826 small-fn inlining | CLBG |
| `binary-trees` | allocation / GC pressure (escaping short-lived records) | scalar-replace, escape analysis | CLBG |
| `dispatch` | polymorphic (**megamorphic**) protocol dispatch | devirt, inline-cache | AWFY-style |
| `mono-dispatch` | **monomorphic** protocol dispatch (devirt/inline-cache *can* fire) | devirt, inline-cache | AWFY-style |
| `collections` | persistent map/vector churn (HAMT / 32-way tries) | persistent structures, transients | CLBG k-nucleotide-style |
| `mandelbrot` | pure float compute (tight arith loops, no alloc/dispatch) | native arith, loop codegen | CLBG |
| `fib` | recursion: function-call + integer-arith overhead | native arith, small-fn inlining | CLBG |
What the ray tracer does **not** capture and these do: allocation as the
bottleneck (~7% there), megamorphic *and* monomorphic dispatch (its dispatch is
@ -35,17 +35,17 @@ control with record state), k-nucleotide proper.
## Holistic scorecard
`JVM=1 bench/run.sh` runs each benchmark on jolt **and** JVM Clojure and prints
the jolt/JVM ratio — the epic's (jolt-ffn) absolute-reference scorecard. As of
the jolt/JVM ratio — the absolute-reference scorecard. As of
the broadening (2026-06-16), ratios cluster by axis:
- **pure compute** (`mandelbrot`) is the floor, ~15× — native arith (jolt-3pl)
- **pure compute** (`mandelbrot`) is the floor, ~15× — native arith
already gets jolt closest to the JVM.
- **collections** ~28×, **fib** ~37×.
- **dispatch** ~75× (megamorphic), and `mono-dispatch` is *worse* (~110×): the
JVM inline-caches a runtime-monomorphic call site to near-free, while jolt does
a full registry dispatch regardless (devirt only fires on *statically* proven
receivers, which `reduce` over a vector doesn't give). This is the signal for
the call-site inline cache (jolt-ez5h).
the call-site inline cache.
- **allocation** (`binary-trees`) is the widest gap — but also the most inflated
by host memory pressure, so read it as "alloc is the worst axis," not a precise
multiple. Numbers are machine-specific; regenerate with `JVM=1 bench/run.sh`.

View file

@ -1,7 +1,7 @@
;; binary-trees (Computer Language Benchmarks Game) — an ALLOCATION/GC stress
;; test. Builds and discards millions of short-lived `Node` records; the nodes
;; ESCAPE (stored in the tree, walked later), so this is the regime jolt-8flj
;; (escape analysis) targets and the ray tracer never exercises (~7% alloc).
;; ESCAPE (stored in the tree, walked later), so this is the regime escape analysis
;; targets and the ray tracer never exercises (~7% alloc).
;;
;; Portable Clojure: runs on jolt and JVM Clojure for cross-impl comparison.
;; jolt -m binary-trees 14 (JOLT_DIRECT_LINK=1 JOLT_WHOLE_PROGRAM=1)

View file

@ -1,7 +1,7 @@
;; dispatch — a POLYMORPHIC-DISPATCH stress test. A protocol method is called in
;; a hot loop over a heterogeneous (megamorphic) collection of record types, with
;; minimal per-call work, so protocol dispatch dominates. This is the regime
;; jolt-41m (devirtualization) and the inline-cache target, and the one the ray
;; devirtualization and the inline-cache target, and the one the ray
;; tracer can't reveal — its dispatch is monomorphic and a small fraction of the
;; float-math cost (devirt measured FLAT there).
;;

View file

@ -1,7 +1,7 @@
;; fib — naive recursive Fibonacci: pure function-call + integer-arithmetic
;; throughput, with no allocation, dispatch, or collections. Isolates call
;; overhead and native integer arith (jolt-3pl), and is the natural target for
;; single-call-site / small-fn inlining (jolt-826) and self-call direct-linking.
;; overhead and native integer arith, and is the natural target for
;; single-call-site / small-fn inlining and self-call direct-linking.
;;
;; Portable Clojure (jolt + JVM Clojure).
;; jolt -m fib 32 (JOLT_DIRECT_LINK=1 JOLT_WHOLE_PROGRAM=1)

View file

@ -3,7 +3,7 @@
;; dispatch, no collections in the hot loop — just double arithmetic and tight
;; recur loops. This isolates the irreducible-math axis the ray tracer is bound
;; on (where devirt/alloc passes measured flat), so it tracks native-arith codegen
;; (jolt-3pl) and loop quality directly.
;; and loop quality directly.
;;
;; Portable Clojure (jolt + JVM Clojure).
;; jolt -m mandelbrot 1000 (JOLT_DIRECT_LINK=1 JOLT_WHOLE_PROGRAM=1)

View file

@ -1,5 +1,5 @@
;; mono-dispatch — protocol dispatch where every call site sees ONE record type
;; (monomorphic). This is the regime where devirtualization (jolt-41m) and a
;; (monomorphic). This is the regime where devirtualization and a
;; call-site inline cache CAN fire — the megamorphic `dispatch` bench deliberately
;; defeats them, so this is its complement: it measures how close a proven/cached
;; monomorphic dispatch gets to a direct call. Same per-call work as `dispatch`.

View file

@ -3,7 +3,7 @@
#
# Each benchmark isolates an axis the ray tracer (float-compute-bound) doesn't
# capture — see README.md. Run back-to-back against `main` to measure a pass's
# impact (the same protocol as test/bench/core-bench.janet).
# impact.
#
# bench/run.sh # default sizes, whole-program optimization on
# JOLT_WHOLE_PROGRAM=0 bench/run.sh # compare with WP off
@ -20,12 +20,12 @@ export JOLT_PATH="$PWD"
# name:default-arg (arg sized to run in a few seconds each). Axes: allocation
# (binary-trees), megamorphic vs monomorphic dispatch, persistent-collection
# churn (collections — now O(log n) via the HAMT, jolt-684u, so sized up), pure
# churn (collections — now O(log n) via the HAMT, so sized up), pure
# float compute (mandelbrot), call+arith recursion (fib).
BENCHES="binary-trees:14 dispatch:2000 mono-dispatch:2000 collections:30000 mandelbrot:200 fib:30"
# JVM=1 also runs each bench on JVM Clojure and prints a jolt/JVM ratio — the
# holistic absolute-reference scorecard for the optimization epic (jolt-ffn).
# holistic absolute-reference scorecard for the optimization work.
run_one() {
ns="${1%%:*}"; arg="${2:-${1##*:}}"
jmean=$(jolt -m "$ns" "$arg" 2>&1 | awk '/^mean:/{print $2}')

File diff suppressed because one or more lines are too long

View file

@ -3,10 +3,11 @@
===========================================================================
This grammar specifies the surface syntax accepted by Jolt's reader
(src/jolt/reader.janet) the text that `read`/`parse-string`/`load-string`
turn into data/forms. It is the syntactic half of Jolt's contract; the
behavioural half lives in test/spec/. Where Jolt diverges from Clojure the
difference is called out in a comment.
(host/chez/reader.ss, with the portable half in jolt-core/jolt/reader.clj)
the text that `read`/`read-string`/`load-string` turn into data/forms. It is
the syntactic half of Jolt's contract; the behavioural half lives in the
conformance corpus (test/chez/corpus.edn, see docs/spec/02-reader.md). Where
Jolt diverges from Clojure the difference is called out in a comment.
Notation (ISO-ish EBNF):
= definition | alternation
@ -128,7 +129,7 @@ meta-form = map | keyword | symbol | string ;
a keyword -> {keyword true}; a map is used as-is. A keyword/symbol/string
meta-form on a symbol rides ON the symbol (it stays a bare symbol, so a hint
like ^String is transparent in params/lets/bodies). A MAP meta-form routes
through a runtime (with-meta form ...) even on a symbol (jolt-8w2), so a name
through a runtime (with-meta form ...) even on a symbol, so a name
with ^{:map} metadata reads as a form, not a bare symbol def/defn/defmacro/ns
unwrap that to the bare name (and attach the metadata). *)
@ -152,7 +153,7 @@ anon-arg = "%" | "%" , digit , { digit } | "%&" ;
var-quote = "#'" , symbol ; (* (var symbol) *)
(* Regex literal -> a Janet PEG-backed regex value.
(* Regex literal -> an irregex-backed regex value.
Supported: groups, greedy/lazy quantifiers, (?:..), lookahead (?=..)/(?!..),
alternation, anchors ^ $ \b \B, classes, (?i). NOT: lookbehind,
backreferences, named groups. *)

175
docs/host-interop.md Normal file
View file

@ -0,0 +1,175 @@
# Host interop and JVM standard-library shims
Jolt runs on Chez Scheme, not the JVM, so there are no real Java classes behind
interop forms. Instead the runtime ships shims for the slice of the JVM standard
library that portable Clojure code reaches for, so libraries written against
`clojure.core` and common `java.*` classes run unchanged. The Clojure interop
syntax works against these shims:
```clojure
(Math/sqrt 2) ; static call
Math/PI ; static field
(StringBuilder.) ; constructor
(.append sb "x") ; instance method
(instance? String "hi") ; class token
```
A class token (`String`, `java.util.UUID`, …) resolves to a name; there is no
reflection and no class hierarchy. `(class x)` returns the JVM class name for the
scalar/collection types Clojure programs compare against (`"java.lang.Long"`,
`"java.lang.String"`, and so on).
## What's shimmed
This is the surface today, not the whole JVM. Methods not listed generally
aren't implemented; a few are accepted but no-ops (noted inline).
### Numbers and language
- **`java.lang.Math`** — `sqrt` `cbrt` `pow` `exp` `log` `log10` `floor` `ceil`
`round` `abs` `max` `min` `sin` `cos` `tan` `asin` `acos` `atan` `signum`
`random`; fields `PI`, `E`. (`clojure.math` mirrors these as functions.)
- **`Long` / `Integer`** — `parseLong`/`parseInt`/`valueOf` (optional radix),
`MAX_VALUE`, `MIN_VALUE`; `(Integer. x)`.
- **`Double` / `Float`** — `parseDouble`, `valueOf`, `toString`, `isNaN`,
`isInfinite`, the `*_VALUE`/`*_INFINITY`/`NaN` fields; `(Double. s)`.
- **`Boolean`** — `parseBoolean`, `TRUE`, `FALSE`.
- **`Character`** — `isUpperCase` `isLowerCase` `isDigit` `isWhitespace` (ASCII).
- **Boxed-number methods** — every number answers `.intValue` `.longValue`
`.doubleValue` `.floatValue` `.byteValue` `.shortValue` `.toString`
`.hashCode` (integer projections wrap modulo their width, as on the JVM).
- **`java.lang.System`** — `currentTimeMillis` `nanoTime` `exit` `getProperty`
`setProperty` `clearProperty` `getProperties` `getenv`.
- **`java.lang.Thread`** — `sleep` (real), `yield`/`interrupted` (no-ops),
`currentThread`.
- **`java.lang.Object`** — `(Object.)` as a fresh-identity sentinel; `.toString`
`.hashCode` `.equals` `.getClass` work on any value.
- **`java.lang.Class`** — `forName`.
### Strings and text
- **`java.lang.String`** statics — `valueOf`, `format` (the `clojure.core/format`
engine; `String/format` with a leading locale is accepted). Instance methods
go through `clojure.string` / the native string ops.
- **`StringBuilder`** — `append` `toString` `length` `charAt` `setLength`.
- **`java.text.NumberFormat`** — `getInstance` `getNumberInstance`
`getIntegerInstance`; `.format`, `.setGroupingUsed`,
`.setMinimum/MaximumFractionDigits`.
- **`java.util.StringTokenizer`** — `hasMoreTokens` `countTokens` `nextToken`.
- **`java.util.regex.Pattern`** — `compile` (with `Pattern/MULTILINE`), `quote`;
`.split`, `.pattern`. (`#"…"` literals and `clojure.string` regex fns are the
usual entry points.)
### Collections (mutable)
- **`java.util.ArrayList`** — `add` `get` `set` `size` `isEmpty` `remove` `clear`
`contains` `toArray` `iterator`.
- **`java.util.HashMap`** — `put` `get` `getOrDefault` `containsKey`
`containsValue` `size` `isEmpty` `remove` `clear` `putAll` `keySet` `values`
`entrySet`.
### I/O
- **`java.io.File`** — `(File. path)` / `(File. parent child)`; `getPath`
`getName` `getAbsolutePath` `getCanonicalPath` `toURI` `toURL` `exists`
`isDirectory` `isFile` `listFiles` `getParent`.
- **`java.io.StringReader` / `StringWriter` / `PushbackReader`** — the
`read`/`readLine`/`mark`/`reset`/`unread`/`write`/`append`/`toString` surface
the reader and `with-out-str` rely on.
- **`java.lang.ClassLoader`** — `getSystemClassLoader`, `.getResource`,
`.getResourceAsStream` (resolved against the source roots).
### Time and date
- **`java.util.Date`** — `(Date.)` / `(Date. ms)`; `getTime` `toInstant`
`toLocalDate(Time)` `before` `after` `equals` `toString` (RFC 3339).
- **`java.time`** — `Instant` (`now`, `ofEpochMilli`, `toEpochMilli`, `atZone`),
`LocalDateTime`, `ZoneId`, `DateTimeFormatter` (`ofPattern`, `ISO_LOCAL_*`,
localized styles), `FormatStyle`.
- **`java.text.SimpleDateFormat`** — `(SimpleDateFormat. pattern)`; `parse`
`format` `toPattern` `applyPattern` (`setTimeZone`/`setLenient` accepted but
ignored — formatting is UTC).
- **`java.util.TimeZone`** / **`java.util.Locale`** — constructed and passed
through; only UTC is honored for formatting.
### Net, encoding, misc
- **`java.net.URL`** — `(URL. spec)`; `toString` `toExternalForm` `getProtocol`
`getPath` `getFile`.
- **`java.net.URI`** — full component accessors (`getScheme` `getHost` `getPort`
`getPath` `getQuery` `getFragment`, raw variants, `isAbsolute`).
- **`java.util.Base64`** — `getEncoder`/`getDecoder` with `encode`,
`encodeToString`, `decode`.
- **`java.nio.charset.Charset`** — `forName`.
- **`java.util.UUID`** — `randomUUID`, `fromString`; `(UUID. s)`.
- **Exceptions**`Throwable` `Exception` `RuntimeException`
`IllegalArgumentException` `IllegalStateException` `IOException`
`NumberFormatException` `ArithmeticException` `NullPointerException`
`ClassCastException` `IndexOutOfBoundsException` `FileNotFoundException`
`UnsupportedOperationException` and the common network exceptions, each with
the `(E.)` / `(E. msg)` / `(E. msg cause)` / `(E. cause)` constructors.
What's deliberately absent: STM (`clojure.lang.LockingTransaction/isRunning`
returns `false`), reflection, `gen-class`/`proxy` of Java classes, and
`BigDecimal`.
## Adding your own shim from a library
The built-in shims above are baked into the seed. A library or project can
register its **own** host classes at load time — no seed re-mint, no host edits.
Put the registration calls at the top level of a namespace your code requires.
Four functions (in `clojure.core`) plus the tagged-table seam (in `jolt.host`)
cover it.
`__register-class-ctor!` makes `(Name. …)` work; `__register-class-statics!`
makes `Name/field` and `(Name/method …)` work; `__register-class-methods!`
attaches instance methods to a tagged value; `__register-instance-check!` teaches
`instance?` about your class. **Method and static names are strings** (they match
the literal name in the interop form).
A stateful object is a *tagged table*`jolt.host/tagged-table` creates one,
`ref-put!`/`ref-get` set and read its fields. Read the tag back with
`jolt.host/ref-get` (or test it with `jolt.host/table?`); a plain `get` /
keyword lookup deliberately can't see a wrapper's own `:jolt/type`.
```clojure
(ns mylib.greeter
(:require [jolt.host :as host]))
;; (Greeter. name) -> a tagged value carrying its name
(__register-class-ctor! "Greeter"
(fn [name] (-> (host/tagged-table :greeter)
(host/ref-put! :name name))))
;; (.hello g) -> instance method, keyed by the literal method name
(__register-class-methods! :greeter
{"hello" (fn [self] (str "hi " (host/ref-get self :name)))})
;; Greeter/VERSION (field) and (Greeter/make x) (static method)
(__register-class-statics! "Greeter"
{"VERSION" "1.0"
"make" (fn [name] (Greeter. name))})
;; (instance? Greeter x)
(__register-instance-check!
(fn [class-name v]
(when (= class-name "Greeter")
(and (host/table? v) (= :greeter (host/ref-get v :jolt/type))))))
```
```clojure
(.hello (Greeter. "ada")) ;=> "hi ada"
Greeter/VERSION ;=> "1.0"
(.hello (Greeter/make "bob")) ;=> "hi bob"
(instance? Greeter (Greeter. "x")) ;=> true
```
An instance-check predicate returns `true`/`false` to decide, or `nil` to defer
to the next registered check and the built-ins — so several libraries can
register checks without clobbering each other. This is the mechanism jolt's
HTTP client library uses to emulate `java.net.URL` and `HttpURLConnection` so
`clj-http-lite` runs unchanged.
Extending a *built-in* class instead (adding a method to core's `String` shim,
say) means editing the relevant `host/chez/*.ss` file and running `make remint`
— see [building-and-deps.md](building-and-deps.md).

View file

@ -77,6 +77,6 @@ per-context opt-in, exactly how the SCI bootstrap now loads
- Loading clj-ecosystem libraries via deps requires deciding their feature
set; the deps loader currently inherits the process default — a future
refinement is per-dependency feature configuration (filed with the deps
work, jolt-dw4).
work).
- `.cljc` authors targeting jolt can write `:jolt` branches and rely on
`:default` fallbacks.

View file

@ -1,6 +1,6 @@
# RFC 0005 — Structural collection-type inference
- **Status**: Implemented (jolt-5uj). Ray tracer 12.8s to 11.0s hint-free,
- **Status**: Implemented. Ray tracer 12.8s to 11.0s hint-free,
matching the explicit `^:struct` version; render checksum unchanged.
- **Champions**: jolt maintainers
- **Created**: 2026-06-13
@ -14,7 +14,7 @@ function its parameter and return types, recursively. A keyword lookup returns
the looked-up field's type, so nested access like `(:r (:direction ray))` is
typed end to end. This unifies the two facts the current inference tracks
inconsistently (a vector's element type, but not a map's field types), subsumes
the existing inference phases (jolt-99x Phases 0 to 3) as special cases, and
the existing inference passes as special cases, and
closes the remaining ray-tracer gap without a hint. The system is a
soft-typing-style inference: it never rejects a program, it assigns a concrete
type only when it can prove one, and it falls back to `:any` (and the existing
@ -22,7 +22,7 @@ runtime guard) everywhere else.
## Motivation
The inference added in jolt-99x specializes a collection access (drops the
The existing inference specializes a collection access (drops the
`:jolt/type` guard, emits `pv-count`, and so on) when it can prove the
collection's type. It works, it is sound, and it is fully dynamic-fallback
safe. But its type lattice grew ad hoc:
@ -96,7 +96,7 @@ are depth 2 to 3, well inside the cap.
Inference is a forward pass producing `[type node']` for each IR node (the
existing shape), threaded with a local type environment and the
inter-procedural state from Phase 1. The rules are uniform over the structural
inter-procedural state. The rules are uniform over the structural
type:
- **Literals.** `{:k v ...}` with constant scalar keys and struct-safe values
@ -115,9 +115,9 @@ type:
signature: core fns from a fixed signature table (below), user fns from the
inter-procedural fixpoint's inferred signature.
The Phase 1 inter-procedural fixpoint, recompile, escape gate, and closed-world
assumption (RFC to follow / jolt-767) are unchanged. They now propagate
structural types instead of flat tags.
The inter-procedural fixpoint, recompile, escape gate, and closed-world
assumption are unchanged. They now propagate structural types instead of flat
tags.
## Core function signatures
@ -266,8 +266,8 @@ plus a signature table.
tables and HOF handling).
4. The back end keeps reading the use-site type to specialize (guard drop for
`{:struct}`, `pv-count`/`pv-nth` for `{:vec}`), now uniformly.
5. Keep the Phase 1 fixpoint, recompile, escape gate, and triggering as is; they
propagate structural types.
5. Keep the inter-procedural fixpoint, recompile, escape gate, and triggering as
is; they propagate structural types.
The phases land incrementally behind the same optimization-mode gate, each
verified against conformance (three modes), the full test gate, and the
@ -298,5 +298,6 @@ ray-tracer benchmark, exactly as the current phases were.
param/return inference is enough for the collection-specialization goal;
full function types matter more for the type-checker (RFC 0006) and could be
deferred.
- **Closed-world boundary.** Inherited from Phase 1: param/return inference
assumes the compiled unit is the whole program. Documented there; unchanged.
- **Closed-world boundary.** Inherited from the inter-procedural pass:
param/return inference assumes the compiled unit is the whole program.
Documented there; unchanged.

View file

@ -2,10 +2,10 @@
- **Status**: Implemented. Core-fn error domains (arithmetic on non-numbers,
count/first/rest/next/seq/nth on non-seqable scalars), `JOLT_TYPE_CHECK=
off|warn|error`. Follow-ups landed: bounded scalar **unions** (jolt-pz5) so a
off|warn|error`. Follow-ups landed: bounded scalar **unions** so a
use is reported only when every member is in the error domain; **user-fn
error domains** behind `JOLT_TYPE_CHECK_USER` (jolt-zo1, closed-world);
precise **file:line:col** locations (jolt-fqy). The checker is now one
error domains** behind `JOLT_TYPE_CHECK_USER` (closed-world);
precise **file:line:col** locations. The checker is now one
inference walk (folded into `infer`), and is **on by default in direct-link
builds** — where it piggybacks on the specialization inference for ~free —
and opt-in (`JOLT_TYPE_CHECK`) in plain builds.
@ -203,22 +203,22 @@ smallest high-confidence table (arithmetic and seq/count/nth/first), and grow.
destroys trust. Mitigation: start tiny, test each entry against the runtime,
grow slowly. Open question: derive the table from the same machinery the
runtime uses, to avoid drift?
- **Unions.** *Resolved (jolt-pz5).* The lattice has a bounded scalar union
- **Unions.** *Resolved.* The lattice has a bounded scalar union
`{:union #{T...}}` (cap 4); differing if-branches form a union instead of
collapsing to `:any`, and a use is reported only when *every* member is in the
error domain. Unions are opaque to structural specialization, so codegen is
unchanged.
- **User-function signatures.** *Resolved (jolt-zo1), opt-in.* Behind
- **User-function signatures.** *Resolved, opt-in.* Behind
`JOLT_TYPE_CHECK_USER`: the checker re-checks a registered non-redefinable
user fn's body with one parameter bound to its concrete argument type; a
diagnostic the all-`:any` body did not have means that argument is provably
wrong. Monotonic, so still no false positives; closed-world, hence opt-in.
- **Negative/never types.** *Resolved (jolt-wwy).* Calling a provably
- **Negative/never types.** *Resolved.* Calling a provably
non-callable value (`:num`/`:str` — keywords/maps/vectors/sets are IFn) is
reported at the default level; wrong-arity to a registered single-fixed-arity
user fn is reported under the `JOLT_TYPE_CHECK_USER` opt-in. A union callee is
flagged only when every member is non-callable.
- **Position vs intent.** *Resolved (jolt-fqy).* The reader records each list
- **Position vs intent.** *Resolved.* The reader records each list
form's absolute offset (identity-keyed, so positions survive macroexpansion
exactly when the user's sub-form is spliced through); the analyzer stamps it
onto `:invoke` nodes, the checker carries it into each diagnostic, and the

View file

@ -0,0 +1,186 @@
# RFC 0007 — Compilation modes and binary output
- **Status**: Draft. No code yet; this fixes the design before Phase 4 work
(beads `jolt-cf1q.5`) starts.
- **Champions**: jolt maintainers
- **Created**: 2026-06-22
## Summary
Give jolt a `jolt build` command that emits a standalone executable, and a
three-mode model that trades dynamism for speed:
- **dev** — open/indirect linking, redefinition works, no perf focus. What
`repl`/`-e`/`nrepl` already are.
- **release** (default for a built program) — direct-linked, closed-world,
per-namespace inference. Fast, still a recognizable Clojure runtime.
- **optimized** — whole-program inference, `fl*`/`fx*` typed emission, Chez
whole-program optimization. Fastest, sacrifices dynamic redefinition.
All three already have their machinery in the tree — the inference and inline
passes were ported into `jolt-core/jolt/passes/`. What is missing is (a) a code
path that writes emitted Scheme to disk and AOT-compiles it instead of
eval'ing it in process, and (b) a switch that turns the dormant passes on. This
RFC specifies both.
## Motivation
The Janet host could produce binaries (`jolt uberscript` with dead-code
elimination, `jolt cgen-build` for a single native binary). The Chez rehost
dropped that machinery with the Janet host — it was Janet-specific (IR→C made
sense when the host was Janet). On Chez the natural target is Chez's own native
compiler, so the old emitters were deleted rather than re-pointed.
The result today: `bin/joltc` only ever loads the checked-in seed and
compile-evals in process. `jolt.main/-main` dispatches `run / -M / -A / repl /
nrepl / task` and nothing else. There is no way to ship an app as a binary, and
the optimization passes are inert — `jolt.host/inline-enabled?` is a stub
returning `#f` (`host/chez/host-contract.ss:283`), so every call links
indirectly and nothing inlines. Jolt on Chez runs only in what this RFC calls
dev mode.
The passes themselves survived intact:
- `jolt/passes/types.clj` — structural collection-type inference (RFC 0005) +
success-type checking (RFC 0006).
- `jolt/passes/inline.clj` — inline + flatten-lets + scalar-replace, already
gated "direct-link only".
- `jolt/passes/fold.clj` — const-fold, including predicate folding.
So this is not a port of lost code. It is wiring: a build front-end, a
file-emitting back-end path, and a mode switch over passes that already exist.
## The three modes
| Mode | Linking | Inference | Redefinition | Driver |
|---|---|---|---|---|
| **dev** | indirect (var-deref per call) | off | yes | `repl`, `-e`, `nrepl`, `run` of a file by default |
| **release** | direct, closed-world | per-namespace | no (closed world) | `jolt build` default |
| **optimized** | direct + whole-program | whole-program fixpoint, `fl*`/`fx*` | no | `jolt build --opt` / `-M`-style entry |
The modes are points on one axis (how much the back end may assume is fixed),
not three code paths. Each mode is a setting of two independent knobs the passes
already understand:
- **direct-link?** — may a call to a var compile to a direct procedure
reference instead of a `var-deref`? Enables inlining and call-site folding.
Opt-out is per-target: a `^:redef` or `^:dynamic` var always links indirect.
- **whole-program?** — does inference see the whole reachable program at once
(closed world), so a record param's callers in other namespaces are visible
and its field reads specialize? Without it, inference is per-namespace and a
cross-ns param de-specializes to `:any` (the cross-ns penalty documented in
the `cross-ns-param-penalty` memory; declared `^RecordType` hints are the
open-world escape hatch).
```
dev: direct-link? = false whole-program? = false
release: direct-link? = true whole-program? = false
optimized: direct-link? = true whole-program? = true
```
`fl*`/`fx*` typed emission (unchecked flonum/fixnum Scheme ops) rides on
optimized: only whole-program inference proves the types that make dropping the
numeric-tower dispatch sound. Release keeps the tower.
## CLI surface
```
jolt build [-m NS | FILE] [-o OUT] [--opt] [--dev]
```
- Resolves `deps.edn` exactly as `run` does (reuse `jolt.deps`).
- Default mode is **release**. `--opt` selects optimized; `--dev` builds an
unoptimized binary (useful to ship a debuggable build, not for the REPL).
- `-o` names the output (default the entry ns / file stem).
- Output is a single executable: a Chez boot file plus the compiled program,
launched by a thin wrapper, or a fully linked image where the platform allows.
App libraries are baked in — no source roots needed at runtime.
Env opt-outs for the build (mirrors the Janet knobs, now keyed off the mode
rather than the run): `JOLT_NO_DIRECT_LINK` forces open linking even in a build,
`JOLT_NO_WHOLE_PROGRAM` keeps direct-link but per-namespace, `JOLT_WHOLE_PROGRAM=1`
forces whole-program. These already name the two knobs above.
## Emission pipeline
The in-process spine today (`host/chez/compile-eval.ss`) is, per form:
```
source → read → analyze (→ IR) → emit (→ Scheme string) → (eval (read …))
```
`jolt build` keeps everything up to `emit` and replaces the per-form `eval` with
accumulate-then-compile:
1. **Assemble the program.** Starting from the entry ns's `-main`, load the
transitive `require` graph (the loader already does this) and collect every
reachable top-level form, in dependency order, with its compile namespace.
2. **Dead-code elimination.** Re-target the uberscript DCE idea: compute
reachability from `-main` plus non-prunable forms, drop dead `defn`/`defn-`.
Bail to keep-all on `resolve`/`ns-resolve`/`requiring-resolve`/`find-var`/
`intern`/`eval`/`load-string` (anything that defeats static reachability);
keep and scan `defmethod`/`defrecord`/`extend` bodies so dispatch targets
stay live.
3. **Emit to a file.** Run `analyze → emit` for each surviving form under the
mode's knobs, concatenating the Scheme strings into one program source (the
core overlay prelude first, exactly as the seed image is built today).
4. **Compile.** Feed that source to Chez `compile-program` (release) or
`compile-whole-program` (optimized, which also lets Chez cross-module
inline), producing a compiled object, then link a boot file / wrapper into
the final executable.
Steps 34 are the only genuinely new back-end code. Step 2 is a re-port of a
deleted pass. Steps before them already run on every `joltc` invocation.
## Turning the passes on
`inline-enabled?` is the existing gate. Today `host-contract.ss` hardwires it to
`#f`. Under this RFC the build sets it (and a parallel `whole-program?` flag)
from the chosen mode before compiling, so:
- release: `inline-enabled?` → true, whole-program off. Per-ns inference and
inlining light up; `fl*`/`fx*` stays off.
- optimized: both on; the types pass runs its whole-program fixpoint and the
back end may emit unchecked numeric ops where a flonum/fixnum is proven.
No new pass is required to reach release — it is the ported passes, ungated.
## Staging
1. **Spike (de-risk Chez AOT).** Emit a trivial whole program to disk and prove
`compile-program` + boot/static link yields a standalone binary that runs.
This is the only real unknown.
2. **`jolt build` release.** Front-end + file-emitting back-end path + flip
`inline-enabled?` from the mode. Gate against the bench/corpus suites; binary
output must pass the corpus a `run` passes.
3. **DCE.** Re-port the reachability pass; gate with a test like the old
`uberscript-dce` case.
4. **Optimized.** Whole-program flag, `compile-whole-program`, `fl*`/`fx*`
emission. Gate on the bench suite (ray tracer, binary-trees) for size and
speed vs the spike baseline.
Each stage is TDD against the existing gates (`make test`, `make corpus`, the
`bench/` programs). Modes land behind the build command, so dev — the only mode
today — is unaffected until a stage proves out.
## Open questions
- **Static vs. boot-file linking.** A fully static Chez image is the smallest,
most portable artifact but the most work to link; a boot file plus a stub
launcher is the easy first cut. Spike decides which step 1 targets.
- **FFI in a built binary.** `jolt.ffi` loads native libraries at runtime; a
closed-world build still needs that to work. The build must bake the FFI
Clojure side and keep dynamic `dlopen` at run time.
- **Macro and `eval` at runtime.** Release/optimized are closed-world, but an
app that calls `eval`/`load-string` needs the compiler present. Either ship
the compiler image in the binary (larger) or reject those builds (the DCE
bail-out already detects the calls).
## Prior art in this repo
The optimization design these modes turn on is RFC 0004 (type hints), RFC 0005
(structural inference), RFC 0006 (success checking). The linking model — direct
linking as a per-unit property, `^:redef`/`^:dynamic` as the only opt-out — and
the cross-ns specialization penalty are recorded in beads memories
(`jolt-linking-model`, `cross-ns-param-penalty`). Phase 4 (`jolt-cf1q.5`) is the
tracking issue.

View file

@ -30,7 +30,7 @@ So a name's *home* is determined by two facts:
`clojure.core` is compiled ahead of time into the checked-in seed
(`host/chez/seed/{prelude,image}.ss`) as Scheme `def-var!` forms. The seed's
source twin is the overlay (`jolt-core/clojure/core/*.clj` plus the stdlib
namespaces under `src/jolt/clojure/`); `host/chez/emit-image.ss` re-emits the
namespaces under `stdlib/clojure/`); `host/chez/emit-image.ss` re-emits the
prelude from those sources on Chez. The build is a byte-fixpoint: rebuilding from
an up-to-date seed reproduces it exactly.

View file

@ -12,29 +12,29 @@ sources, and process: [`../rfc/0001-language-specification.md`](../rfc/0001-lang
| Doc | Content | Status |
|---|---|---|
| [`00-front-matter.md`](00-front-matter.md) | conformance terms, entry format, host classification | drafted |
| `01-evaluation.md``08-macros.md` | see chapter plan in front matter | planned |
| [`02-reader.md`](02-reader.md) | token grammar + reader-macro catalog | drafted |
| `01`, `04``08` | see chapter plan in front matter | planned |
| [`03-special-forms.md`](03-special-forms.md) | special-form catalog + normative exemplars (`if`, `let*`) | exemplars |
| [`09-core-library.md`](09-core-library.md) | per-var entry format + exemplars (`first`, `reduce`, `parse-uuid`) | exemplars |
| [`coverage.md`](coverage.md) | generated dashboard over the 694-var surface | generated |
| [`../grammar.ebnf`](../grammar.ebnf) | reader surface syntax (EBNF), companion to `02-reader.md` | reference |
Regenerate the dashboard after surface changes:
`python3 tools/spec_coverage.py` (requires `clojuredocs-export.json` in the
repo root and a working jolt checkout).
`python3 tools/spec_coverage.py` (reads `tools/clojuredocs-export.json` and
probes a working jolt checkout via `bin/joltc`).
## Current numbers (2026-06-10)
## Current numbers (2026-06-22)
Of the 694 `clojure.core` vars in the ClojureDocs inventory:
Of the 694 `clojure.core` vars in the ClojureDocs inventory, jolt interns 574.
Broadly:
- **380** implemented in jolt *and* exercised by the behavioral suites
- **154** implemented but not directly tested — each gets a test with its spec entry
- **35** portable but missing from jolt (`parse-long`/`parse-double`/
`parse-boolean`, `update-keys`/`update-vals`, `macroexpand`, `time`,
`partitionv`/`partitionv-all`/`splitv-at`, `with-redefs`, `with-open`,
reader fns, ns-introspection stragglers, …) — tracked as implementation gaps
- **22** resolvable in code but invisible to ns introspection
(`resolve`/`ns-publics` can't see seed-fallback names like `compare`,
`gensym`, `type`) — a conformance finding in its own right
- the rest classified host/JVM/concurrency (see dashboard)
- **568** implemented in jolt *and* exercised by the behavioral suites
- **6** implemented but not directly tested — each gets a test with its spec entry
- **6** portable but absent from jolt's resolvable surface (the REPL history
vars `*1`/`*2`/`*3`/`*e`, plus `letfn`/`re-groups`, which work but aren't
interned where `resolve` can see them) — tracked as gaps
- the rest classified host/JVM/concurrency (see the dashboard for the full
per-var breakdown — it is the source of truth)
## How this connects to the test suites

View file

@ -1,21 +1,21 @@
# Appendix A — Coverage Dashboard (generated)
Generated 2026-06-10 by `tools/spec_coverage.py` — do not edit by hand.
Generated 2026-06-22 by `tools/spec_coverage.py` — do not edit by hand.
Surface: **694** clojure.core vars (ClojureDocs export; 648 with
community examples). jolt interns 564 of them.
community examples). jolt interns 574 of them.
| Status | Count | Meaning |
|---|---|---|
| implemented+tested | 564 | in jolt and exercised by spec/conformance |
| implemented-untested | 0 | in jolt, no direct test — spec entries will add them |
| implemented+tested | 568 | in jolt and exercised by spec/conformance |
| implemented-untested | 6 | in jolt, no direct test — spec entries will add them |
| resolvable-not-interned | 0 | works in code but invisible to ns introspection (conformance finding) |
| missing-portable | 0 | portable semantics, jolt lacks it — implementation gap |
| special-form | 15 | specified in §3, not a library var |
| dynamic-var | 29 | classification needed: portable default vs host-dependent |
| agents-taps | 22 | out of scope pending concurrency design note |
| missing-portable | 6 | portable semantics, jolt lacks it — implementation gap |
| special-form | 16 | specified in §3, not a library var |
| dynamic-var | 24 | classification needed: portable default vs host-dependent |
| agents-taps | 16 | out of scope pending concurrency design note |
| stm-refs | 11 | out of scope pending concurrency design note |
| jvm-specific | 53 | catalogued, not specified |
| jvm-specific | 47 | catalogued, not specified |
Classifications are initial and mechanical — reclassifying is an ordinary
spec change. A var is *Verified* only when its §9 entry exists and carries no
@ -27,12 +27,12 @@ UNVERIFIED field; that column will be added as entries land.
|---|---|---|
| `*` | implemented+tested | ✓ |
| `*'` | implemented+tested | ✓ |
| `*1` | implemented+tested | ✓ |
| `*2` | implemented+tested | ✓ |
| `*3` | implemented+tested | ✓ |
| `*1` | missing-portable | ✓ |
| `*2` | missing-portable | ✓ |
| `*3` | missing-portable | ✓ |
| `*agent*` | dynamic-var | ✓ |
| `*allow-unresolved-vars*` | dynamic-var | ✓ |
| `*assert*` | dynamic-var | ✓ |
| `*assert*` | implemented+tested | ✓ |
| `*clojure-version*` | implemented+tested | ✓ |
| `*command-line-args*` | dynamic-var | ✓ |
| `*compile-files*` | dynamic-var | ✓ |
@ -40,21 +40,21 @@ UNVERIFIED field; that column will be added as entries land.
| `*compiler-options*` | dynamic-var | ✓ |
| `*data-readers*` | dynamic-var | ✓ |
| `*default-data-reader-fn*` | dynamic-var | ✓ |
| `*e` | implemented+tested | ✓ |
| `*err*` | dynamic-var | ✓ |
| `*e` | missing-portable | ✓ |
| `*err*` | implemented-untested | ✓ |
| `*file*` | dynamic-var | ✓ |
| `*flush-on-newline*` | dynamic-var | |
| `*fn-loader*` | dynamic-var | |
| `*in*` | implemented+tested | |
| `*math-context*` | dynamic-var | |
| `*ns*` | implemented+tested | ✓ |
| `*out*` | dynamic-var | ✓ |
| `*out*` | implemented-untested | ✓ |
| `*print-dup*` | dynamic-var | ✓ |
| `*print-length*` | dynamic-var | ✓ |
| `*print-level*` | dynamic-var | ✓ |
| `*print-meta*` | dynamic-var | ✓ |
| `*print-namespace-maps*` | dynamic-var | ✓ |
| `*print-readably*` | dynamic-var | ✓ |
| `*print-readably*` | implemented+tested | ✓ |
| `*read-eval*` | dynamic-var | ✓ |
| `*reader-resolver*` | dynamic-var | |
| `*repl*` | dynamic-var | |
@ -63,7 +63,7 @@ UNVERIFIED field; that column will be added as entries land.
| `*unchecked-math*` | implemented+tested | ✓ |
| `*use-context-classloader*` | dynamic-var | ✓ |
| `*verbose-defrecords*` | dynamic-var | |
| `*warn-on-reflection*` | dynamic-var | ✓ |
| `*warn-on-reflection*` | implemented-untested | ✓ |
| `+` | implemented+tested | ✓ |
| `+'` | implemented+tested | ✓ |
| `-` | implemented+tested | ✓ |
@ -77,7 +77,7 @@ UNVERIFIED field; that column will be added as entries land.
| `->VecSeq` | jvm-specific | |
| `-cache-protocol-fn` | jvm-specific | |
| `-reset-methods` | jvm-specific | |
| `.` | implemented+tested | ✓ |
| `.` | special-form | ✓ |
| `..` | implemented+tested | ✓ |
| `/` | implemented+tested | ✓ |
| `<` | implemented+tested | ✓ |
@ -98,8 +98,8 @@ UNVERIFIED field; that column will be added as entries land.
| `add-classpath` | jvm-specific | ✓ |
| `add-tap` | agents-taps | ✓ |
| `add-watch` | implemented+tested | ✓ |
| `agent` | agents-taps | ✓ |
| `agent-error` | agents-taps | ✓ |
| `agent` | implemented+tested | ✓ |
| `agent-error` | implemented+tested | ✓ |
| `agent-errors` | agents-taps | |
| `aget` | implemented+tested | ✓ |
| `alength` | implemented+tested | ✓ |
@ -131,7 +131,7 @@ UNVERIFIED field; that column will be added as entries land.
| `assoc-in` | implemented+tested | ✓ |
| `associative?` | implemented+tested | ✓ |
| `atom` | implemented+tested | ✓ |
| `await` | agents-taps | ✓ |
| `await` | implemented-untested | ✓ |
| `await-for` | agents-taps | ✓ |
| `await1` | agents-taps | |
| `bases` | jvm-specific | ✓ |
@ -183,7 +183,7 @@ UNVERIFIED field; that column will be added as entries land.
| `chunk-rest` | implemented+tested | ✓ |
| `chunked-seq?` | implemented+tested | ✓ |
| `class` | implemented+tested | ✓ |
| `class?` | jvm-specific | ✓ |
| `class?` | implemented+tested | ✓ |
| `clear-agent-errors` | agents-taps | |
| `clojure-version` | implemented+tested | ✓ |
| `coll?` | implemented+tested | ✓ |
@ -375,13 +375,13 @@ UNVERIFIED field; that column will be added as entries land.
| `lazy-cat` | implemented+tested | ✓ |
| `lazy-seq` | implemented+tested | ✓ |
| `let` | implemented+tested | ✓ |
| `letfn` | implemented+tested | ✓ |
| `letfn` | missing-portable | ✓ |
| `line-seq` | implemented+tested | ✓ |
| `list` | implemented+tested | ✓ |
| `list*` | implemented+tested | ✓ |
| `list?` | implemented+tested | ✓ |
| `load` | jvm-specific | ✓ |
| `load-file` | jvm-specific | ✓ |
| `load` | implemented+tested | ✓ |
| `load-file` | implemented-untested | ✓ |
| `load-reader` | jvm-specific | ✓ |
| `load-string` | implemented+tested | ✓ |
| `loaded-libs` | jvm-specific | ✓ |
@ -464,10 +464,10 @@ UNVERIFIED field; that column will be added as entries land.
| `partition-by` | implemented+tested | ✓ |
| `partitionv` | implemented+tested | |
| `partitionv-all` | implemented+tested | |
| `pcalls` | jvm-specific | ✓ |
| `pcalls` | implemented+tested | ✓ |
| `peek` | implemented+tested | ✓ |
| `persistent!` | implemented+tested | ✓ |
| `pmap` | jvm-specific | ✓ |
| `pmap` | implemented+tested | ✓ |
| `pop` | implemented+tested | ✓ |
| `pop!` | implemented+tested | ✓ |
| `pop-thread-bindings` | implemented+tested | |
@ -496,7 +496,7 @@ UNVERIFIED field; that column will be added as entries land.
| `proxy-name` | jvm-specific | |
| `proxy-super` | implemented+tested | ✓ |
| `push-thread-bindings` | implemented+tested | |
| `pvalues` | jvm-specific | ✓ |
| `pvalues` | implemented+tested | ✓ |
| `qualified-ident?` | implemented+tested | ✓ |
| `qualified-keyword?` | implemented+tested | ✓ |
| `qualified-symbol?` | implemented+tested | ✓ |
@ -512,7 +512,7 @@ UNVERIFIED field; that column will be added as entries land.
| `rational?` | implemented+tested | ✓ |
| `rationalize` | implemented+tested | ✓ |
| `re-find` | implemented+tested | ✓ |
| `re-groups` | implemented+tested | ✓ |
| `re-groups` | missing-portable | ✓ |
| `re-matcher` | implemented+tested | ✓ |
| `re-matches` | implemented+tested | ✓ |
| `re-pattern` | implemented+tested | ✓ |
@ -558,7 +558,7 @@ UNVERIFIED field; that column will be added as entries land.
| `reset-vals!` | implemented+tested | ✓ |
| `resolve` | implemented+tested | ✓ |
| `rest` | implemented+tested | ✓ |
| `restart-agent` | agents-taps | ✓ |
| `restart-agent` | implemented-untested | ✓ |
| `resultset-seq` | jvm-specific | ✓ |
| `reverse` | implemented+tested | ✓ |
| `reversible?` | implemented+tested | ✓ |
@ -568,8 +568,8 @@ UNVERIFIED field; that column will be added as entries land.
| `satisfies?` | implemented+tested | ✓ |
| `second` | implemented+tested | ✓ |
| `select-keys` | implemented+tested | ✓ |
| `send` | agents-taps | ✓ |
| `send-off` | agents-taps | ✓ |
| `send` | implemented+tested | ✓ |
| `send-off` | implemented+tested | ✓ |
| `send-via` | agents-taps | ✓ |
| `seq` | implemented+tested | ✓ |
| `seq-to-map-for-destructuring` | implemented+tested | ✓ |

View file

@ -1,12 +1,10 @@
;; async.ss (jolt-byjr) — clojure.core.async on real OS threads for the Chez host.
;; async.ss — clojure.core.async on real OS threads for the Chez host.
;;
;; No mature Chez fibers library exists, and this Chez is a threaded build, so a
;; `go` block is just an OS thread and a channel is a mutex+condition blocking
;; A `go` block is an OS thread and a channel is a mutex+condition blocking
;; queue: <! / >! are the blocking <!! / >!! (they "park" by blocking the thread).
;; <! / >! work ANYWHERE (no CPS transform) —
;; here because they are ordinary blocking calls. Real parallelism, shared heap.
;; Trade-off: one OS thread per go block (fine for typical use / conformance, not
;; for thousands of simultaneous go blocks).
;; <! / >! work ANYWHERE — no CPS transform — because they are ordinary blocking
;; calls. Real parallelism, shared heap. Trade-off: one OS thread per go block
;; (fine for typical use, not for thousands of simultaneous go blocks).
;;
;; Channel: an unbuffered channel is a rendezvous (the putter blocks until its
;; value is taken); a buffered (chan n) put blocks only when full; dropping/sliding

View file

@ -1,26 +1,24 @@
;; atoms (jolt-9ziu) — host-coupled mutable reference cells for the Chez host.
;; atoms — host-coupled mutable reference cells for the Chez host.
;;
;; atom/deref/swap!/reset! are host primitives (not the clojure.core overlay),
;; so the Chez runtime provides native shims, def-var!'d into clojure.core. They
;; so the runtime provides native shims, def-var!'d into clojure.core. They
;; lower to var-deref in prelude mode. The hierarchy machinery
;; (global-hierarchy = (atom (make-hierarchy))) calls `atom` at the prelude's
;; LOAD time, so without this shim the whole prelude fails to load.
;;
;; compare-and-set!/swap-vals!/reset-vals! are overlay fns over the native kernel
;; in the live system; provided here natively too so the Chez host is
;; self-sufficient for atoms without the full prelude (the overlay versions, when
;; the full prelude loads, override these but compose the same native kernel).
;; in the live system; provided here natively too so the host is self-sufficient
;; for atoms without the full prelude (the overlay versions, when the full prelude
;; loads, override these but compose the same native kernel).
;; watches is an alist of (key . watch-fn); validator is a jolt fn or jolt-nil.
;; The overlay's add-watch/set-validator! drive these via jolt.host/ref-put! on a
;; Janet table, which a Chez atom record is not — so the peripheral ops + the
;; notify/validate behaviour live natively here, and post-prelude.ss re-asserts
;; them over the overlay's def-var! (jolt-mn9o).
;; `lock` (jolt-byjr) is a per-atom mutex guarding the read-modify-write critical
;; sections, so swap!/reset!/compare-and-set! are atomic under real OS threads
;; (futures/go blocks share the heap on Chez). The user fn in swap! runs OUTSIDE
;; the lock (a CAS retry loop, like the JVM) so it never deadlocks on re-entrant
;; access and a watch/validator can deref the same atom.
;; The peripheral ops + the notify/validate behaviour live natively here, and
;; post-prelude.ss re-asserts them over the overlay's def-var!.
;; `lock` is a per-atom mutex guarding the read-modify-write critical sections,
;; so swap!/reset!/compare-and-set! are atomic under real OS threads
;; (futures/go blocks share the heap). The user fn in swap! runs OUTSIDE the lock
;; (a CAS retry loop, like the JVM) so it never deadlocks on re-entrant access and
;; a watch/validator can deref the same atom.
(define-record-type jolt-atom
(fields (mutable val) (mutable watches) (mutable validator) lock)
(nongenerative jolt-atom-v3))
@ -108,7 +106,7 @@
(jolt-atom-notify a old v)
(jolt-vector old v)))
;; --- watches / validators (jolt-mn9o) ---------------------------------------
;; --- watches / validators ---------------------------------------------------
;; add-watch interns (key . fn) (replacing any existing key, keeping order);
;; remove-watch drops it; both return the atom. set-validator! installs a
;; validator and validates the CURRENT value immediately (Clojure throws if it's
@ -139,7 +137,7 @@
(def-var! "clojure.core" "reset-vals!" jolt-reset-vals!)
(def-var! "clojure.core" "atom?" jolt-atom?)
;; peripheral ops: the overlay (20-coll) re-defs these over jolt.host/ref-put!,
;; which fails on a Chez atom record — post-prelude.ss re-asserts the natives.
;; which fails on an atom record — post-prelude.ss re-asserts the natives.
(def-var! "clojure.core" "add-watch" jolt-add-watch)
(def-var! "clojure.core" "remove-watch" jolt-remove-watch)
(def-var! "clojure.core" "set-validator!" jolt-set-validator!)

View file

@ -1,9 +1,9 @@
;; BigDecimal (jolt-i2jm). A jbigdec is {unscaled, scale} over Chez arbitrary-
;; precision exact integers; its value is unscaled * 10^-scale (1.5M = {15,1},
;; 1.00M = {100,2}, 3M = {3,0}). M-suffix literals read to a :bigdec form that the
;; back end lowers to jolt-bigdec-from-string; bigdec coerces a number/string.
;; Equality is by value (1.0M = 1.00M), str drops the M, pr keeps it, class is
;; java.math.BigDecimal. Arithmetic contagion is not modelled (jolt-i2jm scope).
;; BigDecimal. A jbigdec is {unscaled, scale} over Chez arbitrary-precision exact
;; integers; its value is unscaled * 10^-scale (1.5M = {15,1}, 1.00M = {100,2},
;; 3M = {3,0}). M-suffix literals read to a :bigdec form that the back end lowers
;; to jolt-bigdec-from-string; bigdec coerces a number/string. Equality is by
;; value (1.0M = 1.00M), str drops the M, pr keeps it, class is
;; java.math.BigDecimal. Arithmetic contagion is not modelled.
(define-record-type jbigdec (fields unscaled scale) (nongenerative chez-jbigdec-v1))

View file

@ -1,13 +1,12 @@
;; bootstrap.ss (jolt-9phg, Phase 3 inc9a) — the pure-Chez self-build.
;; bootstrap.ss — the pure-Chez self-build.
;;
;; This is the zero-Janet build step. Given a SEED (prelude, image) pair — the
;; bootstrap compiler, minted once via the inc8 fixpoint and checked in under
;; Given a SEED (prelude, image) pair — the bootstrap compiler, checked in under
;; host/chez/seed/ — it loads them, then rebuilds the clojure.core prelude AND the
;; compiler image from the .clj/.ss sources using the ON-CHEZ compiler (emit-image.ss),
;; writing fresh artifacts. No Janet is invoked: read -> analyze -> emit all run on
;; compiler image from the .clj/.ss sources using the on-Chez compiler
;; (emit-image.ss), writing fresh artifacts: read -> analyze -> emit all run on
;; Chez. The seed is a JOINT fixpoint, so a rebuild from an up-to-date seed
;; reproduces it byte-for-byte (`make selfhost` checks this); when
;; the sources change, run it twice to reconverge and re-mint the seed.
;; reproduces it byte-for-byte (`make selfhost` checks this); when the sources
;; change, run it twice to reconverge and re-mint the seed.
;;
;; Run from the repo root:
;; chez --script host/chez/bootstrap.ss SEED-PRELUDE SEED-IMAGE OUT-PRELUDE OUT-IMAGE
@ -23,7 +22,7 @@
(define bs-out-image (list-ref bs-args 3))
;; Load the runtime + the SEED compiler (prelude for macros, image for the
;; analyzer/emitter), exactly as the zero-Janet spine assembles a program.
;; analyzer/emitter), exactly as the spine assembles a program.
(load "host/chez/rt.ss")
(set-chez-ns! "clojure.core")
(load bs-seed-prelude)
@ -39,4 +38,4 @@
(put-string p (jolt-emit-prelude)) (close-port p))
(let ((p (open-output-file bs-out-image 'replace)))
(put-string p (jolt-emit-image)) (close-port p))
(display "bootstrap: rebuilt prelude + compiler image on Chez (no Janet)\n")
(display "bootstrap: rebuilt prelude + compiler image on Chez\n")

63
host/chez/build-smoke.sh Executable file
View file

@ -0,0 +1,63 @@
#!/bin/sh
# build smoke: `jolt build` compiles a multi-namespace app (macro + cross-ns +
# clojure.string) into a standalone binary, which then runs with no jolt source
# or Chez install on the path — args reach -main, output matches.
root="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
cd "$root"
# Preflight: a standalone build needs Chez's kernel dev files (libkernel.a +
# scheme.h) and a C compiler. A distro chezscheme package ships neither, so on
# such hosts (CI included) skip — like `certify` skips without Clojure. Pin the
# csv dir we validate so the build uses exactly it.
csv="$JOLT_CHEZ_CSV"
if [ -z "$csv" ]; then
chez_bin="$(command -v chez || command -v scheme || command -v petite || true)"
if [ -n "$chez_bin" ]; then
base="$(cd "$(dirname "$chez_bin")/.." 2>/dev/null && pwd)"
for d in "$base"/lib/csv*/*/; do
[ -f "${d}libkernel.a" ] && csv="${d%/}" && break
done
fi
fi
if ! command -v cc >/dev/null 2>&1 || [ -z "$csv" ] || [ ! -f "$csv/scheme.h" ] || [ ! -f "$csv/libkernel.a" ]; then
echo "build smoke: skipped (Chez kernel dev files or C compiler not available)"
exit 0
fi
export JOLT_CHEZ_CSV="$csv"
app="$root/test/chez/build-app"
out="$(mktemp -d)/app-bin"
trap 'rm -rf "$(dirname "$out")"' EXIT
echo "build smoke: compiling app.core -> $out"
if ! JOLT_PWD="$app" bin/joltc build -m app.core -o "$out" >/dev/null 2>&1; then
echo " FAIL: jolt build exited non-zero"
exit 1
fi
[ -x "$out" ] || { echo " FAIL: no executable produced"; exit 1; }
# Run from a neutral cwd with args; check the three output lines.
got="$(cd / && "$out" alpha bb ccc 2>&1)"
want='HELLO FROM A BUILT BINARY!
HELLO FROM A BUILT BINARY!
args: [alpha bb ccc]
sum: 10'
if [ "$got" != "$want" ]; then
echo " FAIL: binary output mismatch"
echo "--- want ---"; echo "$want"
echo "--- got ----"; echo "$got"
exit 1
fi
# Optimized mode (inference + flatten + scalar-replace) must produce the same
# result — a sanity check that the passes don't miscompile this app.
if ! JOLT_PWD="$app" bin/joltc build -m app.core -o "$out" --opt >/dev/null 2>&1; then
echo " FAIL: jolt build --opt exited non-zero"; exit 1
fi
got_opt="$(cd / && "$out" alpha bb ccc 2>&1)"
if [ "$got_opt" != "$want" ]; then
echo " FAIL: --opt binary output mismatch"
echo "--- got ----"; echo "$got_opt"
exit 1
fi
echo "build smoke: passed (release + optimized)"

258
host/chez/build.ss Normal file
View file

@ -0,0 +1,258 @@
;; build.ss — `jolt build`: AOT-compile an app into a standalone executable.
;;
;; Loaded on demand by cli.ss when the command is `build`. Defines the host
;; primitive jolt.host/build-binary, which jolt.main's build command calls after
;; resolving the project's deps + source roots.
;;
;; The pipeline (Phase 4 stage 2):
;; 1. load the entry namespace — registers its macros/vars and follows requires,
;; recording the app namespaces in dependency order (loader's ns-loaded-hook).
;; 2. re-emit each app namespace to Scheme (the emit-image cross-compile path),
;; now that its macros are registered.
;; 3. textually inline the cli.ss runtime load sequence into one flat source,
;; append the app emission + a launcher that calls the entry's -main.
;; 4. compile-file -> make-boot-file -> embed the boot as C bytes -> cc-link
;; against libkernel.a into a single self-contained binary.
;;
;; emit-image.ss supplies the cross-compiler (ei-* helpers); it's loaded here so a
;; normal run never pays for it.
(load "host/chez/emit-image.ss")
;; --- shell helpers ----------------------------------------------------------
;; Run a command, return its stdout as one trimmed string ("" on no output).
(define (bld-sh-capture cmd)
(let* ((p (process cmd)) (in (car p)))
(let loop ((acc '()))
(let ((l (get-line in)))
(if (eof-object? l)
(begin (close-port in)
(let ((s (apply string-append (reverse acc))))
;; trim a trailing newline-equivalent (we joined without them)
s))
(loop (cons l acc)))))))
(define (bld-system cmd)
(let ((rc (system cmd)))
(unless (zero? rc)
(error 'jolt-build (string-append "command failed (" (number->string rc) "): " cmd)))))
(define (bld-contains? s sub)
(let ((ns (string-length s)) (nsub (string-length sub)))
(let loop ((i 0))
(cond ((> (+ i nsub) ns) #f)
((string=? (substring s i (+ i nsub)) sub) #t)
(else (loop (+ i 1)))))))
;; --- toolchain discovery ----------------------------------------------------
(define bld-machine (symbol->string (machine-type)))
(define bld-osx? (bld-contains? bld-machine "osx"))
;; The Chez executable, for the isolated compile pass (see build-binary step 4).
(define bld-chez
(let ((p (bld-sh-capture "command -v chez || command -v scheme || command -v petite")))
(if (> (string-length p) 0) p "chez")))
;; Chez version off (scheme-version) "Chez Scheme Version X.Y.Z" — last token.
(define bld-version
(let* ((s (scheme-version)) (n (string-length s)))
(let loop ((i n))
(if (or (= i 0) (char=? (string-ref s (- i 1)) #\space))
(substring s i n)
(loop (- i 1))))))
;; The csv<ver>/<machine> dir holding scheme.h, libkernel.a, *.boot. Derived from
;; the chez executable's location; JOLT_CHEZ_CSV overrides.
(define bld-csv-dir
(let ((env (getenv "JOLT_CHEZ_CSV")))
(or (and env (> (string-length env) 0) env)
(let* ((bindir (bld-sh-capture "dirname \"$(command -v chez || command -v scheme || command -v petite)\""))
(cand (string-append bindir "/../lib/csv" bld-version "/" bld-machine)))
cand))))
(define (bld-check-toolchain)
(for-each
(lambda (f)
(let ((p (string-append bld-csv-dir "/" f)))
(unless (file-exists? p)
(error 'jolt-build (string-append "Chez build file missing: " p
"\nSet JOLT_CHEZ_CSV to the csv<ver>/<machine> dir.")))))
'("scheme.h" "libkernel.a" "petite.boot" "scheme.boot")))
;; Link flags. macOS Homebrew layout for the kernel's lz4/zlib/ncurses deps.
(define (bld-link-libs)
(if bld-osx?
(let ((lz4 (bld-sh-capture "brew --prefix lz4 2>/dev/null")))
(string-append
(if (> (string-length lz4) 0) (string-append "-L" lz4 "/lib ") "")
"-llz4 -lz -lncurses -framework Foundation -liconv -lm"))
;; Best-effort Linux/other; untested here.
"-llz4 -lz -lncurses -ldl -lpthread -lm"))
;; --- runtime manifest (mirrors host/chez/cli.ss's load order) ---------------
(define bld-runtime-manifest
(list
"(load \"host/chez/rt.ss\")"
"(set-chez-ns! \"clojure.core\")"
"(load \"host/chez/seed/prelude.ss\")"
"(load \"host/chez/post-prelude.ss\")"
"(set-chez-ns! \"user\")"
"(load \"host/chez/host-contract.ss\")"
"(load \"host/chez/seed/image.ss\")"
"(load \"host/chez/compile-eval.ss\")"
"(load \"host/chez/png.ss\")"
"(load \"host/chez/loader.ss\")"
"(load \"host/chez/ffi.ss\")"
"(set-source-roots! (list \"jolt-core\" \"stdlib\"))"))
;; A single-line top-level `(load "PATH")` -> PATH, else #f.
(define (bld-load-path line)
(let ((s (let trim ((i 0))
(if (and (< i (string-length line))
(memv (string-ref line i) '(#\space #\tab)))
(trim (+ i 1))
(substring line i (string-length line))))))
(and (>= (string-length s) 7)
(string=? (substring s 0 6) "(load ")
(let* ((q1 (let scan ((i 6)) (if (char=? (string-ref s i) #\") i (scan (+ i 1)))))
(q2 (let scan ((i (+ q1 1))) (if (char=? (string-ref s i) #\") i (scan (+ i 1))))))
(substring s (+ q1 1) q2)))))
(define (bld-file-lines path)
(call-with-input-file path
(lambda (p)
(let loop ((acc '()))
(let ((l (get-line p)))
(if (eof-object? l) (reverse acc) (loop (cons l acc))))))))
;; Emit one line to OUT, recursively inlining a `(load ...)` of a repo file.
(define (bld-inline-line line out depth)
(when (> depth 50) (error 'jolt-build "load nesting too deep"))
(let ((p (bld-load-path line)))
(if p
(for-each (lambda (l) (bld-inline-line l out (+ depth 1))) (bld-file-lines p))
(begin (put-string out line) (put-string out "\n")))))
(define (bld-emit-runtime out)
(for-each (lambda (l) (bld-inline-line l out 0)) bld-runtime-manifest))
;; --- app emission -----------------------------------------------------------
;; Re-emit one app namespace to a list of Scheme strings. Like emit-image's
;; ei-emit-ns but WITHOUT the silent (guard ...) wrapper — a form that fails to
;; emit must fail the build, not vanish.
(define (bld-emit-ns ns-name src)
(let loop ((forms (ei-read-all src)) (acc '()))
(if (null? forms)
(reverse acc)
(let ((f (car forms)))
(ce-scan-requires! f ns-name)
(cond
((ei-ns-form? f) (loop (cdr forms) acc))
((ce-macro-form? f)
(let-values (((nm fn-form) (ce-defmacro->fn f)))
(let ((scm (let ((ctx (make-analyze-ctx ns-name)))
(jolt-ce-emit (jolt-ce-analyze ctx fn-form)))))
(loop (cdr forms)
(cons (string-append
"(def-var! " (ei-str-lit ns-name) " " (ei-str-lit nm) "\n "
scm ")\n(mark-macro! "
(ei-str-lit ns-name) " " (ei-str-lit nm) ")")
acc)))))
(else
(let* ((ctx (make-analyze-ctx ns-name))
(scm (jolt-ce-emit (jolt-ce-analyze ctx f))))
(loop (cdr forms) (cons scm acc)))))))))
;; --- the build --------------------------------------------------------------
;; entry-ns: the app's main namespace (a string). out-path: the binary to write.
;; mode: "dev" | "release" | "optimized" (recorded; optimization passes wired in a
;; later stage). Deps + source roots are already applied by the caller.
(define (build-binary entry-ns out-path mode)
(bld-check-toolchain)
;; 1. record app namespaces in dependency order as they finish loading.
(let ((app-order '()))
(set-ns-loaded-hook!
(lambda (name file) (set! app-order (cons (cons name file) app-order))))
(load-namespace entry-ns)
(set-ns-loaded-hook! (lambda (name file) #f))
(let ((ordered (reverse app-order))) ; deps first, entry last
(when (null? ordered)
(error 'jolt-build (string-append "no source namespace loaded for " entry-ns
" — is it on the source roots?")))
;; 2. emit each app namespace. `optimized` turns on the inference + flatten
;; + scalar-replace passes (closed world). release/dev stay on proven
;; var-deref codegen until those passes are validated on Chez — they were
;; dormant before `jolt build` (inline-enabled? was hardwired off).
(set-optimize! (string=? mode "optimized"))
(let* ((app-strs (apply append
(map (lambda (nf) (bld-emit-ns (car nf) (read-file-string (cdr nf))))
ordered)))
(_ (set-optimize! #f))
(builddir (string-append out-path ".build"))
(flat-ss (string-append builddir "/flat.ss"))
(flat-so (string-append builddir "/flat.so"))
(boot (string-append builddir "/jolt.boot"))
(boot-h (string-append builddir "/boot_data.h"))
(main-c (string-append builddir "/main.c")))
(bld-system (string-append "mkdir -p '" builddir "'"))
;; 3. flat source = runtime + app + launcher.
(let ((out (open-output-file flat-ss 'replace)))
(bld-emit-runtime out)
(put-string out "\n;; === app ===\n")
(for-each (lambda (s) (put-string out s) (put-string out "\n")) app-strs)
;; The launcher runs as Chez's scheme-start (so argv reaches -main —
;; top-level boot forms run during heap build, before args are set), and
;; suppresses the interactive greeting.
(put-string out "\n;; === launcher ===\n")
(put-string out (string-append
"(suppress-greeting #t)\n"
"(scheme-start\n"
" (lambda args\n"
" (let ((mainv (var-deref " (ei-str-lit entry-ns) " \"-main\")))\n"
" (apply jolt-invoke mainv args))\n"
" (exit 0)))\n"))
(close-port out))
;; 4. compile -> boot -> embed -> link.
;; compile-file/make-boot-file run in a FRESH Chez, not this process: the
;; loaded runtime shadows `error` (regex.ss, for irregex), which would
;; otherwise bake a broken `error` reference into the boot.
(display (string-append "jolt build: compiling " entry-ns " (" mode " mode)\n"))
(let ((cs (string-append builddir "/compile.ss")))
(let ((p (open-output-file cs 'replace)))
(put-string p
(string-append
"(import (chezscheme))\n"
"(compile-file " (ei-str-lit flat-ss) " " (ei-str-lit flat-so) ")\n"
"(make-boot-file " (ei-str-lit boot) " '()\n "
(ei-str-lit (string-append bld-csv-dir "/petite.boot")) "\n "
(ei-str-lit (string-append bld-csv-dir "/scheme.boot")) "\n "
(ei-str-lit flat-so) ")\n"))
(close-port p))
(bld-system (string-append bld-chez " --script '" cs "'")))
(bld-system (string-append "xxd -i '" boot "' > '" boot-h "'"))
;; The xxd symbol is derived from the path; normalize to jolt_boot.
(bld-system (string-append
"sed -i.bak -E 's/unsigned char [A-Za-z0-9_]+\\[\\]/unsigned char jolt_boot[]/; "
"s/unsigned int [A-Za-z0-9_]+_len/unsigned int jolt_boot_len/' '" boot-h "'"))
(let ((mc (open-output-file main-c 'replace)))
(put-string mc
(string-append
"#include \"scheme.h\"\n#include \"boot_data.h\"\n"
"int main(int argc, char *argv[]) {\n"
" Sscheme_init(0);\n"
" Sregister_boot_file_bytes(\"jolt\", jolt_boot, jolt_boot_len);\n"
" Sbuild_heap(0, 0);\n"
" int status = Sscheme_start(argc, (const char **)argv);\n"
" Sscheme_deinit();\n return status;\n}\n"))
(close-port mc))
(bld-system (string-append
"cc -O2 -I'" bld-csv-dir "' '" main-c "' '" bld-csv-dir "/libkernel.a' "
"-o '" out-path "' " (bld-link-libs)))
(display (string-append "jolt build: wrote " out-path "\n"))))))
(def-var! "jolt.host" "build-binary"
(lambda (entry out mode)
(build-binary (jolt-str-render-one entry)
(jolt-str-render-one out)
(jolt-str-render-one mode))
jolt-nil))

View file

@ -1,8 +1,8 @@
;; cli.ss (jolt-9phg / jolt-90sp) — the pure-Chez jolt runtime. NO Janet.
;; cli.ss — the jolt runtime.
;;
;; Loads the checked-in seed (host/chez/seed/{prelude,image}.ss — the bootstrap
;; compiler) and the zero-Janet spine, then either evaluates a -e expression or
;; dispatches a CLI command (run/-M/repl/path/task) through jolt.main. The loader
;; compiler) and the spine, then either evaluates a -e expression or dispatches a
;; CLI command (run/-M/repl/path/task) through jolt.main. The loader
;; (loader.ss) turns `require` into real file loading off the source roots, so a
;; multi-file project with deps.edn dependencies runs end to end.
;;
@ -23,13 +23,13 @@
(load "host/chez/loader.ss")
;; jolt.ffi host primitives (memory / library loading) load AFTER the loader's
;; baked-ns snapshot, so a library's (require '[jolt.ffi]) still loads jolt.ffi's
;; Clojure side (the foreign-fn / defcfn macros, src/jolt/jolt/ffi.clj).
;; Clojure side (the foreign-fn / defcfn macros, stdlib/jolt/ffi.clj).
(load "host/chez/ffi.ss") ; jolt.ffi (FFI: a library binds native code)
;; jolt.main + jolt.deps live under jolt-core; keep them (and src/jolt) on the
;; jolt.main + jolt.deps live under jolt-core; keep them (and stdlib) on the
;; roots so the CLI's own namespaces — and any jolt.* an app pulls in — resolve.
;; A project's resolved deps roots are prepended to these by jolt.main.
(set-source-roots! (list "jolt-core" "src/jolt"))
(set-source-roots! (list "jolt-core" "stdlib"))
;; Render an uncaught jolt throw (any value, not just a Chez condition) to stderr
;; and exit non-zero, instead of Chez's opaque "non-condition value" dump. An
@ -66,6 +66,11 @@
(display result) (newline))))
;; otherwise dispatch the argv through jolt.main/-main
(else
;; `build` AOT-compiles an app to a standalone binary — load the build
;; driver (the cross-compiler emitter) on demand so a normal run never pays
;; for it. It defines jolt.host/build-binary, which jolt.main's build cmd calls.
(when (and (pair? cli-args) (string=? (car cli-args) "build"))
(load "host/chez/build.ss"))
(load-namespace "jolt.main")
(let ((mainv (var-deref "jolt.main" "-main")))
(apply jolt-invoke mainv cli-args)))))

View file

@ -1,4 +1,4 @@
;; Phase 1 (jolt-cf1q.2, inc 3a) — persistent collections on the Chez RT.
;; persistent collections on the Chez RT.
;;
;; The vector / map / set the emitted programs construct from literals and
;; operate on via the lowered leaf ops (conj/get/nth/count/assoc/...). Loaded by
@ -6,11 +6,8 @@
;; jolt-coll? / jolt-coll=? / jolt-coll-hash hooks defined here (forward refs,
;; resolved at run time — nothing is CALLED during load).
;;
;; Phase note: the persistent vector is a copy-on-write Scheme vector and the
;; map/set are a bitmap HAMT (the structure 0c measured self-hostable). They live
;; in Scheme for the Phase-1 bootstrap; the 0c decision is to SELF-HOST them in
;; Clojure once core is up on Chez (Phase 3 shim shrink). Correctness, not perf,
;; is the Phase-1 gate.
;; The persistent vector is a copy-on-write Scheme vector and the map/set are a
;; bitmap HAMT. They live in Scheme; correctness, not perf, is the gate.
;; ============================================================================
;; small immutable-vector helpers (manual; avoid stdlib arg-order ambiguity)
@ -40,7 +37,7 @@
;; A pvec carries an `ent` flag: #t marks a MAP ENTRY (the [k v] pair seq'd out
;; of a map). A map entry equals its [k v] vector and walks like one (nth/count/
;; seq/=/hash/print all read only `v`), but is NOT `vector?` and IS `map-entry?`
;; — matching Clojure's MapEntry (jolt-agw6). The flag defaults #f, so every
;; — matching Clojure's MapEntry. The flag defaults #f, so every
;; existing `(make-pvec v)` builds a plain vector; modifying an entry (conj/assoc)
;; likewise yields a plain vector.
(define-record-type pvec

View file

@ -1,11 +1,11 @@
;; compile-eval.ss (jolt-hs9n, Phase 3 inc6) — the zero-Janet compile spine.
;; compile-eval.ss — the compile spine.
;;
;; Ties together the cross-compiled compiler image (jolt.ir + jolt.analyzer +
;; jolt.backend-scheme, loaded as def-var! forms) and the host contract
;; (host-contract.ss) into a runtime entry: a Clojure source string is read by the
;; Chez data reader, analyzed by the ON-CHEZ analyzer to IR, emitted to Scheme by
;; the ON-CHEZ emitter, and eval'd — no Janet in the loop. This is the spine the
;; stage2==stage3 bootstrap fixpoint (later increments) closes over.
;; Chez data reader, analyzed by the analyzer to IR, emitted to Scheme by the
;; emitter, and eval'd. This is the spine the stage2==stage3 bootstrap fixpoint
;; closes over.
;;
;; Loaded after host-contract.ss + the compiler image.
@ -13,11 +13,11 @@
(define jolt-ce-emit (var-deref "jolt.backend-scheme" "emit"))
(define jolt-ce-read (var-deref "clojure.core" "read-string"))
;; The zero-Janet spine ALWAYS runs with the full clojure.core prelude loaded, so a
;; clojure.* ref must lower to var-deref (resolved from the prelude), not trip the
;; emitter's "unsupported stdlib fn (no core on Chez yet)" out-of-subset guard —
;; that guard is only for the bare -e subset with no prelude. Turn prelude mode on
;; once, here, so every analyze->emit on this spine sees the full core (jolt-qjr0).
;; The spine ALWAYS runs with the full clojure.core prelude loaded, so a clojure.*
;; ref must lower to var-deref (resolved from the prelude), not trip the emitter's
;; "unsupported stdlib fn (no core on Chez yet)" out-of-subset guard — that guard
;; is only for the bare -e subset with no prelude. Turn prelude mode on once, here,
;; so every analyze->emit on this spine sees the full core.
((var-deref "jolt.backend-scheme" "set-prelude-mode!") #t)
;; (quote X) -> X, else x — unwraps a quoted require spec.
@ -31,7 +31,7 @@
;; Pre-register any (require ...)/(use ...) :as aliases under `ns` BEFORE analysis,
;; so a qualified s/foo resolves while compiling (analysis precedes the runtime
;; require). Walks the whole form (a require may be nested in a do/let). jolt-qjr0.
;; require). Walks the whole form (a require may be nested in a do/let).
(define (ce-clause-require? cl) ; (:require ...) / (:use ...) ns clause
(and (pair? cl) (keyword? (car cl))
(let ((kn (keyword-t-name (car cl)))) (or (string=? kn "require") (string=? kn "use")))))
@ -66,7 +66,7 @@
(define (jolt-analyze-emit src ns)
(jolt-analyze-emit-form (jolt-ce-read src) ns))
;; --- runtime defmacro (jolt-r8ku) -------------------------------------------
;; --- runtime defmacro -------------------------------------------------------
;; Shared with emit-image.ss (loaded after this). A defmacro lowers to a def of
;; its expander fn + a macro flag, exactly as the prelude emits build-time macros.
@ -82,7 +82,7 @@
;; Strips a leading docstring (native string) + attr-map (a non-symbol pmap), then
;; re-heads the rest with `fn` so a destructured macro arglist desugars. Emits the
;; BARE fn (the caller wraps it in def-var! + mark-macro!), never a (def NAME ...) —
;; interning NAME would make require skip the real macro (jolt-r9lm).
;; interning NAME would make require skip the real macro.
(define (ce-defmacro->fn f)
(let* ((items (seq->list f))
(name-sym (cadr items))
@ -131,7 +131,6 @@
;; clojure.core/load-string: read every form from the source string and compile+
;; eval each in the current ns, returning the last value (nil for blank input).
;; jolt-r8ku.
(define (jolt-load-string s)
(let loop ((src s) (result jolt-nil))
(let ((pn (jolt-parse-next src)))

View file

@ -1,13 +1,12 @@
;; concurrency.ss (jolt-byjr) — real OS-thread futures + promises for the Chez host.
;; concurrency.ss — real OS-thread futures + promises for the Chez host.
;;
;; SHARED-HEAP semantics (JVM Clojure), NOT Janet's isolated-heap snapshot: a
;; future body runs on a native thread (fork-thread) over the SAME heap, so a
;; captured atom is shared and the body's mutations are visible to the parent —
;; matching `clojure.core` on the JVM. deref blocks on a mutex+condition latch.
;; SHARED-HEAP semantics, like JVM Clojure: a future body runs on a native thread
;; (fork-thread) over the SAME heap, so a captured atom is shared and the body's
;; mutations are visible to the parent. deref blocks on a mutex+condition latch.
;;
;; future / future-call / future-cancel / future? / future-done? / future-cancelled?
;; promise / deliver, and the deref extension for both, are bound here (some
;; re-asserted in post-prelude.ss over the overlay's Janet-shaped versions).
;; re-asserted in post-prelude.ss over the overlay's versions).
;;
;; pmap / pcalls / pvalues live in the clojure.core overlay (40-lazy) expressed
;; over `future`, so they light up for free once future-call exists.
@ -105,8 +104,8 @@
(and (jolt-future? x) (jolt-future-cancelled? x)))
;; --- promises ---------------------------------------------------------------
;; A blocking promise (JVM), not Janet's non-blocking atom shim: deref parks until
;; deliver, then caches the value. deliver wins once; later delivers return nil.
;; A blocking promise (like the JVM): deref parks until deliver, then caches the
;; value. deliver wins once; later delivers return nil.
(define-record-type jolt-promise
(fields (mutable delivered?) (mutable value) mu cv)
(nongenerative jolt-promise-v1))
@ -144,8 +143,8 @@
(if got (jolt-promise-value p) timeout-val)))
;; --- agents (async, per-agent serialized dispatch) --------------------------
;; JVM semantics, not Janet's synchronous shim: send/send-off enqueue an action
;; and a single worker thread applies them to the state IN ORDER; deref reads the
;; JVM semantics: send/send-off enqueue an action and a single worker thread
;; applies them to the state IN ORDER; deref reads the
;; (possibly not-yet-updated) state without blocking; await blocks until the queue
;; drains. An action error is captured (agent-error) and stops the queue.
(define-record-type jolt-agent
@ -246,8 +245,8 @@
((jolt-delay? x) (jolt-delay-force x))
(else (apply %pre-conc-deref x opts)))))
;; realized? for a Chez future/promise/delay (the overlay reads Janet map keys).
;; Wrapped over the overlay version in post-prelude.ss.
;; realized? for a future/promise/delay. Wrapped over the overlay version in
;; post-prelude.ss.
(define (jolt-conc-realized? x)
(cond ((jolt-future? x) (jolt-future-done? x))
((jolt-promise? x) (jolt-promise-delivered? x))
@ -273,7 +272,7 @@
(def-var! "clojure.core" "delay?" jolt-delay?)
(def-var! "clojure.core" "deref" jolt-deref)
;; --- cooperative thread interrupt (jolt-amzy) -------------------------------
;; --- cooperative thread interrupt -------------------------------------------
;; Chez has no force-kill, but its engine timer (set-timer + timer-interrupt-
;; handler, thread-local) is polled at procedure-call / loop back-edges — so a
;; running computation, even a tight Scheme loop, can be aborted from another

View file

@ -143,8 +143,8 @@
(def-var! "clojure.core" "gensym" jolt-gensym)
(def-var! "clojure.core" "int" jolt-int)
;; char: coerce a code point (jolt's all-flonum number) to a Chez char; pass a
;; char through. Inverse of int on chars. (Missing on Chez before jolt-hs9n — the
;; cross-compiled emitter's chez-str-lit needs it for printable-ASCII escaping.)
;; char through. Inverse of int on chars. The cross-compiled emitter's
;; chez-str-lit needs it for printable-ASCII escaping.
(define (jolt-char x) (if (char? x) x (integer->char (exact (round x)))))
(def-var! "clojure.core" "char" jolt-char)
;; long: same truncation as int in jolt's all-flonum model (seed core-long =

View file

@ -1,5 +1,5 @@
;; dot-forms.ss — generic dispatch for the `.` special-form / `.-field` desugar
;; (jolt-kuic). The analyzer lowers (. target member arg*) and (.-field target)
;; dot-forms.ss — generic dispatch for the `.` special-form / `.-field` desugar.
;; The analyzer lowers (. target member arg*) and (.-field target)
;; to a :host-call; the Chez emit routes a non-shimmed :host-call through
;; record-method-dispatch. This file extends that dispatcher with the collection
;; arms the interpreter's dispatch-member covers but the record/string base does

View file

@ -1,5 +1,5 @@
;; dynamic var binding (jolt-2o7x, Phase 2) — binding / with-bindings* / var-set /
;; thread-bound? / with-local-vars / with-redefs / bound-fn* / get-thread-bindings.
;; dynamic var binding — binding / with-bindings* / var-set / thread-bound? /
;; with-local-vars / with-redefs / bound-fn* / get-thread-bindings.
;;
;; A per-thread dynamic-binding stack: a list of frames, innermost (most recently
;; pushed) at the HEAD. Each frame is an alist of (var-cell . value) MUTABLE pairs
@ -15,8 +15,8 @@
;; the stack before falling back to the cell root. Loaded LAST (after vars.ss and
;; ns.ss) so it chains the fully-extended jolt-var-get and overrides rt.ss var-deref.
;; THREAD-LOCAL (jolt-byjr): a Chez thread parameter, so each OS thread (a future
;; / go block) has its own binding stack. Chez initializes a new thread's parameter
;; THREAD-LOCAL: a Chez thread parameter, so each OS thread (a future / go block)
;; has its own binding stack. Chez initializes a new thread's parameter
;; to the spawning thread's value at fork time, giving Clojure binding conveyance
;; for free (the future shim also installs an explicit snapshot, belt-and-suspenders).
(define dyn-binding-stack (make-thread-parameter '()))
@ -104,8 +104,8 @@
;; jolt-var-get's unbound-error path) so undefined-var reads keep prior behaviour.
;; The *ns* var cell — its reads are thread-local: with no thread-binding they
;; derive from chez-current-ns (a thread-parameter), so *ns* tracks in-ns per
;; thread and a (binding [*ns* ..]) drives resolution (jolt-6rld). Captured now
;; that *ns* is defined (ns.ss loaded earlier); chez-current-ns consults it too.
;; thread and a (binding [*ns* ..]) drives resolution. Captured now that *ns* is
;; defined (ns.ss loaded earlier); chez-current-ns consults it too.
(set! star-ns-cell (jolt-var "clojure.core" "*ns*"))
(define %dyn-rt-var-deref var-deref)

View file

@ -1,4 +1,4 @@
;; emit-image.ss (jolt-cf1q.4 inc8) — the on-Chez compiler-image emitter.
;; emit-image.ss — the on-Chez compiler-image emitter.
;;
;; This is the stage2/stage3 half of the self-hosting fixpoint. The
;; analyze->emit runs ON CHEZ (jolt-ce-analyze / jolt-ce-emit, loaded from a
@ -34,7 +34,7 @@
;; Each form is analyzed with a fresh ctx — resolution is via the runtime var-table
;; + alias tables, not ctx-accumulated state, so this matches the spine's per-form
;; analyze. A defmacro emits its expander fn as (def-var! ns name <fn>) +
;; (mark-macro! ns name) so the on-Chez analyzer can expand it (jolt-r9lm).
;; (mark-macro! ns name) so the on-Chez analyzer can expand it.
(define (ei-emit-ns ns-name src)
(let loop ((forms (ei-read-all src)) (acc '()))
(if (null? forms)
@ -82,12 +82,12 @@
(append
(map (lambda (tf) (cons "clojure.core" (string-append "jolt-core/clojure/core/" tf ".clj")))
'("00-syntax" "00-kernel" "10-seq" "20-coll" "25-sorted" "30-macros" "40-lazy" "50-io"))
(list (cons "clojure.string" "src/jolt/clojure/string.clj")
(cons "clojure.walk" "src/jolt/clojure/walk.clj")
(cons "clojure.template" "src/jolt/clojure/template.clj")
(cons "clojure.edn" "src/jolt/clojure/edn.clj")
(cons "clojure.set" "src/jolt/clojure/set.clj")
(cons "clojure.pprint" "src/jolt/clojure/pprint.clj"))))
(list (cons "clojure.string" "stdlib/clojure/string.clj")
(cons "clojure.walk" "stdlib/clojure/walk.clj")
(cons "clojure.template" "stdlib/clojure/template.clj")
(cons "clojure.edn" "stdlib/clojure/edn.clj")
(cons "clojure.set" "stdlib/clojure/set.clj")
(cons "clojure.pprint" "stdlib/clojure/pprint.clj"))))
;; Join a list of form strings with "\n", no trailing newline.
(define (ei-join forms)

View file

@ -1,4 +1,4 @@
;; host class tokens (jolt-13zk) — a bare class name (String, Keyword, File...)
;; host class tokens — a bare class name (String, Keyword, File...)
;; evaluates to its JVM canonical-name STRING, the same value (class instance)
;; returns, so (= String (class "x")) holds and a (defmethod m String ...) keys
;; against a (class …) dispatch (ring.util.request does this).
@ -81,8 +81,8 @@
(lambda (pair) (def-var! "clojure.core" (car pair) (cdr pair)))
class-token-alist)
;; resolve a ^Type hint symbol-name to its canonical class name at def time
;; (jolt-a1ir): "String" -> "java.lang.String", matching the JVM compiler. An
;; resolve a ^Type hint symbol-name to its canonical class name at def time:
;; "String" -> "java.lang.String", matching the JVM compiler. An
;; already-canonical name maps to itself; an unknown name yields #f (left as-is).
(define class-hint-table (make-hashtable string-hash string=?))
(for-each (lambda (p) (hashtable-set! class-hint-table (car p) (cdr p))) class-token-alist)

View file

@ -1,4 +1,4 @@
;; host-contract.ss (jolt-hs9n, Phase 3 inc6) — the jolt.host contract on Chez.
;; host-contract.ss — the jolt.host contract on Chez.
;;
;; The portable seam between jolt-core (analyzer/IR/emitter, cross-compiled to
;; Scheme) and the host. Every
@ -6,7 +6,7 @@
;; jolt.analyzer / jolt.backend-scheme — whose unqualified form-*/resolve-global/
;; ... refs lower to (var-deref "jolt.host" ...) — resolve here at runtime.
;;
;; This is what puts analyze->IR->emit ON CHEZ (the zero-Janet spine). It runs
;; This is what puts analyze->IR->emit ON CHEZ. It runs
;; over the Chez data reader's forms (reader.ss): symbols are symbol-t, lists are
;; cseq (list?), () is empty-list-t, vectors/maps are pvec/pmap, sets and #tag/
;; regex/inst/uuid are pmaps tagged :jolt/type, chars are NATIVE Chez chars.
@ -41,13 +41,13 @@
(define (hc-sym? x) (symbol-t? x))
;; ANY non-empty seq is a list form for analysis (a macro/eval form built via
;; concat/map/cons is a lazy cseq with list?=#f, but evaluating it still means
;; calling its head) — not just reader-built lists (jolt-cf1q.7).
;; calling its head) — not just reader-built lists.
(define (hc-list? x) (or (empty-list-t? x) (cseq? x)))
(define (hc-vec? x) (pvec? x))
(define (hc-map? x) (and (pmap? x) (jolt-nil? (jolt-get x hc-kw-jolt-type))))
;; A set form is the reader's tagged map {:jolt/type :jolt/set :value <pvec>} OR a
;; real pset value — a macro template's #{...} expansion (syntax-quote.ss jolt-sqset)
;; produces a pset, which the analyzer must still read as a set literal (jolt-r9lm).
;; produces a pset, which the analyzer must still read as a set literal.
(define (hc-set? x)
(or (pset? x)
(and (pmap? x) (eq? (jolt-get x hc-kw-jolt-type) hc-kw-jolt-set))))
@ -67,7 +67,7 @@
(define (hc-bigdec-source x) (jolt-get x hc-kw-form))
;; A live namespace value spliced into a form (e.g. `(str ~*ns*) in a macro):
;; the analyzer can't carry an opaque runtime value, so recognize a jns and
;; reconstruct it by name at the call site (jolt-8sha).
;; reconstruct it by name at the call site.
(define (hc-ns-value? x) (jns? x))
(define (hc-ns-value-name x) (jns-name x))
@ -98,7 +98,7 @@
(let ((kv (hashtable-ref rdr-map-order x #f)))
(if kv
;; reader-built map literal: emit pairs in SOURCE order (kv = k1 v1 k2 v2 …)
;; so the analyzer evaluates the values left-to-right (jolt-qjr0).
;; so the analyzer evaluates the values left-to-right.
(let loop ((kv kv) (acc '()))
(if (null? kv) (apply jolt-vector (reverse acc))
(loop (cddr kv) (cons (jolt-vector (car kv) (cadr kv)) acc))))
@ -111,14 +111,14 @@
(define (hc-inst-source x) (jolt-get x hc-kw-form))
(define (hc-uuid-source x) (jolt-get x hc-kw-form))
;; The Chez reader does not record source offsets yet (jolt-q2kg).
;; The Chez reader does not record source offsets yet.
(define (hc-form-position x) jolt-nil)
;; --- special forms ----------------------------------------------------------
;; Mirrors host_iface special-names + interop-head? — forms the analyzer marks
;; uncompilable (the handled specials are dispatched in analyze-list BEFORE this).
;; `eval` is NOT here: it is a clojure.core FUNCTION on the spine (compile-eval.ss
;; def-var!s it), so it must resolve as an ordinary var, not punt (jolt-r8ku).
;; def-var!s it), so it must resolve as an ordinary var, not punt.
;; `defmacro` stays special — the spine intercepts it before analysis.
(define hc-special-names
'("quote" "syntax-quote" "unquote" "unquote-splicing" "do" "if" "def"
@ -155,7 +155,7 @@
(and ref (var-cell-lookup ref nm)))
(var-cell-lookup "clojure.core" nm)))))
;; Runtime macros (jolt-r9lm, inc6b): a defmacro is emitted into the prelude as a
;; Runtime macros: a defmacro is emitted into the prelude as a
;; def-var! of its cross-compiled expander fn plus (mark-macro! ns name), so the
;; var cell is flagged a macro (rt.ss var-macro-table). form-macro? checks the
;; flag; form-expand-1 applies the expander to the unevaluated arg forms (the rest
@ -173,7 +173,7 @@
;; {:kind :var :ns NS :name NAME} — a defined var (compile ns / clojure.core)
;; {:kind :unresolved :name NAME} — not found (late-bind -> var-ref @ compile ns;
;; a qualified one -> host-static in the analyzer)
;; No :host branch: there is no Janet-style native-op env on Chez — the hot
;; No :host branch: there is no separate native-op env — the hot
;; clojure.core primitives (+,-,map,...) are declared in clojure.core below so
;; they classify as :var and the emitter's native-op path lowers them.
(define (hc-resolve-global ctx sym)
@ -187,7 +187,7 @@
(define (hc-intern! ctx ns-name nm) (declare-var! ns-name nm) jolt-nil)
;; --- syntax-quote lowering (jolt-qjr0, inc7) ---------------------------------
;; --- syntax-quote lowering ---------------------------------------------------
;; Lowers a `form
;; to CONSTRUCTION CODE — Chez reader forms calling __sqcat/__sqvec/__sqmap/
;; __sqset/__sq1 + quote — that the analyzer re-analyzes, so a backtick compiles
@ -236,7 +236,7 @@
;; to the target namespace — Clojure resolves the alias part of a qualified
;; symbol in syntax-quote, so a macro's `impl/foo` expands to its real
;; (clojure.tools.logging.impl/foo) name and stays unambiguous even when
;; another loaded ns shares the alias's short name (jolt-qjr0). Otherwise
;; another loaded ns shares the alias's short name. Otherwise
;; leave it as written (a real ns or an interop class token).
(let ((target (chez-resolve-alias (chez-actx-cns ctx) sns)))
(if target (jolt-symbol target nm) form)))))
@ -280,7 +280,12 @@
(define (hc-record-type? ctx name) #f)
(define (hc-record-ctor-key ctx name) jolt-nil)
(define (hc-record-shapes ctx) (jolt-hash-map))
(define (hc-inline-enabled? ctx) #f)
;; Optimization gate. Off for ordinary runs (open world, redefinition); `jolt
;; build` flips it on during app emission for release/optimized modes (closed
;; world), turning on the inference + flatten + scalar-replace passes.
(define hc-optimize? #f)
(define (set-optimize! on) (set! hc-optimize? on))
(define (hc-inline-enabled? ctx) hc-optimize?)
(define (hc-inline-ir ctx ns-name nm) jolt-nil)
;; --- declare the hot clojure.core primitives so resolve-global sees them ------

View file

@ -1,4 +1,4 @@
;; host-static.ss (jolt-avt6) — host class statics + constructors on Chez.
;; host-static.ss — host class statics + constructors on Chez.
;;
;; The analyzer lowers `Class/member` to a :host-static node and `(Class. ...)` /
;; `(new Class ...)` to a :host-new node (jolt-core/jolt/analyzer.clj); the Chez
@ -99,7 +99,7 @@
(let ((ctor (lookup-class class-ctors-tbl class)))
(cond
(ctor (apply ctor args))
;; deftype/defrecord (jolt-499t): the type name is bound as a VAR (the
;; deftype/defrecord: the type name is bound as a VAR (the
;; make-deftype-ctor closure) in its defining ns, not a registered host class.
;; Resolve it in the current ns / clojure.core and invoke it — so (P. args)
;; works the same as the ->P factory.
@ -111,7 +111,7 @@
(error #f (string-append "No constructor for class " class))))))))
;; ---- coercion helpers -------------------------------------------------------
;; numeric tower (jolt-n6al): currentTimeMillis/nanoTime are exact longs (JVM).
;; numeric tower: currentTimeMillis/nanoTime are exact longs (JVM).
(define (->num x) x)
(define (jnum->exact n) (exact (truncate n)))
;; parse an integer string in radix; #f on failure
@ -161,7 +161,7 @@
(cons "PI" (->dbl (* 4 (atan 1)))) (cons "E" (->dbl (exp 1)))
(cons "random" (lambda args (random 1.0)))))
;; Thread: real OS threads back futures/promises (jolt-byjr), so sleep genuinely
;; Thread: real OS threads back futures/promises, so sleep genuinely
;; parks the calling thread for `ms` milliseconds (a worker sleeping doesn't block
;; the parent). yield hands off the scheduler.
(register-class-statics! "Thread"
@ -178,7 +178,7 @@
(list (cons "getContextClassLoader" (lambda (self) (make-jhost "classloader" '())))))
;; clojure.lang.LockingTransaction: jolt has no STM (no refs/dosync), so a
;; transaction is never running. isRunning -> false (jolt-0obq).
;; transaction is never running. isRunning -> false.
(register-class-statics! "LockingTransaction" (list (cons "isRunning" (lambda () #f))))
(register-class-statics! "clojure.lang.LockingTransaction" (list (cons "isRunning" (lambda () #f))))
@ -267,7 +267,7 @@
(apply jolt-format (car rest) (cdr rest))
(apply jolt-format a rest))))))
;; ---- java.text.NumberFormat (jolt-1nnn) -------------------------------------
;; ---- java.text.NumberFormat -------------------------------------------------
;; A grouping decimal formatter (selmer number-format / cuerdas). state:
;; #(grouping? min-frac max-frac). .format groups the integer part with commas.
(define (nf-make grouping? minf maxf) (make-jhost "numberformat" (vector grouping? minf maxf)))
@ -371,7 +371,7 @@
((char=? (string-ref l i) #\=) i)
(else (scan (+ i 1)))))))
(loop (if eq (cons (cons (substring l 0 eq) (substring l (+ eq 1) (string-length l))) acc) acc)))))))))
;; JOLT_BAKE_ENV_ALLOWLIST (jolt-s3j): when set, only the listed comma-separated
;; JOLT_BAKE_ENV_ALLOWLIST: when set, only the listed comma-separated
;; names are served; unset (the normal case) reads are live and unfiltered.
(define (env-allowlist)
(let ((a (getenv "JOLT_BAKE_ENV_ALLOWLIST")))
@ -399,7 +399,7 @@
;; or a unique sentinel). Each call returns a new jhost so identical?/= separate.
(register-class-ctor! "Object" (lambda _ (make-jhost "object" (vector))))
;; ---- java.util.ArrayList (jolt-1nnn) ----------------------------------------
;; ---- java.util.ArrayList ----------------------------------------------------
;; A mutable list backed by a Scheme list in a box. medley's stateful transducers
;; (window / partition-between) build one with .add / .size / .toArray / .clear /
;; .remove. (ArrayList.) | (ArrayList. n) | (ArrayList. coll).
@ -899,5 +899,5 @@
(def-var! "clojure.core" "__register-instance-check!"
(lambda (f) (set! user-instance-checks (append user-instance-checks (list f))) jolt-nil))
;; (jolt.host/table? x) — is x a host tagged-table (the Janet-table replacement)?
;; (jolt.host/table? x) — is x a host tagged-table?
(def-var! "jolt.host" "table?" (lambda (x) (if (htable? x) #t #f)))

View file

@ -1,10 +1,9 @@
;; host tables + sorted collections (jolt-cf1q.3, jolt-0zoy) — the jolt.host
;; value primitives and the 25-sorted tier's runtime.
;; host tables + sorted collections — the jolt.host value primitives and the
;; 25-sorted tier's runtime.
;;
;; jolt.host/tagged-table + ref-put! + ref-get resolved to jolt-nil on the Chez
;; prelude, so the whole sorted tier (sorted-map/sorted-set/subseq/rsubseq) AND
;; every overlay fn that calls (sorted? x) — empty, ifn?, reversible?, map?, set?,
;; coll? — hit the apply-jolt-nil crash bucket. This provides:
;; jolt.host/tagged-table + ref-put! + ref-get back the whole sorted tier
;; (sorted-map/sorted-set/subseq/rsubseq) AND every overlay fn that calls
;; (sorted? x) — empty, ifn?, reversible?, map?, set?, coll?. This provides:
;; 1. tagged-table / ref-put! / ref-get over a Chez mutable tagged-table type
;; (a string-keyed hashtable wrapped in an `htable` record), def-var!'d into
;; the jolt.host ns. The sorted tier (25-sorted.clj) mints its wrapper with
@ -28,10 +27,9 @@
(let ((h (make-hashtable string-hash string=?)))
(hashtable-set! h "jolt/type" tag)
(make-htable h)))
;; ref-put! threads the table back; a nil value REMOVES the key (Janet table
;; semantics — h-ref-put!). Errors on a non-htable so the atom-watch / volatile
;; uses (which pass a different ref type and have no Chez table yet) stay a crash
;; rather than silently diverging.
;; ref-put! threads the table back; a nil value REMOVES the key. Errors on a
;; non-htable so the atom-watch / volatile uses (which pass a different ref type
;; and have no table yet) stay a crash rather than silently diverging.
(define (jolt-ref-put! t k v)
(unless (htable? t) (error #f "ref-put!: not a host table" t))
(if (jolt-nil? v)

View file

@ -1,4 +1,4 @@
;; #inst values + a java.time formatting shim (jolt-at0a, inc X).
;; #inst values + a java.time formatting shim.
;;
;; A #inst literal lowers (analyzer :inst node -> emit) to (jolt-inst-from-string
;; "…"); this file parses the RFC3339 string to epoch-ms and models the value as a
@ -356,7 +356,7 @@
;; java.util.Date / java.sql.Timestamp: #inst's classes. (Date.) = now, (Date. ms)
;; or (Date. another-date) -> a jinst (ms-of accepts a number / jinst / instant), so
;; .getTime / inst? / instance? Date|Timestamp work. (jolt-dcmm)
;; .getTime / inst? / instance? Date|Timestamp work.
(define (date-ctor . args)
(make-jinst (if (null? args) (now-ms) (ms->exact (ms-of (car args))))))
(register-class-ctor! "Date" date-ctor)

View file

@ -1,5 +1,5 @@
;; java.io.File + host file I/O (jolt-yyud). A
;; Chez-native implementation over Chez's filesystem primitives. A File is a
;; java.io.File + host file I/O, implemented over Chez's filesystem
;; primitives. A File is a
;; path-backed jfile record: (instance? java.io.File f) is true, str/slurp coerce
;; it to its path, and the File method surface (getName/getPath/exists/
;; isDirectory/isFile/listFiles) dispatches through record-method-dispatch.
@ -8,8 +8,7 @@
;; list-dir for the overlay file-seq (20-coll.clj), which calls __file?/__dir?/
;; __list-dir + the .isDirectory/.listFiles/.isFile method surface.
;;
;; Reader/StringReader-coupled io (io/reader, line-seq over a file, .toURL,
;; slurp over a reader) is deferred to jolt-at0a. Loaded LAST in rt.ss, after
;; Loaded LAST in rt.ss, after
;; dot-forms.ss (so the jfile method arm wraps the fully-built dispatch) and
;; natives-meta.ss / records.ss / printing.ss (jolt-type / instance-check /
;; jolt-str-render-one, which it extends).
@ -109,8 +108,8 @@
(%io-rmd obj method-name rest-args))))
;; .isDirectory / .listFiles emit to jolt-host-call (rt.ss), not record-method-
;; dispatch — the Phase-1 shims there assume a
;; path STRING target. Make them jfile-aware so file-seq's File branch works.
;; dispatch — the shims there assume a path STRING target. Make them jfile-aware
;; so file-seq's File branch works.
(define %io-host-call jolt-host-call)
(set! jolt-host-call
(lambda (method target . args)
@ -156,17 +155,15 @@
(begin (reader-refill! r "") (values jolt-nil #f))
(begin (reader-refill! r (jolt-nth pr 1)) (values (jolt-nth pr 0) #t)))))
;; clojure.edn/read over a reader (jolt-uicd): the overlay edn.clj's drain-reader is
;; janet/type-coupled, so on Chez we drain the jhost reader to a string and read the
;; clojure.edn/read over a reader: drain the jhost reader to a string and read the
;; first EDN form (read-string). Re-asserted over the prelude in post-prelude.ss.
(define (chez-edn-read reader)
(jolt-invoke (var-deref "clojure.core" "read-string")
(if (reader-jhost? reader) (drain-reader reader) (jolt-str-render-one reader))))
;; line-seq (jolt-0obq): the overlay line-seq reads via a Janet map-reader's
;; :read-line-fn, but a Chez io/reader is a jhost StringReader. Drain it (or take a
;; string) and split on newline; a trailing newline does NOT yield a final empty
;; line (like readLine -> nil at EOF). Re-asserted in post-prelude.ss.
;; line-seq: an io/reader is a jhost StringReader. Drain it (or take a string)
;; and split on newline; a trailing newline does NOT yield a final empty line
;; (like readLine -> nil at EOF). Re-asserted in post-prelude.ss.
(define (chez-lines s)
(let loop ((cs (string->list s)) (cur '()) (acc '()))
(cond ((null? cs) (reverse (if (null? cur) acc (cons (list->string (reverse cur)) acc))))
@ -350,7 +347,7 @@
(else (let ((ctor (lookup-class class-ctors-tbl "URL")))
(if ctor (ctor (jolt-str-render-one x)) (make-url (jolt-str-render-one x))))))))
;; --- java.lang.ClassLoader (jolt-1nnn) --------------------------------------
;; --- java.lang.ClassLoader --------------------------------------------------
;; jolt has no classpath; a "classloader" resolves a named resource against the
;; loader's source roots (the same model as clojure.java.io/resource), returning a
;; file: URL or nil. getSystemClassLoader / a thread's contextClassLoader both hand
@ -380,7 +377,7 @@
(register-class-statics! "Thread" (list (cons "currentThread" (lambda () the-thread))))
(register-class-statics! "java.lang.Thread" (list (cons "currentThread" (lambda () the-thread))))
;; --- java.io.File / java.util.UUID constructors (jolt-1nnn) ------------------
;; --- java.io.File / java.util.UUID constructors -----------------------------
;; (java.io.File. parent child) joins with "/"; (File. path) wraps the path.
(register-class-ctor! "File"
(lambda (a . rest)
@ -396,7 +393,7 @@
(cons "fromString" (lambda (s) (jolt-parse-uuid (jolt-str-render-one s))))))
(register-class-ctor! "UUID" (lambda (s) (jolt-parse-uuid (jolt-str-render-one s))))
;; --- java.net.URI (jolt-1nnn) -----------------------------------------------
;; --- java.net.URI -----------------------------------------------------------
;; A minimal RFC-3986 split into scheme/authority/host/port/path/query/fragment,
;; kept in a jhost "uri" carrying the original string. (str u)/(.toString u) give
;; the original; getHost is nil for a relative URI (hiccup.util/to-str branches on

View file

@ -1,12 +1,10 @@
;; lazy-seq bridge (jolt-cf1q.3, jolt-dmw9) — make-lazy-seq / coll->cells.
;; lazy-seq bridge — make-lazy-seq / coll->cells.
;;
;; The `lazy-seq` macro (00-syntax.clj) expands to
;; (make-lazy-seq (fn* [] (coll->cells (do body))))
;; and `lazy-cat` to (concat (lazy-seq c) ...). make-lazy-seq / coll->cells had
;; no Chez shim, so EVERY overlay fn
;; and `lazy-cat` to (concat (lazy-seq c) ...). These back every overlay fn
;; built on lazy-seq — repeat / iterate / cycle / dedupe / take-nth / keep /
;; interpose / reductions / tree-seq (-> flatten) / lazy-cat — resolved the call
;; to jolt-nil and hit the apply-jolt-nil crash bucket.
;; interpose / reductions / tree-seq (-> flatten) / lazy-cat.
;;
;; Bridge to the cseq model (seq.ss): a `jolt-lazyseq` is a deferred seq — a 0-arg
;; thunk that, when forced once, yields a seq (cseq | nil). coll->cells coerces the

View file

@ -1,10 +1,9 @@
;; loader.ss (jolt-90sp) — file-based namespace loading + a shell primitive.
;; loader.ss — file-based namespace loading + a shell primitive.
;;
;; The corpus/CLI spine compiles one program at a time; namespaces declared in
;; that program see each other because a top-level (do …) unrolls. A real project
;; spans many FILES, so `require` must locate a namespace's source on the search
;; roots and load it — transitively, once each. This is the piece the Phase-3
;; "cross-ns load is deferred" note left open (ns.ss).
;; roots and load it — transitively, once each.
;;
;; Loaded by cli.ss AFTER compile-eval.ss (it calls jolt-compile-eval-form). The
;; gates load compile-eval.ss but NOT this file, so the corpus/unit/sci runners
@ -54,6 +53,23 @@
(vector-for-each (lambda (c) (hashtable-set! loaded-ns (var-cell-ns c) #t))
(hashtable-values var-table))
;; Does `name` already have vars in the var-table? A namespace baked into the
;; image after the snapshot above — an AOT'd app namespace in a `jolt build`
;; binary — exists in memory with no source file; a later `require` of it must
;; no-op rather than hunt the (absent) source.
(define (ns-has-vars? name)
(let ((found #f))
(vector-for-each
(lambda (c) (when (and (not found) (string=? (var-cell-ns c) name)) (set! found #t)))
(hashtable-values var-table))
found))
;; Called after a file-backed namespace finishes loading, with (name file). The
;; build driver sets this to record app namespaces in dependency order for AOT
;; emission; a no-op for normal runs.
(define ns-loaded-hook (lambda (name file) #f))
(define (set-ns-loaded-hook! f) (set! ns-loaded-hook f))
;; Read every form from a file and compile+eval it in turn. The first form is
;; normally (ns …), which expands to (in-ns …) and switches the current ns, so
;; later forms compile in that namespace — (chez-current-ns) is re-read each step.
@ -81,17 +97,22 @@
;; restored afterward, since loading the file switched it.
(define (load-namespace name)
(unless (hashtable-ref loaded-ns name #f)
(hashtable-set! loaded-ns name #t)
(let ((file (find-ns-file name)))
(if (not file)
(begin
(hashtable-delete! loaded-ns name)
(error #f (string-append "Could not locate " (ns-name->rel name)
".clj (or .cljc) on the source roots") name))
(let ((saved (chez-current-ns)))
(load-jolt-file file)
;; restore the current ns (thread-local); *ns* reads derive from it.
(set-chez-ns! saved))))))
(cond
(file
(hashtable-set! loaded-ns name #t) ; mark before load so a cycle terminates
(let ((saved (chez-current-ns)))
(load-jolt-file file)
;; restore the current ns (thread-local); *ns* reads derive from it.
(set-chez-ns! saved))
(ns-loaded-hook name file))
;; No source file but the namespace exists in memory (AOT'd into a built
;; binary): it's already defined — mark loaded and move on.
((ns-has-vars? name)
(hashtable-set! loaded-ns name #t))
(else
(error #f (string-append "Could not locate " (ns-name->rel name)
".clj (or .cljc) on the source roots") name))))))
;; load-file: load an explicit path (a `run FILE`), in the current ns.
(define (jolt-load-file path)

View file

@ -1,10 +1,10 @@
;; clojure.math (jolt-22vo) — Chez host shim over native flonum math.
;; clojure.math host shim over native flonum math.
;;
;; clojure.math is registered as native bindings (jolt-h79), NOT a .clj file — so
;; there's no source tier to emit. Chez provides its own def-var! shims here, one per
;; clojure.math fn, over Chez's native procedures. The analyzer knows the
;; clojure.math ns exists, so a ref
;; like clojure.math/sqrt lowers to a var-deref; these cells back it at runtime.
;; clojure.math is registered as native bindings, NOT a .clj file — so there's no
;; source tier to emit. The def-var! shims here back each clojure.math fn over
;; Chez's native procedures. The analyzer knows the clojure.math ns exists, so a
;; ref like clojure.math/sqrt lowers to a var-deref; these cells back it at
;; runtime.
;;
;; jolt is all-flonum, so every result is a flonum (inputs arrive as flonums; Chez
;; sqrt/sin/expt/... return flonums for flonum args). Semantics match

View file

@ -1,4 +1,4 @@
;; multimethods (jolt-9ls5) — the multimethod dispatch runtime on the Chez host.
;; multimethods — the multimethod dispatch runtime on the Chez host.
;;
;; defmulti/defmethod are macros that expand to ctx-capturing setup CALLS
;; (defmulti-setup / defmethod-setup, + the table ops get-method/methods/
@ -21,7 +21,7 @@
;; so they agree with defmulti. Loaded from rt.ss after seq.ss (jolt-invoke),
;; collections.ss (jolt=/key-hash/jolt-hash-map) and the var-cell machinery.
;; THREAD-LOCAL (jolt-6rld): a Chez thread-parameter, so each OS thread (an nREPL
;; THREAD-LOCAL: a Chez thread-parameter, so each OS thread (an nREPL
;; session worker / future) has its own current ns — vars stay global, only the
;; "current ns" pointer is per-thread, matching Clojure's thread-local *ns*. A new
;; thread inherits the forking thread's value. `star-ns-cell` (the *ns* var cell,

View file

@ -1,4 +1,4 @@
;; natives-array.ss (jolt-cf1q.7) — Java-style mutable arrays for the Chez host.
;; natives-array.ss — Java-style mutable arrays for the Chez host.
;;
;; A jolt-array wraps a Chez mutable vector + a `kind` tag (for bytes?). The array
;; CONSTRUCTORS are native (they build the backing); the overlay's aget/aset/alength
@ -24,7 +24,7 @@
(make-jolt-array (make-vector (exact (na-idx a)) (if (pair? rest) (car rest) init)) kind)
(na-from-seq a kind)))
;; numeric tower (jolt-n6al): array element defaults / masked bytes / count are
;; numeric tower: array element defaults / masked bytes / count are
;; EXACT integers (= JVM byte/short/int), matching exact integer literals.
(define (na-byte-of v) (bitwise-and (exact (floor v)) #xff))

View file

@ -1,10 +1,8 @@
;; Collection constructors + rand (jolt-cf1q.3 Phase 2 inc A) — host-coupled
;; natives the overlay assumes as bare clojure.core vars but which were never
;; def-var!'d, so they resolved to jolt-nil and any call hit the apply-jolt-nil
;; crash bucket. The persistent-collection constructors already exist in
;; collections.ss (jolt-hash-map / jolt-hash-set / jolt-vector); this just binds
;; the public clojure.core names to them. Loaded after def-var! (rt.ss) + the
;; collections + seq tiers. hash-map/array-map/hash-set/set/rand semantics.
;; Collection constructors + rand — host-coupled natives the overlay assumes as
;; bare clojure.core vars. The persistent-collection constructors already exist
;; in collections.ss (jolt-hash-map / jolt-hash-set / jolt-vector); this just
;; binds the public clojure.core names to them. Loaded after def-var! (rt.ss) +
;; the collections + seq tiers. hash-map/array-map/hash-set/set/rand semantics.
;; hash-map / hash-set: variadic kvs / elems straight onto the existing ctors.
;; array-map: Clojure preserves insertion order, but jolt's `=` is structural and

View file

@ -1,4 +1,4 @@
;; metadata (jolt-cf1q.3 Phase 2 inc E) — meta / with-meta. Chez values don't
;; metadata — meta / with-meta. Chez values don't
;; carry metadata, so collections use an identity-keyed side-table: with-meta
;; returns a fresh COPY of the value (new identity) and records its meta there, so
;; the original is unchanged (Clojure's immutable-with-meta) and a copy made by a
@ -13,7 +13,7 @@
(cond
((symbol-t? x) (let ((m (symbol-t-meta x))) (if (jolt-nil? m) jolt-nil m)))
;; a var's meta is {:ns :name} (derived from the cell) + any def-time user
;; meta from rt.ss's var-meta-table (jolt-zikh).
;; meta from rt.ss's var-meta-table.
((var-cell? x)
(let ((user (hashtable-ref var-meta-table x #f)))
(jolt-assoc (if user user (jolt-hash-map))

View file

@ -1,5 +1,4 @@
;; misc scalar natives (jolt-cf1q.3) — UUID, format/printf, tagged-literal,
;; bigint. Seed natives that were jolt-nil on the prelude.
;; misc scalar natives — UUID, format/printf, tagged-literal, bigint.
;;
;; Loaded after the printers (pr-str of a uuid is #uuid "…") and converters
;; (jolt-str-render-one for %s / str of a uuid).

View file

@ -1,8 +1,7 @@
;; bit ops + string->number parsers (jolt-cf1q.3 Phase 2 inc C) — host-coupled
;; natives (bit family, parse-long/double) that
;; resolved to jolt-nil. jolt models every number as a double, so bit ops coerce
;; to an exact integer, operate, and return a flonum. parse-* use
;; strict shapes (Clojure 1.11: nil on malformed, throw on a non-string).
;; bit ops + string->number parsers — host-coupled natives (bit family,
;; parse-long/double). jolt models every number as a double, so bit ops coerce
;; to an exact integer, operate, and return a flonum. parse-* use strict shapes
;; (Clojure 1.11: nil on malformed, throw on a non-string).
;; bit ops return EXACT integers (= JVM long). ->int coerces the operand.
(define (->int x) (exact (truncate x)))

View file

@ -1,5 +1,5 @@
;; natives-parity.ss (jolt-cf1q.7) — native Chez shims for clojure.core fns that
;; had no Chez shim, so they resolved to nil ("not a fn"). Pure-Chez, JVM-matching.
;; natives-parity.ss — native Chez shims for clojure.core fns. Pure-Chez,
;; JVM-matching.
;;
;; Loaded after host-table.ss (htable-sorted?), transients.ss (jolt-transient?),
;; values.ss (jolt-hash), seq.ss (jolt-seq/seq->list/list->cseq/jolt-invoke).

View file

@ -1,4 +1,4 @@
;; natives-queue.ss (jolt-b8he) — clojure.lang.PersistentQueue for the Chez host.
;; natives-queue.ss — clojure.lang.PersistentQueue for the Chez host.
;;
;; A functional queue: a `front` Scheme list (the dequeue end, head = front of the
;; queue) + a reversed `rear` Scheme list (the enqueue end, head = most recent).

View file

@ -1,9 +1,6 @@
;; seq-native shims (jolt-y6mv) — native seq fns the overlay assumes are
;; clojure.core natives but which have no def-var! in the assembled prelude and
;; resolve to jolt-nil on
;; Chez. This was the dominant prelude-parity crash bucket ('apply jolt-nil').
;; Each is a pure fn over the existing seq layer (seq.ss) — collection arities
;; only; the 1-arg transducer arities are jolt-kxsr. Loaded last (after
;; seq-native shims — native seq fns the overlay assumes are clojure.core
;; natives. Each is a pure fn over the existing seq layer (seq.ss) — collection
;; arities only; the 1-arg transducer arities follow below. Loaded last (after
;; converters.ss for jolt-compare and seq.ss for the reduced record).
;; reduced / reduced? — the box itself is the jolt-reduced record from seq.ss
@ -14,7 +11,7 @@
(define (ensure-reduced x) (if (jolt-reduced? x) x (make-jolt-reduced x)))
;; ============================================================================
;; transducers (jolt-kxsr) — the 1-arg arity of map/filter/take/... returns a
;; transducers — the 1-arg arity of map/filter/take/... returns a
;; transducer (fn [rf] rf') where rf' is a reducing fn with arities
;; []=init, [acc]=complete, [acc x]=step. rf and the mapping/predicate fns are jolt values, so every
;; call routes through jolt-invoke. A `reduced` step stops the fold — reduce-seq

View file

@ -1,4 +1,4 @@
;; natives-str.ss (jolt-nfca) — java.lang.String method interop on Chez.
;; natives-str.ss — java.lang.String method interop on Chez.
;;
;; (.method s arg*) on a string target lowers to record-method-dispatch (emit.ss),
;; which falls through to jolt-string-method here when the target is a string.
@ -316,10 +316,9 @@
;; (require ...) / (use ...) at runtime: register each spec's :as alias + :refer
;; names into the runtime ns tables (chez-register-spec!, ns.ss), keyed by the
;; current ns. The zero-Janet spine also pre-registers these at analyze time
;; (idempotent); but when the JANET analyzer compiled the form (the prelude path)
;; the Chez tables were never populated, so ns-aliases/ns-resolve over an :as alias
;; need this runtime registration (jolt-cf1q.7). Specs arrive evaluated (quoted).
;; current ns. The spine also pre-registers these at analyze time (idempotent),
;; so ns-aliases/ns-resolve over an :as alias resolve. Specs arrive evaluated
;; (quoted).
(define (chez-runtime-require . specs)
(for-each (lambda (s) (chez-register-spec! (chez-current-ns) s)) specs)
jolt-nil)

View file

@ -1,10 +1,8 @@
;; volatiles + sequence / transduce (jolt-cf1q.3, jolt-xjx6) — the transducer
;; application surface.
;; volatiles + sequence / transduce — the transducer application surface.
;;
;; `sequence` and `transduce` are seed natives that were jolt-nil on the prelude.
;; The stateful transducer arities (take-nth/map-indexed/partition-by/dedupe/
;; distinct, all overlay) use volatile!/vswap!/vreset!/deref, also unshimmed — so
;; the whole (sequence xform coll) / (transduce xform f coll) surface crashed.
;; `sequence` and `transduce` are seed natives. The stateful transducer arities
;; (take-nth/map-indexed/partition-by/dedupe/distinct, all overlay) use
;; volatile!/vswap!/vreset!/deref, shimmed here.
;;
;; Volatiles are a native mutable box (jvol) — the overlay vreset!/vswap! drive a
;; volatile through jolt.host/ref-put!+get, but a Chez volatile is a record, not a

View file

@ -1,15 +1,15 @@
;; namespaces (jolt-yxqm, Phase 2) — the namespace value model.
;; namespaces — the namespace value model.
;;
;; Chez has no ctx, so the ctx-coupled seed natives (find-ns/resolve/in-ns/…) are
;; reimplemented over the rt.ss var-table (cells carry ns + name + defined?) and
;; the multimethods.ss chez-current-ns box. A namespace VALUE is a `jns` record
;; carrying its name string — distinct from a map/record so (map? ns) is #f, but
;; the overlay's `ns-name` reads (get ns :name); that's overridden natively in
;; post-prelude.ss (loads after the overlay clobbers it).
;; The namespace ops (find-ns/resolve/in-ns/…) work over the rt.ss var-table
;; (cells carry ns + name + defined?) and the multimethods.ss chez-current-ns
;; box. A namespace VALUE is a `jns` record carrying its name string — distinct
;; from a map/record so (map? ns) is #f, but the overlay's `ns-name` reads
;; (get ns :name); that's overridden natively in post-prelude.ss (loads after
;; the overlay clobbers it).
;;
;; Loaded LAST from rt.ss. SCOPE (jolt-yxqm): the read/resolve/in-ns/*ns* ops.
;; use/require cross-ns SWITCHING is deferred (Phase 3) — the analyzer bakes a
;; def's target ns at compile time, so a runtime in-ns can't redirect later defs.
;; Loaded LAST from rt.ss. The analyzer bakes a def's target ns at compile time,
;; so a runtime in-ns redirects only *ns* / str-of-ns, not later defs in the
;; same program.
(define-record-type jns (fields name) (nongenerative chez-jns-v1))
@ -24,11 +24,11 @@
(intern-ns! "user")
(intern-ns! "clojure.core")
;; --- namespace aliases (jolt-qjr0) -----------------------------------------
;; --- namespace aliases ------------------------------------------------------
;; (require '[ns :as a]) registers a -> ns so the analyzer can resolve a/foo to
;; ns/foo. Keyed by (compile-ns . alias). On the zero-Janet spine the requires are
;; pre-registered at analyze time (compile-eval.ss) — analysis precedes eval, so a
;; runtime require no-op is fine. Also drives jolt-ns-aliases below.
;; ns/foo. Keyed by (compile-ns . alias). The requires are pre-registered at
;; analyze time (compile-eval.ss) — analysis precedes eval, so a runtime require
;; no-op is fine. Also drives jolt-ns-aliases below.
(define ns-alias-table (make-hashtable equal-hash equal?))
(define (chez-register-alias! cns alias target)
(hashtable-set! ns-alias-table (cons cns alias) target))
@ -105,10 +105,10 @@
(define (jolt-create-ns desig) (intern-ns! (ns-desig->name desig)))
;; in-ns: register + switch the current ns + re-bind *ns* + return the jns. NOTE
;; (Phase-3 deferral): this updates only the RUNTIME current ns — subsequent defs
;; in the same program were already ns-baked by the analyzer, so it does not
;; redirect them. It is enough for *ns* / str-of-ns to track the switch.
;; in-ns: register + switch the current ns + re-bind *ns* + return the jns. This
;; updates only the RUNTIME current ns — subsequent defs in the same program were
;; already ns-baked by the analyzer, so it does not redirect them. It is enough
;; for *ns* / str-of-ns to track the switch.
(define (jolt-in-ns desig)
(let* ((nm (ns-desig->name desig)) (n (intern-ns! nm)))
;; set the THREAD-LOCAL current ns; *ns* reads derive from it (dyn-binding.ss),
@ -140,7 +140,7 @@
m))
(define (jolt-ns-publics desig) (ns-vars-pmap (ns-desig->name desig)))
;; ns-aliases (jolt-cf1q.7): the {alias-sym -> ns-value} registered under `desig`
;; ns-aliases: the {alias-sym -> ns-value} registered under `desig`
;; (default the current ns) via require :as / alias. Reads ns-alias-table.
(define (jolt-ns-aliases . desig)
(let ((cns (if (pair? desig) (ns-desig->name (car desig)) (chez-current-ns)))
@ -153,7 +153,7 @@
(hashtable-keys ns-alias-table))
m))
;; ns-refers (jolt-cf1q.7): the {sym -> var} referred into `desig` via refer/use.
;; ns-refers: the {sym -> var} referred into `desig` via refer/use.
(define (jolt-ns-refers desig)
(let ((cns (ns-desig->name desig)) (m (jolt-hash-map)))
(vector-for-each
@ -225,7 +225,7 @@
(when c (var-cell-defined?-set! c #f) (var-cell-root-set! c jolt-unbound)))
jolt-nil)
;; --- ns runtime fns (jolt-cf1q.7) -------------------------------------------
;; --- ns runtime fns ---------------------------------------------------------
;; ns-resolve: resolve `sym` as if reading it in namespace `ns-desig`. Qualified
;; syms consult that ns's :as aliases; unqualified resolve in the ns, its :refers,
;; then clojure.core. Returns the var or nil (never interns).

View file

@ -1,4 +1,4 @@
;; png.ss (jolt-90sp) — jolt.png: a minimal PNG writer, the built-in the
;; png.ss — jolt.png: a minimal PNG writer, the built-in the
;; ray-tracer-multi example renders through. Truecolor (8-bit RGB), no
;; compression: the IDAT zlib stream uses DEFLATE "stored" (uncompressed) blocks,
;; so there is no compressor to carry — just CRC-32 / Adler-32 framing over Chez

View file

@ -1,20 +1,16 @@
;; post-prelude overrides (jolt-9ziu) — loaded AFTER the assembled clojure.core
;; post-prelude overrides — loaded AFTER the assembled clojure.core
;; prelude, so these win over the overlay's own def-var!.
;;
;; A few clojure.core predicates are implemented in the overlay by inspecting a
;; Janet-host tagged value's :jolt/type key (e.g. (get x :jolt/type)). That key
;; doesn't exist for Chez-native representations: a jolt char is a Scheme char,
;; an atom is a Chez record. The overlay's def-var! loads after rt.ss, so it
;; clobbers the correct native shims (predicates.ss / atoms.ss) with versions
;; that return false on every Chez value. Re-assert the native versions here.
;;
;; (Long-term these predicates want a host-neutral implementation that calls a
;; host primitive instead of reading :jolt/type; until then this is the Chez-host
;; override.)
;; tagged value's :jolt/type key (e.g. (get x :jolt/type)). That key doesn't
;; exist for native representations: a jolt char is a Scheme char, an atom is a
;; Chez record. The overlay's def-var! loads after rt.ss, so it clobbers the
;; correct native shims (predicates.ss / atoms.ss) with versions that return
;; false on every Chez value. Re-assert the native versions here.
(def-var! "clojure.core" "char?" jolt-char-pred?)
(def-var! "clojure.core" "atom?" jolt-atom?)
;; atom watches/validators: the overlay drives these via jolt.host/ref-put! on a
;; Janet table (get a :watches), which a Chez atom record is not — re-assert the
;; tagged table (get a :watches), which a Chez atom record is not — re-assert the
;; native versions (defined in atoms.ss), and swap!/reset! notify+validate there.
(def-var! "clojure.core" "add-watch" jolt-add-watch)
(def-var! "clojure.core" "remove-watch" jolt-remove-watch)
@ -30,7 +26,7 @@
;; would wrongly report every var unbound. Native version (defined in vars.ss).
(def-var! "clojure.core" "bound?" jolt-bound?)
;; uuid?/random-uuid/parse-uuid/tagged-literal? are overlay (read :jolt/type or
;; build tagged tables) — re-assert the native versions (defined in natives-misc.ss).
;; build tagged tables) — re-assert the native versions (natives-misc.ss).
(def-var! "clojure.core" "uuid?" jolt-uuid-pred?)
(def-var! "clojure.core" "random-uuid" jolt-random-uuid)
(def-var! "clojure.core" "parse-uuid" jolt-parse-uuid)
@ -38,10 +34,10 @@
;; ns-name: the overlay reads (get ns :name) — nil on a jns namespace record.
;; Native version (defined in ns.ss) returns the namespace's name symbol.
(def-var! "clojure.core" "ns-name" jolt-ns-name)
;; concurrency (jolt-byjr): the overlay's future-done?/future-cancelled?/realized?
;; read a Janet future-map's :cached/:cancelled keys, and promise/deliver are a
;; non-blocking atom shim. A Chez future/promise is a record, and we want JVM
;; (blocking, shared-heap) semantics — re-assert the native versions. realized?
;; concurrency: the overlay's future-done?/future-cancelled?/realized? read a
;; future-map's :cached/:cancelled keys, and promise/deliver are a non-blocking
;; atom shim. A Chez future/promise is a record, and we want JVM (blocking,
;; shared-heap) semantics — re-assert the native versions. realized?
;; wraps the overlay (which still handles delay/lazy-seq/atom) for non-futures.
(def-var! "clojure.core" "future-done?" jolt-native-future-done?)
(def-var! "clojure.core" "future-cancelled?" jolt-native-future-cancelled?)
@ -68,8 +64,8 @@
;; realized? reads :jolt/type and throws on a jolt-lazyseq record.
((jolt-lazyseq? x) (jolt-lazyseq-realized? x))
(else (jolt-invoke overlay-realized? x))))))
;; clojure.edn/read over a reader: the overlay edn.clj drain-reader uses janet/type;
;; the native Chez version (io.ss) drains the jhost reader instead (jolt-uicd/jolt-7t3l).
;; clojure.edn/read over a reader: the overlay edn.clj drain-reader reads
;; :jolt/type; the native version (io.ss) drains the jhost reader instead.
(def-var! "clojure.edn" "read"
(case-lambda
((reader) (chez-edn-read reader))
@ -80,7 +76,7 @@
(def-var! "clojure.core" "line-seq"
(lambda (rdr)
(if (reader-jhost? rdr) (chez-line-seq rdr) (jolt-invoke overlay-line-seq rdr)))))
;; JVM-parity numeric tower (jolt-n6al): the overlay (20-coll.clj) carries an
;; JVM-parity numeric tower: the overlay (20-coll.clj) carries an
;; all-flonum number-predicate web with no Ratio concept (ratio? -> false,
;; double? -> not-integer, float? -> double?, rational? -> int?), which
;; misclassifies exact rationals on the Chez tower (e.g. (double? 1/2) -> true).
@ -95,11 +91,11 @@
(def-var! "clojure.core" "decimal?" jolt-decimal?)
(def-var! "clojure.core" "==" jolt-num-equiv)
;; chunked-seq? is true for a vector's seq (a real chunked-seq); the overlay's
;; always-false stub loaded over the host fn, so re-assert it (jolt-hs5q).
;; always-false stub loaded over the host fn, so re-assert it.
(def-var! "clojure.core" "chunked-seq?" na-chunked-seq?)
;; record? is a host type check (jrec?), not the overlay's (some? (get x
;; :jolt/deftype)) — the get-trick invokes a sorted-map's comparator on
;; :jolt/deftype and throws (jolt-3bbj). Matches the JVM (instance? IRecord).
;; :jolt/deftype and throws. Matches the JVM (instance? IRecord).
(def-var! "clojure.core" "record?" (lambda (x) (jrec? x)))
;; read / read+string over a HOST reader jhost (java.io StringReader/PushbackReader):

View file

@ -1,21 +1,19 @@
;; type predicates + simple accessors (jolt-9ziu) — host-coupled natives.
;; type predicates + simple accessors — host-coupled natives.
;;
;; These are host primitives (not clojure.core overlay fns), so they're never
;; def-var!'d by the assembled prelude; the Chez host must provide them.
;; map?/vector?/set? are STRICT
;; over the persistent-collection records, seq? is true only for real sequences,
;; coll? is the union. Records (shape-recs) are Phase 2, so the record arms of the
;; predicates are simply absent here for now.
;; map?/vector?/set? are STRICT over the persistent-collection records, seq? is
;; true only for real sequences, coll? is the union. Record arms are added by
;; records.ss, which extends these dispatchers.
(define (jolt-map? x) (pmap? x))
;; a map entry is a pvec under the hood AND is vector? — Clojure's MapEntry
;; implements IPersistentVector, so (vector? (first {:a 1})) is true
;; (jolt-75sv corrected the earlier exclusion).
;; implements IPersistentVector, so (vector? (first {:a 1})) is true.
(define (jolt-vector? x) (pvec? x))
(define (jolt-set? x) (pset? x))
(define (jolt-seq? x) (or (cseq? x) (empty-list-t? x)))
;; (list? x): a list-marked cseq node or the empty list (). A lazy/vector-backed
;; seq, (rest list), (seq coll), (map …) are seqs but not lists (jolt-75sv).
;; seq, (rest list), (seq coll), (map …) are seqs but not lists.
(define (jolt-list-pred? x) (or (and (cseq? x) (cseq-list? x)) (empty-list-t? x)))
(define (jolt-coll-pred? x)
(or (pvec? x) (pmap? x) (pset? x) (cseq? x) (empty-list-t? x)))

View file

@ -1,7 +1,6 @@
;; readable printer + output seams (jolt-cf1q.3 Phase 2 inc B) — the __pr-str1 /
;; __write / __with-out-str host seams the overlay's pr-str/pr/prn/print/println/
;; *-str family is built on (jolt-core/clojure/core/20-coll.clj). They resolved to
;; jolt-nil, so the whole print family hit the apply-jolt-nil crash bucket.
;; readable printer + output seams — the __pr-str1 / __write / __with-out-str
;; host seams the overlay's pr-str/pr/prn/print/println/*-str family is built on
;; (jolt-core/clojure/core/20-coll.clj).
;;
;; jolt-pr-str (rt.ss) is STR-style: strings render raw. pr-str needs READABLE
;; (pr) style: strings quoted+escaped at every nesting level. This adds the

View file

@ -1,14 +1,12 @@
;; Chez-side Clojure data reader (jolt-r8ku, inc Y).
;; Chez-side Clojure data reader.
;;
;; The data half of runtime read/eval: a recursive-descent reader that parses
;; ONE Clojure form off a string and produces jolt runtime values
;; (the analyzer/eval half — eval, load-string,
;; runtime defmacro — stays Phase-3, it needs the compiler at runtime). Two host
;; ONE Clojure form off a string and produces jolt runtime values. Two host
;; seams hang off it:
;; read-string : string -> first form (clojure.core seam, src 772)
;; __parse-next : string -> [form rest] | nil (the *in* family seam, src 801)
;; read / read+string / with-in-str / line-seq / clojure.edn are Clojure over
;; these (jolt-core/clojure/core/50-io.clj, src/jolt/clojure/edn.clj).
;; these (jolt-core/clojure/core/50-io.clj, stdlib/clojure/edn.clj).
;;
;; Form shapes:
;; sets -> {:jolt/type :jolt/set :value [...]} (a FORM, not a set)
@ -74,9 +72,6 @@
((char=? (string-ref str i) c) i)
(else (loop (+ i 1)))))))
;; jolt models EVERY number as a double (emit-const lowers integer literals to
;; flonums too), so the reader coerces every parsed number to inexact — else a
;; read int (exact) is not jolt= to a source int literal (flonum).
;; Numeric tower (JVM parity): integer literals read as exact integers (= Long/
;; BigInt, arbitrary precision), a/b ratios as exact rationals (= Ratio), and
;; decimal/exponent literals as flonums (= double).
@ -139,7 +134,7 @@
(let ((n (string->number (substring body 0 (- blen 1)))))
(and n (integer? n) (* sign n))))
;; bigdecimal suffix M -> a :bigdec form carrying the numeric text; the back
;; end lowers it to a runtime jbigdec (jolt-i2jm).
;; end lowers it to a runtime jbigdec.
((and (> blen 1) (char=? (string-ref body (- blen 1)) #\M))
(let ((n (string->number (substring body 0 (- blen 1)))))
(and n (real? n)
@ -311,7 +306,7 @@
(else (jolt-list (jolt-symbol #f "with-meta") target meta))))
;; --- # dispatch -------------------------------------------------------------
;; #(...) anonymous fn shorthand (jolt-qjr0): % -> p1, %N -> pN, %& -> rest. The
;; #(...) anonymous fn shorthand: % -> p1, %N -> pN, %& -> rest. The
;; fixed arity is the MAX positional used (Clojure: #(do %2 %&) -> [p1 p2 & rest]).
;; Param names carry a trailing "#" so a #() inside a syntax-quote still reads them
;; as auto-gensyms.
@ -366,7 +361,7 @@
(if rest-sym (list (jolt-symbol #f "&") rest-sym) '()))))
(values (jolt-list (jolt-symbol #f "fn*") (apply jolt-vector params) body) j))))))
;; reader conditionals (jolt-qjr0): jolt's feature set is {:jolt :clj :default};
;; reader conditionals: jolt's feature set is {:jolt :clj :default};
;; the FIRST clause whose feature key is in the set wins (clause order, like
;; Clojure). jolt is a Clojure/JVM-compatible host — it emulates clojure.lang.*
;; and java.* interop — so it reads the :clj branch of a .cljc library (the JVM
@ -537,7 +532,7 @@
(jolt-vector form (substring s j end))))))
;; __read-tagged: apply a built-in data reader to an already-read form. The tag
;; is the :#name keyword the reader produced; #uuid/#inst reuse the inc X ctors.
;; is the :#name keyword the reader produced; #uuid/#inst reuse the inst-time ctors.
(define (jolt-read-tagged tag form)
(cond
((eq? tag (keyword #f "#uuid")) (jolt-uuid-from-string form))

View file

@ -1,7 +1,5 @@
;; records + protocols (jolt-cf1q.3 Phase 2 inc D) — the deftype/defrecord +
;; defprotocol/extend-type subsystem. These are ctx-capturing natives
;; that resolved to jolt-nil on the prelude, so every record
;; case hit the apply-jolt-nil crash bucket.
;; records + protocols — the deftype/defrecord + defprotocol/extend-type
;; subsystem.
;;
;; A record is a `jrec`: a type tag ("ns.Name") + an alist of (kw . val) in
;; declared field order. It is map?/coll?, equal to another jrec of the same tag
@ -28,8 +26,8 @@
(define (jrec-has? r k)
(let loop ((ps (jrec-pairs r)))
(cond ((null? ps) #f) ((jolt=2 (caar ps) k) #t) (else (loop (cdr ps))))))
;; mutate a deftype's mutable field in place (jolt-c3q): the pairs are runtime
;; cons cells, so set-cdr! updates the field. (set! field v) inside a method
;; mutate a deftype's mutable field in place: the pairs are runtime cons cells,
;; so set-cdr! updates the field. (set! field v) inside a method
;; lowers to this; returns v, as set! does.
(define (jolt-set-field! inst k v)
(if (jrec? inst)
@ -97,8 +95,6 @@
(define %r-jolt-pr-readable jolt-pr-readable)
(set! jolt-pr-readable (lambda (x) (if (jrec? x) (jrec-pr x) (%r-jolt-pr-readable x))))
;; records are map? and coll? (Clojure: a record IS an associative map). Override
;; the public predicates to include jrec.
;; records are map? and coll? (Clojure: a record IS an associative map). The
;; predicates.ss vars hold a snapshot, so re-def-var! after extending. record? is
;; the overlay's (some? (get x :jolt/deftype)) — works for free since the get
@ -244,7 +240,7 @@
;; protocol's extended impls over the reify's host tags
;; (e.g. an Object/default extension). malli reifies some
;; protocols and relies on a protocol's default for the
;; rest (jolt-az9a).
;; rest.
(let loop ((tags (value-host-tags obj)))
(cond ((null? tags) (error #f (string-append "No reified method " method-name)))
((find-protocol-method (car tags) proto-name method-name)
@ -290,7 +286,7 @@
((reified-methods obj)
=> (lambda (rm) (let ((f (hashtable-ref rm method-name #f)))
(if f (apply jolt-invoke f obj rest) (error #f (string-append "No method " method-name))))))
;; java.lang.String interop (jolt-nfca): defined in natives-str.ss, loaded
;; java.lang.String interop: defined in natives-str.ss, loaded
;; after this file (free reference, resolved at call time).
((string? obj) (jolt-string-method method-name obj rest))
((jiterator? obj)
@ -505,7 +501,7 @@
(let ((s (jrec-pr v))) (substring s 1 (string-length s)))))
(%r-str-render-one v))))
;; `type` lives in natives-meta.ss (jolt-fmm4): it needs jolt-meta for the :type
;; `type` lives in natives-meta.ss: it needs jolt-meta for the :type
;; override and a total value->taxonomy mapping, so it sits with meta — a record
;; yields (jolt-symbol #f (jrec-tag x)), the ns.Name class-name symbol.

View file

@ -1,4 +1,4 @@
;; Phase 1 (jolt-cf1q.2) — regex on Chez via vendored irregex (jolt-i0s3).
;; regex on Chez via vendored irregex.
;;
;; Chez has no regex at all. We vendor
;; Alex Shinn's irregex (vendor/irregex, BSD) — a portable Scheme regex with
@ -33,7 +33,7 @@
(apply %chez-error args)))
(load "vendor/irregex/irregex.scm")
;; Unicode property classes \p{...} (jolt-y1zq): irregex's string syntax has no
;; Unicode property classes \p{...}: irregex's string syntax has no
;; \p{...}, so translate a fixed set of property names
;; to ASCII char classes before compiling. ASCII-only — \p{L} would need
;; UTF-8 high bytes counted as letters, which a Unicode-char Scheme string can't

View file

@ -1,4 +1,4 @@
;; Phase 1 (jolt-cf1q.2) — the minimal Chez RT the emitted Scheme rests on.
;; The minimal Chez RT the emitted Scheme rests on.
;;
;; Sits above the value model (values.ss) and below an emitted program. Adds the
;; two things the back end's output references that aren't in the value layer:
@ -20,7 +20,7 @@
;; jolt `not`: only nil and false are falsey.
(define (jolt-not x) (if (jolt-truthy? x) #f #t))
;; --- exceptions (jolt-vcsl) --------------------------------------------------
;; --- exceptions --------------------------------------------------------------
;; throw raises the jolt value RAW (no envelope);
;; catch (emitted as `guard`) binds it directly. Chez `raise` accepts any
;; object, so a thrown number/map/ex-info all work; uncaught -> non-zero exit.
@ -50,7 +50,7 @@
jolt-kw-data jolt-nil
jolt-kw-cause (if (null? more) jolt-nil (car more))))
;; --- host interop (jolt-0kf5) ------------------------------------------------
;; --- host interop ------------------------------------------------------------
;; (.method target arg*) lowers to (jolt-host-call "method" target arg*). JVM
;; interop has no general Chez analog, but the few methods jolt-core's io tier
;; calls map onto Chez equivalents: a writer's .write is a port display; a File's
@ -74,7 +74,7 @@
(define jolt-unbound (string->symbol "#<jolt-unbound>"))
;; `defined?` distinguishes a genuinely interned var (def / declare / a native-op
;; cell) from a cell lazily materialised by a forward `var-deref` / `(var x)` on a
;; not-yet-defined name — `resolve` returns the cell iff defined? (jolt-yxqm).
;; not-yet-defined name — `resolve` returns the cell iff defined?.
;; ns-unmap clears it. Avoids the (def x nil) edge of probing the root.
(define-record-type var-cell (fields ns name (mutable root) (mutable defined?)) (nongenerative var-cell-v2))
(define var-table (make-hashtable string-hash string=?))
@ -93,7 +93,7 @@
;; (pr-str (def x 1)) is "#'ns/x". The prelude's def-var! forms discard the
;; return, so this is transparent there.
(define (def-var! ns name v) (let ((c (jolt-var ns name))) (var-cell-root-set! c v) (var-cell-defined?-set! c #t) c))
;; var def-time metadata (jolt-zikh): the :def emit passes the def's reader meta
;; var def-time metadata: the :def emit passes the def's reader meta
;; (^:private / ^Type tag / docstring -> {:doc}) here, stored in an eq side-table
;; keyed by the cell. jolt-meta (natives-meta.ss) merges it onto {:ns :name},
;; which it derives from the cell — so EVERY var (plain def, native-op, declare)
@ -103,7 +103,7 @@
(define jolt-kw-var-name (keyword #f "name"))
(define (def-var-with-meta! ns name v m)
(let ((c (def-var! ns name v))) (hashtable-set! var-meta-table c m) c))
;; runtime-macro registry (jolt-r9lm, inc6b): a var whose root holds a macro
;; runtime-macro registry: a var whose root holds a macro
;; expander fn is flagged here, so the ON-CHEZ analyzer's form-macro?/form-expand-1
;; (host-contract.ss) expand it. The prelude emits each core/stdlib defmacro as a
;; def-var! of its (cross-compiled) expander followed by (mark-macro! ns name).
@ -124,17 +124,17 @@
(hashtable-set! var-table k c)
c))))
;; regex (jolt-i0s3): defines regex-t + the re-* fns (def-var!'d into
;; regex: defines regex-t + the re-* fns (def-var!'d into
;; clojure.core), so it loads after def-var! and before the printer below (which
;; renders a regex-t as #"source").
(load "host/chez/regex.ss")
;; atoms (jolt-9ziu): host-coupled mutable cells; def-var!'d into clojure.core
;; atoms: host-coupled mutable cells; def-var!'d into clojure.core
;; (atom/deref/swap!/reset! + the compare/vals kernel). Loads after def-var! and
;; jolt-invoke (seq.ss) / jolt= (values.ss) / jolt-vector (collections.ss).
(load "host/chez/atoms.ss")
;; type predicates + simple accessors (jolt-9ziu): seed natives the overlay
;; type predicates + simple accessors: seed natives the overlay
;; assumes (map?/vector?/nil?/number?/.../name/namespace), def-var!'d into
;; clojure.core. Loads after the value-model record predicates they wrap.
(load "host/chez/predicates.ss")
@ -158,7 +158,6 @@
;; quotes), chars as `\c`/`\newline`, collections recursively. NOTE: maps/sets
;; render in HAMT-iteration order, which is not a stable insertion order —
;; so unordered values are compared via `=` (true/false), not printed form.
;; The full canonical printer is Phase 2.
(define (jolt-str-join strs)
(cond ((null? strs) "") ((null? (cdr strs)) (car strs))
(else (string-append (car strs) " " (jolt-str-join (cdr strs))))))
@ -197,174 +196,174 @@
(loop (jolt-seq (seq-more s)) (cons (jolt-pr-str (seq-first s)) acc))))) ")"))
(else (format "~a" x))))
;; converters + string ops (jolt-t6cr): str/subs/vec/keyword/symbol/compare/int/
;; converters + string ops: str/subs/vec/keyword/symbol/compare/int/
;; double/gensym — host-coupled seed natives def-var!'d into clojure.core. Loaded
;; LAST because `str` reuses jolt-pr-str (defined just above).
(load "host/chez/converters.ss")
;; transients (jolt-kl2l): copy-on-write transient collections + persistent disj;
;; transients: copy-on-write transient collections + persistent disj;
;; extends get/count/contains? to see through a transient. After collections.ss
;; (the persistent ops it delegates to).
(load "host/chez/transients.ss")
;; seq-native shims (jolt-y6mv): mapcat/take-while/drop-while/partition/sort +
;; seq-native shims: mapcat/take-while/drop-while/partition/sort +
;; reduced/reduced?/identical? — seed-native fns the overlay assumes are core
;; natives. Over the seq layer + jolt-compare, so loaded after converters.ss.
(load "host/chez/natives-seq.ss")
;; readable printer + output seams (jolt-9zhh, Phase 2 inc B): __pr-str1/__write/
;; readable printer + output seams: __pr-str1/__write/
;; __with-out-str/__eprint/__eprintf — the host seams the overlay print family
;; (pr-str/pr/prn/print/println/*-str) is built on. After converters.ss (uses
;; jolt-pr-str/jolt-str-join) + seq.ss (jolt-invoke).
(load "host/chez/printing.ss")
;; collection constructors + rand (jolt-agw6, Phase 2 inc A): bind the public
;; collection constructors + rand: bind the public
;; clojure.core names hash-map/hash-set/array-map/set/rand to the existing
;; pmap/pset ctors. After collections.ss (the ctors) + seq.ss (seq->list).
(load "host/chez/natives-coll.ss")
;; bit ops + parse-long/parse-double (jolt-cf1q.3 inc C): host-coupled scalar
;; bit ops + parse-long/parse-double: host-coupled scalar
;; seed natives over the all-flonum number model.
(load "host/chez/natives-num.ss")
;; multimethods (jolt-9ls5): defmulti/defmethod dispatch runtime. Needs jolt-invoke
;; multimethods: defmulti/defmethod dispatch runtime. Needs jolt-invoke
;; (seq.ss), jolt=/key-hash/jolt-hash-map (collections.ss), jolt-atom? (atoms.ss),
;; jolt-pr-str (above), and the var-cell machinery — so loaded last.
(load "host/chez/multimethods.ss")
;; records + protocols (jolt-jgoc, Phase 2 inc D): defrecord/deftype/defprotocol/
;; records + protocols: defrecord/deftype/defprotocol/
;; extend-type/reify. A jrec record type set!-extended into the collection
;; dispatchers + a protocol registry. After multimethods.ss (chez-current-ns) and
;; the dispatchers/printers it wraps (collections/seq/values/converters/printing/
;; transients).
(load "host/chez/records.ss")
;; metadata (jolt-rkbc, Phase 2 inc E): meta / with-meta over an identity-keyed
;; metadata: meta / with-meta over an identity-keyed
;; side-table. After records.ss (jrec) + the collection ctors it copies.
(load "host/chez/natives-meta.ss")
;; host class tokens (jolt-13zk): bare class names (String/Keyword/File...) ->
;; host class tokens: bare class names (String/Keyword/File...) ->
;; canonical JVM class-name strings + (class x). After natives-meta.ss (jolt-type)
;; and the printer (jolt-str-render-one).
(load "host/chez/host-class.ss")
;; dynamic vars (jolt-9ls5): *clojure-version* / *unchecked-math* constants the host
;; dynamic vars: *clojure-version* / *unchecked-math* constants the host
;; binds natively. After collections.ss (jolt-hash-map) + def-var!.
(load "host/chez/dynamic-vars.ss")
;; host tables + sorted collections (jolt-0zoy, Phase 2): jolt.host/tagged-table/
;; host tables + sorted collections: jolt.host/tagged-table/
;; ref-put!/ref-get + the 25-sorted tier's runtime (sorted-map/sorted-set routed
;; through their :ops table). Loaded LAST — wraps the jrec-extended dispatchers
;; (records.ss), jolt-disj (transients.ss), and value-host-tags (records.ss).
(load "host/chez/host-table.ss")
;; lazy-seq bridge (jolt-dmw9, Phase 2): make-lazy-seq / coll->cells over the
;; lazy-seq bridge: make-lazy-seq / coll->cells over the
;; cseq model — unblocks every overlay fn built on the lazy-seq macro (repeat/
;; iterate/cycle/dedupe/take-nth/keep/interpose/reductions/tree-seq/lazy-cat).
;; Loaded LAST so %ls-seq captures the fully-extended (sorted-aware) jolt-seq.
(load "host/chez/lazy-bridge.ss")
;; volatiles + sequence / transduce (jolt-xjx6, Phase 2): native volatile boxes +
;; volatiles + sequence / transduce: native volatile boxes +
;; the transduce/sequence entry points over into-xform/reduce-seq. After
;; natives-seq.ss (into-xform), seq.ss (reduce-seq) + atoms.ss (deref).
(load "host/chez/natives-xform.ss")
;; vars as first-class objects (jolt-n7rz, Phase 2): var?/var-get/deref/invoke/=/
;; vars as first-class objects: var?/var-get/deref/invoke/=/
;; pr-str over the rt.ss var-cell. After natives-xform.ss (chains deref) + the
;; printers. emit lowers :the-var to (jolt-var ns name).
(load "host/chez/vars.ss")
;; misc scalar natives (jolt-cf1q.3): UUID (random-uuid/parse-uuid/uuid?), format/
;; misc scalar natives: UUID (random-uuid/parse-uuid/uuid?), format/
;; printf, tagged-literal, bigint. After the printers + converters (str/pr-str of
;; a uuid). Overlay names (uuid?/random-uuid/parse-uuid/tagged-literal?) re-asserted
;; in post-prelude.ss.
(load "host/chez/natives-misc.ss")
;; namespaces (jolt-yxqm, Phase 2): the namespace value model — find-ns/ns-name/
;; namespaces: the namespace value model — find-ns/ns-name/
;; all-ns/the-ns/create-ns/in-ns/ns-publics/ns-map/ns-interns/ns-aliases/resolve/
;; find-var/ns-unmap/*ns*, over the var-table + chez-current-ns. Loaded LAST: needs
;; var-cell + var-cell-defined?, jolt-symbol/jolt-hash-map/jolt-assoc, chez-current-ns
;; (multimethods.ss), list->cseq (seq.ss), and the fully-patched printers (vars.ss).
(load "host/chez/ns.ss")
;; dynamic var binding (jolt-2o7x, Phase 2): the per-thread binding stack +
;; dynamic var binding: the per-thread binding stack +
;; push/pop/get-thread-bindings/__thread-bound?/var-set/alter-var-root/__local-var.
;; Chains var-deref (rt.ss) and jolt-var-get (vars.ss) onto the stack, so a `binding`
;; frame is seen by every var read. Loaded LAST: needs the fully-extended var-read
;; paths + jolt-hash-map/pmap-fold/pmap-assoc (collections.ss).
(load "host/chez/dyn-binding.ss")
;; java.lang.String method interop (jolt-nfca, Phase 2): jolt-string-method, the
;; java.lang.String method interop: jolt-string-method, the
;; portable String/CharSequence surface record-method-dispatch falls through to on
;; a string target. After regex.ss (jolt-re-pattern/regex-t-irx) + records.ss
;; (which references jolt-string-method).
(load "host/chez/natives-str.ss")
;; host class statics + constructors (jolt-avt6, Phase 2): host-static-ref/
;; host class statics + constructors: host-static-ref/
;; host-static-call/host-new + the jhost method registry. Loads LAST — it extends
;; record-method-dispatch (records.ss) and reuses natives-str helpers (str-trim,
;; ascii-string-down, re-split, str-split-drop-trailing) + the regex-t accessors.
(load "host/chez/host-static.ss")
;; generic dot-form dispatch (jolt-kuic): field access + map/vector member access
;; generic dot-form dispatch: field access + map/vector member access
;; for the `.` / `.-field` desugar. Loads after host-static.ss so it wraps every
;; record-method-dispatch arm (jhost/number/regex/jrec/string) and falls through.
(load "host/chez/dot-forms.ss")
;; java.io.File + host file I/O (jolt-yyud): path-backed jfile record, slurp/spit/
;; java.io.File + host file I/O: path-backed jfile record, slurp/spit/
;; flush, file-seq dir primitives, clojure.java.io/file. Loads LAST so its jfile
;; arm wraps the fully-built record-method-dispatch and the str/type/instance-check
;; extensions sit over every prior shim.
(load "host/chez/io.ss")
;; #inst values + java.time formatting (jolt-at0a inc X): jinst (RFC3339 ms) +
;; #inst values + java.time formatting: jinst (RFC3339 ms) +
;; DateTimeFormatter/Instant/ZoneId/LocalDateTime/FormatStyle/Locale/Date. Loads
;; LAST — it extends record-method-dispatch / jolt-get / jolt= / jolt-hash /
;; jolt-pr-str / jolt-type / instance-check and uses host-static.ss's registries.
(load "host/chez/inst-time.ss")
;; Chez-side data reader (jolt-r8ku inc Y): read-string / __parse-next /
;; Chez-side data reader: read-string / __parse-next /
;; __read-tagged. Loads after inst-time.ss — __read-tagged reuses its #uuid/#inst
;; constructors, and the reader needs the full value/collection layer above.
(load "host/chez/reader.ss")
;; clojure.math (jolt-22vo): native flonum-math shims def-var!'d into the
;; clojure.math: native flonum-math shims def-var!'d into the
;; clojure.math ns. Self-contained (only def-var! + Chez math), order-independent.
(load "host/chez/math.ss")
;; parity shims (jolt-cf1q.7): native clojure.core fns missing on the zero-Janet
;; spine (hash family / rseq / cat / transient?). After host-table.ss (sorted),
;; parity shims: native clojure.core fns not covered by the overlay
;; (hash family / rseq / cat / transient?). After host-table.ss (sorted),
;; transients.ss, values.ss (jolt-hash), seq.ss.
(load "host/chez/natives-parity.ss")
;; Java-style arrays (jolt-cf1q.7): object/typed array constructors + a jolt-array
;; Java-style arrays: object/typed array constructors + a jolt-array
;; backing; extends count/nth/seq/get/ref-put! so the overlay aget/aset/alength see
;; it. After the dispatchers it chains.
(load "host/chez/natives-array.ss")
;; clojure.lang.PersistentQueue (jolt-b8he): a functional queue + EMPTY static.
;; clojure.lang.PersistentQueue: a functional queue + EMPTY static.
;; Chains seq/count/empty?/peek/pop/conj/sequential?/class/instance?/printer, so
;; load after natives-array (the dispatchers it extends).
(load "host/chez/natives-queue.ss")
;; syntax-quote form builders (jolt-r9lm, inc6b): __sqcat/__sqvec/__sqmap/__sqset/
;; syntax-quote form builders: __sqcat/__sqvec/__sqmap/__sqset/
;; __sq1, def-var!'d into clojure.core. A cross-compiled macro expander (analyzer
;; on Chez, inc6b) calls these to build its expansion as reader forms. Needs the
;; on Chez) calls these to build its expansion as reader forms. Needs the
;; collection/seq layer + def-var!; order-independent past those.
(load "host/chez/syntax-quote.ss")
;; concurrency (jolt-byjr): real OS-thread futures + blocking promises, shared-heap
;; concurrency: real OS-thread futures + blocking promises, shared-heap
;; (JVM) semantics. Loaded LAST — chains the fully-built jolt-deref and conveys the
;; thread-local binding stack (dyn-binding.ss) into workers. pmap/pcalls/pvalues
;; (overlay, over `future`) light up once future-call exists here.
(load "host/chez/concurrency.ss")
;; clojure.core.async (jolt-byjr): real-thread blocking channels + go/go-loop/
;; clojure.core.async: real-thread blocking channels + go/go-loop/
;; thread macros, def-var!'d into clojure.core.async. After concurrency.ss (reuses
;; ms->duration) and the collection/seq layer.
(load "host/chez/async.ss")
;; BigDecimal (jolt-i2jm): the jbigdec value type + bigdec/decimal?/class/equality/
;; BigDecimal: the jbigdec value type + bigdec/decimal?/class/equality/
;; printing. Loads LAST so its set!-wraps of jolt-class/jolt=2/the printers sit
;; outermost over every earlier extension.
(load "host/chez/bigdec.ss")

View file

@ -1,7 +1,7 @@
;; run-corpus.ss — the standing correctness gate, pure Chez. NO Janet.
;; run-corpus.ss — the standing correctness gate.
;;
;; Loads the checked-in seed (host/chez/seed/{prelude,image}.ss) + the zero-Janet
;; spine, reads test/chez/corpus.edn, and for each row evaluates :actual and
;; Loads the checked-in seed (host/chez/seed/{prelude,image}.ss) + the spine,
;; reads test/chez/corpus.edn, and for each row evaluates :actual and
;; :expected through jolt-compile-eval and compares by value-equality (jolt=). The
;; corpus :expected is JVM-sourced (test/conformance/regen-corpus.clj), so this
;; measures jolt-on-Chez against reference Clojure.
@ -170,7 +170,7 @@
(define n-eval (+ pass (length crashes) (length diverged) (length known-hit)))
(define secs (let ((d (time-difference (current-time) t0)))
(+ (time-second d) (/ (time-nanosecond d) 1e9))))
(printf "\nZero-Janet corpus parity: ~a/~a evaluated cases pass (~as)\n"
(printf "\nCorpus parity: ~a/~a evaluated cases pass (~as)\n"
pass n-eval (/ (round (* secs 10)) 10.0))
(printf " crash: ~a NEW divergence: ~a known: ~a (throws skipped: ~a)\n"
(length crashes) (length diverged) (length known-hit) throws)

View file

@ -1,6 +1,6 @@
;; run-sci.ss — SCI conformance: load borkdude/sci's own source (vendor/sci) through
;; joltc and require its forms to compile+eval. A real-world Clojure-compatibility
;; stress test. Pure Chez, no Janet. Floor-gated like the corpus: a regression below
;; stress test. Floor-gated like the corpus: a regression below
;; the floor (or the count today, 205/218) fails. Raise the floor as host gaps close
;; (the tail is genuine gaps — set! on vars, some macro/def shapes).
;;
@ -54,7 +54,7 @@
(define verbose (and (getenv "SCI_VERBOSE") #t))
;; stubs first (host shims SCI's source expects)
(for-each (lambda (f) (load-forms (string-append "src/jolt/clojure/sci/" f) verbose))
(for-each (lambda (f) (load-forms (string-append "stdlib/clojure/sci/" f) verbose))
'("lang_stubs.clj" "io_stubs.clj" "host_stubs.clj"))
(define sci-base "vendor/sci/src/sci/")

View file

@ -1,4 +1,4 @@
;; run-unit.ss — host-specific unit gate, pure Chez. NO Janet.
;; run-unit.ss — host-specific unit gate.
;;
;; Loads the checked-in seed + spine, reads test/chez/unit.edn, and for each case
;; evaluates :expr (wrapped in (do ...), as `joltc -e` does) and compares its PRINTED

View file

@ -1,4 +1,4 @@
;; Phase 1 (jolt-cf1q.2, inc 3b) — the seq tier on the Chez RT.
;; The seq tier on the Chez RT.
;;
;; One lazy-capable node (cseq) models Clojure's list, cons, and lazy seq — all
;; print as (...), all sequential-= to each other AND to vectors. `jolt-seq`
@ -21,13 +21,13 @@
;; list? : #t when this cell is a PersistentList node (list literal / (list ...)
;; / cons / reverse / conj-onto-list) vs a lazy or vector-backed seq cell — the
;; only thing that distinguishes a list from any other realized seq on this host,
;; since one record type backs both (clojure.core/list? — jolt-75sv). The marker
;; since one record type backs both (clojure.core/list?). The marker
;; lives on the cell, so (rest a-list) / (seq a-vector) / (map …) yield plain seq
;; cells and are not list?.
;; cvec/ci: for a vector-backed seq cell, the backing vector and this cell's
;; element index — so it is a real chunked-seq (chunked-seq? true, chunk-first
;; hands out a 32-element block, chunk-rest is the seq at the next block) and
;; reduce iterates the vector directly with no per-element cells (jolt-hs5q).
;; reduce iterates the vector directly with no per-element cells.
;; cvec is #f for every other seq; stored as two fields (not a cons) so a vector
;; seq cell costs no extra allocation. The rest of the seq layer ignores them, so
;; first/rest/count/printing are unchanged.
@ -45,7 +45,7 @@
(define-record-type empty-list-t (fields) (nongenerative empty-list-v1))
(define jolt-empty-list (make-empty-list-t))
;; reduced (jolt-y6mv): a box a reducing fn returns to stop reduce early. The
;; reduced: a box a reducing fn returns to stop reduce early. The
;; reduce machinery below unwraps it; (deref a-reduced) / unreduced also read it.
;; reduced?/reduced are def-var!'d into clojure.core in natives-seq.ss.
(define-record-type jolt-reduced (fields val) (nongenerative jolt-reduced-v1))
@ -213,7 +213,7 @@
(if (if (> step 0.0) (< n end) (> n end))
(cseq-lazy n (lambda () (range-bounded (+ n step) end step)))
jolt-nil))
;; numeric tower (jolt-n6al): exact 0/1 defaults so (range 3) yields exact ints
;; numeric tower: exact 0/1 defaults so (range 3) yields exact ints
;; (= JVM longs); flonum args still produce flonums (Scheme arithmetic preserves).
(define jolt-range
(case-lambda

View file

@ -1,8 +1,8 @@
;; syntax-quote form builders (jolt-r9lm, inc6b). A macro expander whose body was a
;; syntax-quote template (lowered by jolt.host/form-syntax-quote-lower) calls these
;; at RUNTIME on Chez to build the EXPANSION as
;; Chez READER forms (cseq list / pvec / pmap / tagged-set pmap) so the on-Chez
;; analyzer can re-analyze it. def-var!'d into clojure.core, so the lowered body's
;; syntax-quote form builders. A macro expander whose body was a syntax-quote
;; template (lowered by jolt.host/form-syntax-quote-lower) calls these at RUNTIME
;; to build the EXPANSION as READER forms (cseq list / pvec / pmap / tagged-set
;; pmap) so the on-Chez analyzer can re-analyze it. def-var!'d into clojure.core,
;; so the lowered body's
;; unqualified __sqcat/__sqvec/__sqmap/__sqset/__sq1 refs (which lower to var-deref
;; in prelude mode) resolve here.
;;
@ -36,7 +36,7 @@
;; pmap that __sqcat/__sqvec/__sqmap build double as their own form rep — but a set
;; value (pset) differs from the reader's set FORM ({:jolt/type :jolt/set :value
;; <pvec>}), so building the tagged form here would make a runtime `#{~@xs} a map,
;; not a set (jolt-r9lm regression). Build the value; the analyzer's form-set?
;; not a set. Build the value; the analyzer's form-set?
;; (host-contract.ss) additionally recognizes a pset, so a macro template's #{...}
;; expansion still re-analyzes as a set literal.
(define (jolt-sqset . parts) (apply jolt-hash-set (sq-flatten parts)))

View file

@ -1,13 +1,12 @@
;; transients (jolt-kl2l) — mutable backing per collection kind, snapshotted to
;; the immutable collection on persistent!. A faithful port of the Janet host's
;; mutable transients: conj!/assoc!/dissoc!/disj!/pop! mutate in place (amortized
;; O(1)); persistent! converts back to a pvec / pmap / pset once.
;; transients — mutable backing per collection kind, snapshotted to the immutable
;; collection on persistent!. conj!/assoc!/dissoc!/disj!/pop! mutate in place
;; (amortized O(1)); persistent! converts back to a pvec / pmap / pset once.
;;
;; vec : a growable Scheme vector (capacity) + a fill count `n`. conj!/pop! are
;; O(1) amortized — the old copy-on-write rebuilt the whole vector per op,
;; so building an N-vector was O(N^2).
;; map : a Chez hashtable keyed by key-hash / jolt= (value-equality, nil-safe —
;; a jolt-nil key stores fine here, unlike a Janet table).
;; a jolt-nil key stores fine here).
;; set : a Chez hashtable of elements.
;; cow : fallback for anything else (e.g. a sorted coll) — copy-on-write over
;; the persistent ops, preserving jolt's superset of Clojure's transients.

View file

@ -1,16 +1,16 @@
;; Jolt value model on Chez Scheme — Phase 0a (jolt-cf1q.1).
;; Jolt value model on Chez Scheme.
;;
;; The irreducible value layer the self-hosted RT rests on. Maps Clojure's value
;; types onto Chez natives where possible, and adds records only where Chez lacks
;; a distinct type (nil sentinel, keywords, ns-bearing symbols). Loaded into an
;; env that has already (import (chezscheme)); becomes a real library in Phase 1.
;; env that has already (import (chezscheme)).
;;
;; Design notes:
;; - nil is a UNIQUE sentinel, distinct from #f and '() (the classic Lisp-on-Lisp
;; trap). jolt false -> Chez #f, jolt true -> #t.
;; - Chez's numeric tower IS Clojure's: long->exact integer, double->flonum,
;; ratio->exact rational, bigint->bignum. A windfall vs Janet (ratios/bignums
;; for free). Clojure `=` is exactness-aware: (= 1 1.0) is FALSE.
;; ratio->exact rational, bigint->bignum. Clojure `=` is exactness-aware:
;; (= 1 1.0) is FALSE.
;; --- nil ---------------------------------------------------------------------
(define-record-type jolt-nil-t (fields) (nongenerative jolt-nil-v1))
@ -42,7 +42,7 @@
;; chars/strings: Chez natives (strings treated immutable).
;; --- jolt equality (Clojure =) — scalars; collections land in Phase 2 --------
;; --- jolt equality (Clojure =) — scalars + collections ----------------------
(define (jolt=2 a b)
(cond
((and (jolt-nil? a) (jolt-nil? b)) #t)
@ -69,7 +69,7 @@
((jolt=2 a (car rest)) (loop (car rest) (cdr rest)))
(else #f))))
;; --- jolt hash — consistent with jolt= (for the HAMT in 0c / Phase 2) ---------
;; --- jolt hash — consistent with jolt= (for the HAMT) -----------------------
(define (jolt-hash x)
(cond
((jolt-nil? x) 0)

View file

@ -1,4 +1,4 @@
;; vars as first-class objects (jolt-cf1q.3, jolt-n7rz) — (var x) / #'x.
;; vars as first-class objects — (var x) / #'x.
;;
;; The emitter lowers :the-var to (jolt-var ns name) — the rt.ss var-cell, which
;; is now also a Clojure VAR value. var? / var-get / deref-of-var / var-as-IFn /
@ -6,8 +6,8 @@
;; in post-prelude.ss (the overlay reads (get v :root), nil on a record).
;;
;; Dynamic binding (binding / with-bindings* / var-set / thread-bound? /
;; with-redefs) is a separate follow-up — those crash on nil host primitives,
;; which is safe (a crash stays a crash, not a divergence).
;; with-redefs) lives in dyn-binding.ss, which chains the var-read paths set up
;; here.
;;
;; Loaded LAST (after natives-xform.ss): chains jolt-deref (atom/volatile arms)
;; and the printers.

View file

@ -25,7 +25,7 @@
(throw (str "zero? requires a number, got: " x)))))
;; pos? checks number? explicitly: this tier is recompiled by the staged pass,
;; where a bare (> x 0) emits the native janet op that happily orders strings
;; where a bare (> x 0) emits the native op that happily orders strings
;; (the documented native-ops relaxation) — the guard keeps Clojure's throw.
(def pos?
(fn* pos? [x]
@ -111,7 +111,7 @@
;; of 00-syntax has loaded, so using them here is fine.
(defmacro ns [nm & clauses]
;; ^{:map} metadata on the ns name reads as a (with-meta sym {...}) form, not an
;; annotated symbol (jolt-8w2). Real libraries put :author/:doc there
;; annotated symbol. Real libraries put :author/:doc there
;; (clojure.tools.logging), so unwrap to the bare symbol; jolt does not track
;; namespace metadata, so the map is dropped.
(let [nm (if (and (seq? nm) (= 'with-meta (first nm))) (second nm) nm)
@ -152,7 +152,7 @@
;; Forward declaration interns unbound vars (Clojure semantics). The interpreter
;; resolves forward refs lazily either way, but the COMPILER classifies globals at
;; compile time: without the var, a declared name that collides with a Janet root
;; compile time: without the var, a declared name that collides with a host root
;; binding (parse, hash, …) would compile to the host fn instead of the var.
(defmacro declare [& syms]
`(do ~@(map (fn* [s] `(def ~s)) syms)))
@ -309,8 +309,8 @@
(if (seq ps)
(if (symbol? (first ps))
(go (next ps) (conj nps (first ps)) lets)
;; bare (gensym) here is Janet's (a Janet symbol the destructurer
;; rejects); round-trip through str for a jolt symbol.
;; a bare (gensym) returns a host symbol the destructurer rejects;
;; round-trip through str for a jolt symbol.
(let [g (symbol (str (gensym)))]
(go (next ps) (conj nps g) (conj (conj lets (first ps)) g))))
[nps lets]))
@ -348,18 +348,18 @@
body (if (and (seq body) (map? (first body)) (not (symbol? (first body))))
(rest body) body)
;; ^{:map} metadata on the name reads as a (with-meta sym …) form, not an
;; annotated symbol (jolt-8w2). def attaches the metadata, but fn needs a
;; annotated symbol. def attaches the metadata, but fn needs a
;; bare symbol, so unwrap it for the fn name.
fn-only-name (if (symbol? fn-name) fn-name (first (rest fn-name)))]
;; pass the name through to fn: the compiled fn's janet name carries it,
;; so stack traces read app.deep/level3 instead of a gensym (jolt-2o7.1)
;; pass the name through to fn: the compiled fn's host name carries it,
;; so stack traces read app.deep/level3 instead of a gensym
`(def ~fn-name (fn ~fn-only-name ~@body))))
;; Jolt doesn't enforce privacy, so defn- is just defn (matching how Clojure's own
;; defn- delegates to defn with :private metadata).
(defmacro defn- [fn-name & body] `(defn ~fn-name ~@body))
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a Janet symbol
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a host symbol
;; the destructurer rejects). This defn compiles fine: by the time a tier triggers
;; the analyzer build the kernel is in place (the build is gated until then).
(defn- fresh-sym [] (symbol (str (gensym))))
@ -422,9 +422,9 @@
;; Per binding group: :when wraps the inner form in (if test (list inner) []) so
;; mapcat drops it when false; :let wraps it in a let*; :while wraps the coll in
;; take-while. The last group with no modifiers is a plain map (no flatten needed).
;; Faithful port of the prior Janet macro (single body expr). The body uses only
;; kernel/seed fns so it runs at analyzer-build time. `fn` (not fn*) carries the
;; binding so destructuring forms work.
;; Single body expr. The body uses only kernel/seed fns so it runs at
;; analyzer-build time. `fn` (not fn*) carries the binding so destructuring forms
;; work.
(defmacro for [bindings body]
(let [scan (fn scan [bvec i bind coll mods]
(if (and (< i (count bvec)) (keyword? (nth bvec i)))
@ -471,9 +471,9 @@
(build 0 (parse-groups bindings 0 []))
body)))
;; doseq runs body for side effects across the bindings, returning nil. Same
;; shortcut as the prior Janet macro: realize a `for` comprehension with count
;; (for handles :when/:let/:while and multiple bindings).
;; doseq runs body for side effects across the bindings, returning nil. Realizes
;; a `for` comprehension with count (for handles :when/:let/:while and multiple
;; bindings).
(defmacro doseq [bindings & body]
`(do (count (for ~bindings (do ~@body nil))) nil))
@ -489,7 +489,7 @@
;; lazy-seq / lazy-cat live here (not 30-macros) because the seq/coll tiers use
;; them and compile-as-they-load: the macro must be registered before those tiers
;; or (lazy-seq …) compiles to a call of the macro-as-function and leaks its
;; expansion at runtime (jolt-r81). They use only seed fns (make-lazy-seq/
;; expansion at runtime. They use only seed fns (make-lazy-seq/
;; coll->cells/concat) + map, all available from the start.
;; lazy-seq defers its body: make-lazy-seq holds a thunk that realizes the body
;; to cells when forced. lazy-cat wraps each coll in a lazy-seq and concats.

View file

@ -3,14 +3,14 @@
;; mode these self-host through the now-built analyzer (interpreted otherwise).
;;
;; Migration rule for adding fns here: the fn must (1) NOT be in
;; compiler/core-renames (that map emits core-X Janet symbols directly), (2) have
;; no internal Janet callers of its core-X binding, and (3) NOT be used by the
;; compiler/core-renames (that map emits core-X symbols directly), (2) have
;; no internal callers of its core-X binding, and (3) NOT be used by the
;; self-hosted compiler (jolt-core/jolt/*.clj). Compiler-facing structural fns go
;; in the kernel tier (00-kernel) instead — see its header.
;; Volatiles (moved up from 20-coll: this tier's transducers use them, and the
;; analyzer now ERRORS on unresolved forward references — jolt-2o7.3). The
;; constructor (volatile!) stays native; these are pure over ref-put!/get.
;; Volatiles (this tier's transducers use them, and the analyzer ERRORS on
;; unresolved forward references). The constructor (volatile!) stays native;
;; these are pure over ref-put!/get.
(defn vreset! [vol newval]
(jolt.host/ref-put! vol :val newval) newval)
(defn vswap! [vol f & args]
@ -21,7 +21,7 @@
(defn fnext [coll] (first (next coll)))
(defn nnext [coll] (next (next coll)))
;; Canonical Clojure defs: pure first/next/loop/recur, no Janet realize-for-iteration.
;; Canonical Clojure defs: pure first/next/loop/recur.
(defn last [s]
(if (next s) (recur (next s)) (first s)))

View file

@ -4,7 +4,7 @@
;; redefinable. Loaded after the seq tier; self-hosted in compile mode.
;;
;; Same migration rule as the seq tier (see 10-seq.clj): not in core-renames, no
;; internal Janet callers, not used by the self-hosted compiler.
;; internal callers, not used by the self-hosted compiler.
;; Tiny leaves first — fns below in this tier (and 25-sorted) use them.
(defn some? [x] (not (nil? x)))
@ -20,7 +20,7 @@
;; overlay versions cost an extra call layer per element (seq-pipe bench 4x).
;; Variadic bit ops — canonical Clojure arities folding the binary host op
;; (__bit-* seams). 2-arg call sites still compile to the native janet op via
;; (__bit-* seams). 2-arg call sites still compile to the native op via
;; the backend's native-ops table, so the binary fast path is unchanged.
(defn bit-and
([x y] (__bit-and x y))
@ -87,8 +87,8 @@
;; Distinct keys are recorded in a side vector so the buckets can be frozen in
;; place (no second map rebuild). A bucket's FIRST element is stored as a cheap
;; persistent [x]; only the second element promotes it to a transient — so an
;; all-singletons grouping pays no transient alloc and matches the old cost,
;; while any bucket that actually grows rides the O(1) push.
;; all-singletons grouping pays no transient alloc, while any bucket that
;; actually grows rides the O(1) push.
(defn group-by [f coll]
(let [tm (transient {})
ks (reduce (fn [ks x]
@ -355,7 +355,7 @@
(make-hierarchy) (partition 2 deriv-seq))
h))))
;; --- Stage 3 tier shrink: pure-over-core leaves moved off the host primitives ----
;; --- pure-over-core leaves expressed off the host primitives -----------------
;; Representation predicates over the overlay's own predicates.
(defn sequential? [x] (or (vector? x) (seq? x)))
@ -364,7 +364,7 @@
(or (vector? x) (map? x) (set? x) (list? x) (string? x)))
(defn indexed? [x] (vector? x))
;; sorted? is defined by the next tier (25-sorted) — declared here so this
;; tier compiles (forward references are analysis errors now, jolt-2o7.3).
;; tier compiles (forward references are analysis errors).
(declare sorted?)
(defn reversible? [x] (or (vector? x) (sorted? x)))
@ -377,7 +377,7 @@
(defn infinite? [x] (and (number? x) (or (= x ##Inf) (= x ##-Inf))))
;; qualified-/simple- keyword?/symbol? moved above qualified-ident? (forward
;; references are analysis errors now — jolt-2o7.3).
;; references are analysis errors).
;; realized?: defined on the pending types only (delay/lazy-seq/future read
@ -453,7 +453,7 @@
(defn println-str [& xs] (__with-out-str (fn* [] (apply println xs))))
(defn prn-str [& xs] (__with-out-str (fn* [] (apply prn xs))))
;; --- Phase 2 leaf batch 4 (jolt-ded): over the rand / sort host seams --------
;; --- leaves over the rand / sort host seams ----------------------------------
;; Canonical truncation toward zero via int (the kernel fn floored, which is
;; wrong for a negative n).
@ -549,11 +549,11 @@
;; file-seq: the tree of paths under root (root included), directories walked
;; via the host dir primitives. Paths (strings), not File objects. (Lives below
;; tree-seq: forward references are analysis errors now — jolt-2o7.3.)
;; tree-seq: forward references are analysis errors.)
(defn file-seq [root]
(if (__file? root)
;; java.io.File tree: walk via the File method surface so leaves are File
;; values callers can invoke .isFile/.getName/slurp on (jolt-hjw).
;; values callers can invoke .isFile/.getName/slurp on.
(tree-seq (fn [f] (.isDirectory f)) (fn [f] (seq (.listFiles f))) root)
(tree-seq __dir? __list-dir root)))
@ -587,10 +587,6 @@
;; No ratio type on Jolt, so rationalize is identity.
(defn rationalize [x] x)
;; trampoline: repeatedly calls f with args until a non-function result.
;; rand-int: random integer in [0, n). Uses Janet math/random.
;; 0-arg: a stateful transducer (tracks [seen? prev] in a volatile, so no sentinel
;; value is needed). 1-arg: eager dedupe of consecutive equal elements.
(defn dedupe
@ -625,8 +621,7 @@
;; canonical Clojure 1.11 shape (core.clj seq-to-map-for-destructuring):
;; even pairs build a map (later keys win, as createAsIfByAssoc), a SINGLE
;; element is returned as-is (the trailing-map calling convention), and an
;; unpaired key past pairs throws. The old jolt version silently dropped the
;; trailing element, losing (f {:b 2}) kwargs calls.
;; unpaired key past pairs throws.
(defn seq-to-map-for-destructuring [s]
(if (next s)
(loop [m {} xs (seq s)]
@ -637,8 +632,8 @@
m))
(if (seq s) (first s) {})))
;; Phase 4 (jolt-1j0): host-coupled fns that are pure logic over existing core
;; primitives, so they need no new jolt.host surface.
;; Host-coupled fns that are pure logic over existing core primitives, so they
;; need no new jolt.host surface.
;; vary-meta: f applied to obj's metadata (+ extra args), reattached. meta and
;; with-meta are the irreducible host primitives; vary-meta is just their compose.
@ -650,9 +645,8 @@
(apply str (map (fn [c] (if (= c \-) \_ c)) (seq (str s)))))
;; reduce-kv over a map (k v) or vector (index v). Both branches go through reduce,
;; so reduced short-circuits — and the vector path indexes correctly. (The prior
;; Janet version saw a pvec as a table and folded over its internal keys; it also
;; ignored reduced.) nil folds to init, matching Clojure.
;; so reduced short-circuits — and the vector path indexes correctly. nil folds
;; to init, matching Clojure.
(defn reduce-kv [f init coll]
(cond
(vector? coll) (reduce (fn [acc i] (f acc i (nth coll i))) init (range (count coll)))
@ -660,7 +654,7 @@
(nil? coll) init
:else (throw (str "reduce-kv not supported on: " coll))))
;; ex-info accessors. The Janet constructor (ex-info) stays — it builds the tagged
;; ex-info accessors. The constructor (ex-info) stays native — it builds the tagged
;; value and wires into throw — but the value exposes :jolt/type/:message/:data/
;; :cause via get, so the accessors are pure over get. A thrown non-ex-info arrives
;; wrapped as {:jolt/type :jolt/exception :value v}; unwrap that first.
@ -724,7 +718,7 @@
(when (< i (count vars))
(var-set (nth vars i) (nth saved i))
(recur (inc i))))))))
;; Jolt has no chunked seqs (Phase 5 territory), so this is always false.
;; Jolt has no chunked seqs, so this is always false.
(defn chunked-seq? [x] false)
;; Atom peripheral operations. atom/swap!/reset!/deref stay native — the compiler
@ -733,7 +727,7 @@
;; slot; add-watch/remove-watch/set-validator! mutate the atom (or its watches
;; sub-table) through the one host primitive jolt.host/ref-put! — the minimal
;; mutation kernel the overlay can't express over core fns (a nil value removes the
;; key). compare-and-set! compares by value, matching the prior Janet behavior.
;; key). compare-and-set! compares by value.
(defn swap-vals! [a f & args]
(let [old (deref a)] [old (apply swap! a f args)]))
(defn reset-vals! [a newval]
@ -777,7 +771,7 @@
(jolt.host/ref-put! target (nth idxs+val (- n 2)) val)
val))
;; --- Phase 2 leaf batch (jolt-ded): fn combinators + host-free stubs ---------
;; --- fn combinators + host-free stubs ----------------------------------------
(defn complement
"Takes a fn f and returns a fn that takes the same arguments as f, has the
@ -785,8 +779,7 @@
[f]
(fn [& args] (not (apply f args))))
;; Canonical Clojure fnil: patches only the FIRST 1-3 arguments (the old Janet
;; kernel patched every position it had a default for, which Clojure does not).
;; Canonical Clojure fnil: patches only the FIRST 1-3 arguments.
(defn fnil
([f x]
(fn [a & args] (apply f (if (nil? a) x a) args)))
@ -818,7 +811,7 @@
(let [t (:test (meta v))]
(if t (do (t) :ok) :no-test)))
;; --- Phase 2 leaf batch 2 (jolt-ded): canonical Clojure ports ----------------
;; --- canonical Clojure ports -------------------------------------------------
;; key/val/find first — merge-with and memoize below use them.
;; Strict, as in Clojure: an entry is what (seq m) yields (a host tuple), NOT
@ -882,8 +875,7 @@
(recur nxt (next ks))))
m)))))
;; find-based, so nil RESULTS are cached too (the old kernel fn re-computed
;; them); args canonicalize as a collection key.
;; find-based, so nil RESULTS are cached too; args canonicalize as a collection key.
(defn memoize [f]
(let [mem (atom (hash-map))]
(fn [& args]
@ -920,11 +912,9 @@
(defn reverse [coll] (reduce conj (list) coll))
;; --- Phase 2 leaf batch 3 (jolt-ded) -----------------------------------------
;; An empty coll of the same category; sorted colls keep their comparator (the
;; value's own :empty op). Strings and scalars are nil, as in Clojure; a lazy
;; seq empties to () (the old kernel fn returned a host table for it).
;; seq empties to ().
(defn empty [coll]
(cond
(nil? coll) nil
@ -948,8 +938,6 @@
(assoc m k (apply f (get m k) args)))))]
(up m ks f args)))
;; --- jolt-brh: the last missing-portable vars --------------------------------
;; jolt keywords have no intern table (any keyword "exists"), so find-keyword
;; always finds — babashka makes the same call.
(defn find-keyword
@ -962,7 +950,7 @@
;; Canonical comp — here rather than a host primitive so each stage is invoked with
;; jolt call semantics: (comp seq :content) works because the keyword stage
;; goes through IFn dispatch (raw Janet keyword application does not).
;; goes through IFn dispatch.
(defn comp
([] identity)
([f] f)
@ -977,17 +965,15 @@
([x y z & args] (f (apply g x y z args)))))
([f g & fs] (reduce comp (comp f g) fs)))
;; Canonical IFn set (jolt-1vx): fns, keywords, symbols, maps (sorted incl.),
;; Canonical IFn set: fns, keywords, symbols, maps (sorted incl.),
;; sets, vectors, and vars — NOT lists ((ifn? '(1 2)) is false in Clojure).
;; Mutable-mode caveat: vectors and lists share the array representation
;; there, so vector? can't separate them and lists read as ifn?.
(defn ifn? [x]
(or (fn? x) (keyword? x) (symbol? x) (map? x) (set? x) (vector? x) (var? x)))
;; Auto-promoting (') and unchecked arithmetic. Jolt numbers don't overflow,
;; so all of these are the checked ops; fixed arities mirror Clojure's
;; signatures. unchecked-divide-int goes through quot, so dividing by zero
;; throws as on the JVM (the old seed fn silently truncated infinity).
;; throws as on the JVM.
(def +' +)
(def -' -)
(def *' *)
@ -1079,7 +1065,7 @@
(defn unchecked-float [x] (double x))
(defn unchecked-double [x] (double x))
;; --- transduce / into / eduction (seed-shrink round 5) ---------------------
;; --- transduce / into / eduction ---------------------------------------------
;; Canonical transduce: build the stacked rf once, reduce (which honors
;; `reduced` and steps lazy seqs incrementally), then run the completion arity.
(defn transduce
@ -1089,9 +1075,9 @@
(xf (reduce xf init coll)))))
;; into stays a host primitive: it's perf-wall hot (the into-vec bench pays ~11%
;; through the overlay call layers — same lesson as even?/odd? in round 4).
;; through the overlay call layers — same lesson as even?/odd?).
;; eduction is EAGER on jolt (documented divergence, as before): the composed
;; eduction is EAGER on jolt (documented divergence): the composed
;; xforms applied to coll, realized into a vector.
(defn eduction [& args]
(let [coll (last args)
@ -1102,7 +1088,7 @@
(defn ->Eduction [xform coll] (into [] xform coll))
;; --- JVM-shape stubs and trivial shells (seed-shrink batch 2) --------------
;; --- JVM-shape stubs and trivial shells --------------------------------------
;; Pure compositions or documented jolt stubs; the host keeps nothing.
(defn enumeration-seq [e] (seq e))
(defn iterator-seq [i] (seq i))

View file

@ -1,4 +1,4 @@
;; clojure.core — sorted collections tier (stage 3, jolt-0lj).
;; clojure.core — sorted collections tier.
;;
;; A sorted-map / sorted-set is a tagged host table
;; {:jolt/type :jolt/sorted-map|:jolt/sorted-set
@ -10,8 +10,7 @@
;; The tree is a left-leaning-free red-black tree — Rich Hickey's algorithm,
;; ported from the ClojureScript PersistentTreeMap (cljs.core: tree-map-add /
;; balance-left / balance-right / tree-map-append / balance-*-del). assoc / get /
;; dissoc / contains are O(log n); the old sorted-VECTOR rep was O(n) per assoc,
;; O(n^2) to build (jolt-684u's sibling, jolt-0hbr). cljs uses BlackNode/RedNode
;; dissoc / contains are O(log n). cljs uses BlackNode/RedNode
;; deftypes, but this tier loads before 30-macros (no deftype), so a node is a
;; plain vector [color k v left right] (color :red/:black; left/right node|nil)
;; and the methods become functions — the algorithm is identical.

View file

@ -1,6 +1,6 @@
;; clojure.core — macro tier. Macros expressed in Clojure (defmacro + syntax-quote)
;; rather than as hand-built Janet form-transformers. Loaded after the fn tiers,
;; so a macro here may use any already-frozen core fn/macro.
;; clojure.core — macro tier. Macros expressed in Clojure (defmacro + syntax-quote).
;; Loaded after the fn tiers, so a macro here may use any already-frozen core
;; fn/macro.
;;
;; IMPORTANT — only macros NOT used by the self-hosted compiler (jolt-core/jolt/*)
;; or by the earlier overlay tiers belong here; those (and/or/when/when-not/
@ -34,7 +34,7 @@
(defmacro defmethod [mm dispatch-val & fn-tail]
`(defmethod-setup (quote ~mm) ~dispatch-val (fn ~@fn-tail)))
;; Multimethod table ops (tier 6c): a multimethod's method table lives on its
;; Multimethod table ops: a multimethod's method table lives on its
;; VAR (the value is just the dispatch closure), so these pass the name quoted
;; to ctx-capturing setups — the same shape as defmulti/defmethod above.
(defmacro prefer-method [mm dval-a dval-b]
@ -48,7 +48,7 @@
;; methods/get-method take the multimethod VALUE (Clojure semantics); the setup
;; maps it back to its var via the registry, so a bare multifn ref works from a
;; compiled fn in any namespace (jolt multimethod table-visibility fix).
;; compiled fn in any namespace.
(defmacro get-method [mm dval]
`(get-method-setup ~mm ~dval))
@ -75,7 +75,7 @@
`(do ~x ~@body))
;; defonce: define name only if it isn't already bound to a non-nil root;
;; returns the existing var untouched otherwise (matching the prior arm).
;; returns the existing var untouched otherwise.
;; time: evaluate expr, print the elapsed wall-clock, return the value.
;; current-time-ms is the host's monotonic clock.
(defmacro time [expr]
@ -164,14 +164,12 @@
(loop [~i 0]
(when (< ~i n#) ~@body (recur (inc ~i)))))))
;; A fresh jolt symbol inside a macro body: (gensym) here resolves to Janet's
;; builtin (a Janet symbol the destructurer rejects), so round-trip through str.
;; A fresh jolt symbol inside a macro body: a bare (gensym) returns a host symbol
;; the destructurer rejects, so round-trip through str.
(defn- fresh-sym [] (symbol (str (gensym))))
;; Lazy-safe: take only the head via first (Clojure uses (seq coll), but Jolt's
;; eager seq would realize an infinite coll like (repeat nil) and hang). Matches
;; the prior Janet behavior; the nil/false-head distinction waits on Phase 5
;; laziness.
;; eager seq would realize an infinite coll like (repeat nil) and hang).
(defmacro when-first [bindings & body]
(let [x (bindings 0) coll (bindings 1)]
`(when-let [~x (first ~coll)] ~@body)))
@ -289,7 +287,7 @@
;; a seq of field keywords; spliced into a vector LITERAL below ([~@…]) so
;; the analyzer sees a vector form, not a runtime pvec value.
field-kws (map (fn [f] (keyword (name f))) fields)
;; per-field TYPE HINT (jolt-3ko): ^Vec3 origin -> "Vec3" (a record type
;; per-field TYPE HINT: ^Vec3 origin -> "Vec3" (a record type
;; name), ^:num x -> "num", else nil. Lets the inference know a field's
;; exact type up front, so reading it back carries that type (not :any) —
;; the key to fast nested-record code. Spliced as a vector literal too.
@ -298,7 +296,7 @@
(and mt (:num mt)) "num"
:else nil)))
fields)
;; per-field MUTABILITY (jolt-c3q): ^:unsynchronized-mutable / ^:volatile-
;; per-field MUTABILITY: ^:unsynchronized-mutable / ^:volatile-
;; mutable marks a field set!-able. A type with any mutable field opts out
;; of the immutable shape-rec layout and uses the mutable table form, so
;; set! can mutate it (the ctor reads this vector). Spliced as a literal.
@ -309,7 +307,7 @@
fields)
;; mutable field symbols (^:unsynchronized-mutable / ^:volatile-mutable):
;; (set! field v) in a method body lowers to (set! (.-field inst) v), the
;; in-place field write the analyzer compiles to jolt-set-field! (jolt-c3q).
;; in-place field write the analyzer compiles to jolt-set-field!.
mutable-syms (map first (filter second (map vector fields field-muts)))
mutable? (fn [s] (boolean (some (fn [m] (= m s)) mutable-syms)))
rewrite-set (fn rw [inst form]
@ -359,7 +357,7 @@
{} sigs)]
`(do
(def ~pname (make-protocol ~(name pname) ~methods))
;; register method var-keys for devirtualization (jolt-41m); the inference
;; register method var-keys for devirtualization; the inference
;; reads this (via infer-unit!) to resolve a protocol call on a known record
(register-protocol-methods! ~(name pname) [~@(map (fn [s] (name (first s))) sigs)])
~@(map (fn [sig]
@ -431,8 +429,7 @@
`(do ~@(map (fn [g] `(extend-type ~(first g) ~psym ~@(rest g)))
(group-by-head type-impls))))
;; extend (the fn form) is not supported — stub to nil, as before.
;; extend is a real FUNCTION now — defined above extend-type.
;; extend is a real FUNCTION — defined above extend-type.
;; JVM proxies are unsupported.
(defmacro proxy [& args] nil)
;; definterface is JVM-only; bind the name to a marker and return the name (not a
@ -474,7 +471,7 @@
(let [argv (nth spec 1)
inst (first argv)
;; hint `this` with the record type so the inference
;; types it (jolt-3ko) and its field reads bare-index
;; types it and its field reads bare-index
;; instead of going through the runtime tag guard.
hinted (assoc argv 0 (vary-meta inst assoc :tag (name name-sym)))
binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields))]
@ -492,8 +489,8 @@
;; lazy-seq / lazy-cat moved to the 00-syntax tier: the seq/coll tiers (10-seq,
;; 20-coll) use lazy-seq, and in compile mode a tier's forms are compiled as it
;; loads — so the macro must be registered BEFORE those tiers, else (lazy-seq …)
;; compiles as a call to the macro-as-function and leaks its expansion at runtime
;; (jolt-r81). They only need seed fns (make-lazy-seq/coll->cells/concat).
;; compiles as a call to the macro-as-function and leaks its expansion at runtime.
;; They only need seed fns (make-lazy-seq/coll->cells/concat).
;; memfn: a fn wrapping a method call, (memfn toUpperCase) => #(.toUpperCase %).
;; The method symbol is rewritten to jolt's .method call sugar; extra arg names

View file

@ -105,7 +105,7 @@
;; --- partition-all --- (transducer + [n coll] + [n step coll])
;; The collection arities realize EXACTLY n per chunk via a first/rest loop and
;; continue from the advanced cursor (not a re-drop / nthrest), so they realize
;; minimally — the §6.3 laziness counters depend on this.
;; minimally — the laziness counters depend on this.
;; (A take/nthrest form is correct but over-realizes.)
(defn partition-all
([n]
@ -139,7 +139,7 @@
(cons (take n s) (go (nthrest s step))))))]
(go coll))))
;; --- Phase 2 leaf batch 3 (jolt-ded): canonical lazy + transducer arities ----
;; --- canonical lazy + transducer arities -------------------------------------
(defn interpose
([sep]
@ -176,7 +176,7 @@
(when-let [s (seq coll)]
(cons (first s) (take-nth n (drop n s)))))))
;; --- pmap family (jolt-oeu): parallel map over real-thread futures ----------
;; --- pmap family: parallel map over real-thread futures ----------------------
;; Each element's work runs on its own OS thread with SNAPSHOT semantics
;; (futures marshal captured state — pure fns only, mutations don't propagate
;; back). All futures are spawned up front (doall), then derefed in order:

View file

@ -1,4 +1,4 @@
;; clojure.core — IO tier: the *in* reader family (jolt-0d9).
;; clojure.core — IO tier: the *in* reader family.
;;
;; *in* is a dynamic var holding a READER: a plain map whose two ops close
;; over their source — :read-line-fn (next line, newline
@ -135,7 +135,7 @@
(when line
(cons line (line-seq rdr)))))))
;; --- print-method (jolt-g1r) ------------------------------------------------
;; --- print-method ------------------------------------------------
;; Canonical dispatch (clojure/core.clj 3693): the :type metadata when it's a
;; keyword, else the value's type. On jolt, type is the keyword tag for
;; builtins and the deftype name SYMBOL for records — so a record method is

View file

@ -1,8 +1,8 @@
(ns jolt.analyzer
"Portable Clojure analyzer: reader form -> host-neutral IR (see jolt.ir).
Pure jolt-core depends only on the host contract (jolt.host) and IR
constructors (jolt.ir), never on Janet. The contract fns are referred unqualified
Depends only on the host contract (jolt.host) and IR
constructors (jolt.ir). The contract fns are referred unqualified
(host form predicates are `form-*` to avoid colliding with clojure.core), so the
bootstrap can compile this namespace via its plain :var path. ctx is an opaque
host handle threaded to the contract fns; the analyzer never inspects it.
@ -50,7 +50,7 @@
(defn- add-locals [env names] (update env :locals #(reduce conj % names)))
(defn- with-recur [env name] (assoc env :recur name))
;; Type hints (jolt-94n). The reader keeps ^hint metadata on the binding symbol.
;; Type hints. The reader keeps ^hint metadata on the binding symbol.
;; Two hints resolve to the :struct fast path (a constant-keyword lookup skips
;; the :jolt/type guard and emits a bare get): ^:struct (a plain struct/record
;; map) and ^TypeName where TypeName is a defrecord/deftype (its instances are
@ -138,7 +138,7 @@
;; param an ordinary positional slot (holding the collected seq), so recur
;; is a self-call carrying the rest seq directly — Clojure semantics.
;; The recur target doubles as the COMPILED FN'S NAME, which is what a
;; janet stack trace shows — so carry the Clojure ns/fn-name (jolt-2o7.1):
;; host stack trace shows — so carry the Clojure ns/fn-name:
;; an error inside app.deep/level3 traces as _r$app.deep/level3--N
;; (report-error demangles the _r$/--N wrapper). gen-name's counter
;; keeps recur targets unique per compilation unit.
@ -215,7 +215,7 @@
;; the arity :rest key above). Assoc'ing them nil-when-absent would give the
;; node a nil-valued key, which makes it a phm in jolt's map representation
;; and forces the back end to densify it (norm-node) before reading :op — the
;; map-nil-representation trap Phase 2 cleaned up for def/fn/arity nodes. The
;; map-nil-representation trap, also avoided for def/fn/arity nodes. The
;; back end reads each key with a nil-safe (node :k) and gates on it, so an
;; absent key is indistinguishable from a present-nil one.
(let [n {:op :try :body (analyze-seq ctx @body env)}
@ -282,13 +282,13 @@
(let [nm (form-sym-name name-sym)
cur (compile-ns ctx)
;; (def name docstring value): docstring is form 2, value form 3.
;; Matches the interpreter; without this the docstring was taken
;; as the value and the real init dropped (jolt-6ym).
;; Matches the interpreter; otherwise the docstring is taken as
;; the value and the real init dropped.
has-doc (and (> (count items) 3) (string? (nth items 2)))
val-form (nth items (if has-doc 3 2))
base0 (or (form-sym-meta name-sym) {})
;; resolve a ^Type hint to its canonical class name at def
;; time (jolt-a1ir), as the JVM compiler does: ^String ->
;; time, as the JVM compiler does: ^String ->
;; java.lang.String. A record/unknown hint is left untouched.
tag (get base0 :tag)
tag-name (cond (form-sym? tag) (form-sym-name tag)
@ -365,7 +365,7 @@
:else (uncompilable "set! of an unsupported target")))
(uncompilable (str "special form " op))))
;; Host interop method call (jolt-0kf5). `(.method target arg*)` — a head that
;; Host interop method call. `(.method target arg*)` — a head that
;; starts with "." but not ".-" (field access stays punted). Analyzes to a
;; :host-call node; the Chez back end lowers it to a jolt-host-call dispatch.
(defn- method-head? [nm]
@ -391,11 +391,11 @@
;; `(Class. args*)` and `(new Class args*)` -> a :host-new node carrying the class
;; token and the analyzed args. The Chez back end lowers it to a runtime
;; constructor dispatch (jolt-avt6).
;; constructor dispatch.
(defn- analyze-ctor [ctx class args env]
(host-new class (mapv #(analyze ctx % env) args)))
;; jolt.ffi/__cfn (jolt-ffi): the low-level foreign-function form a jolt library
;; jolt.ffi/__cfn: the low-level foreign-function form a jolt library
;; uses (via the jolt.ffi/foreign-fn macro) to bind native code. Shape:
;; (jolt.ffi/__cfn "c_symbol" [:argtype ...] :rettype) ; non-blocking
;; (jolt.ffi/__cfn "c_symbol" [:argtype ...] :rettype :blocking) ; may block
@ -417,8 +417,7 @@
;; A symbol member whose name starts with "-" is a field read; otherwise it is a
;; method (call with the trailing args). Both lower to a :host-call carrying the
;; member name verbatim (the leading "-" survives so the runtime dispatcher reads
;; it as a field). The Chez back end dispatches it through record-method-dispatch
;; (jolt-kuic).
;; it as a field). The Chez back end dispatches it through record-method-dispatch.
(defn- analyze-dot [ctx items env]
(when (< (count items) 3)
(throw (str "Malformed (. target member ...) form")))
@ -457,15 +456,15 @@
(var-ref (:ns r) (:name r))
;; A non-var qualified ref `Class/member` is a host class static
;; (Math/sqrt, Long/MAX_VALUE, System/getenv). The Chez back end
;; lowers it to a runtime static dispatch (jolt-avt6).
;; lowers it to a runtime static dispatch.
(host-static ns nm)))
:else (let [r (resolve-global ctx form)]
(case (:kind r)
:var (var-ref (:ns r) (:name r))
:host (host-ref (:name r))
;; :unresolved — previously emitted a var-ref that auto-interned
;; an UNBOUND var, so a typo'd symbol died later as 'Cannot call
;; nil as a function' with no hint which symbol (jolt-2o7.3).
;; :unresolved — emitting a var-ref here would auto-intern an
;; UNBOUND var, so a typo'd symbol would die later as 'Cannot call
;; nil as a function' with no hint which symbol.
;; Punt to the interpreter: its resolver raises Clojure's
;; 'Unable to resolve symbol' when the form actually runs (at
;; eval for top-level forms, at call for fn bodies). A punt
@ -524,7 +523,7 @@
(and hname (not shadowed) (form-special? hname))
(uncompilable (str "special form " hname))
:else
;; stamp the list form's source offset onto the :invoke (jolt-fqy)
;; stamp the list form's source offset onto the :invoke
;; so the success checker can report file:line:col. nil when the
;; reader did not record it (synthetic/macro-built forms).
(let [n (invoke (analyze ctx head env)

View file

@ -1,25 +1,12 @@
(ns jolt.backend-scheme
"Portable Clojure IR -> Chez Scheme emitter (Chez Phase 3, jolt-cf1q.4).
"Lowers the host-neutral IR (jolt.ir) to Chez Scheme source text.
Consumes the
host-neutral IR (jolt.ir, see jolt-core/jolt/ir.clj) the analyzer produces and
emits Chez Scheme source TEXT. Pure jolt-core (clojure.core + clojure.string
only) so that, once cross-compiled, it runs ON Chez and the analyzer can emit
its own code the bootstrap spine.
Output is a STRING of Scheme source; `host/compile` on Chez is `(eval (read
...))`. Lowers each IR op to Scheme.
INCREMENT 1 (jolt-hg7z): const/local/var/the-var/if/do/let/loop/recur/invoke
(+ native-ops)/fn/def + the escaping/flonum/munge helpers.
INCREMENT 2 (jolt-7jvp): collection literals (vector/map/set, emit-ordered) +
quote (emit-quoted, walks the raw reader form via the portable jolt.host form-*
contract same seam the analyzer uses, so it stays host-neutral).
INCREMENT 3 (jolt-me6m): try/throw + host-call + regex/inst/uuid + def-meta +
quoted-symbol-meta. With this the emitter covers every IR op.
emit-quoted now also reconstructs plain jolt VALUES (def/symbol :meta), enabled
by making :meta a portable struct at the host seam (h-sym-meta). Program
assembly + the prelude driver port land with compile-from-source (inc 4+)."
The analyzer produces IR; this emitter turns each IR op into a string of Scheme
source, which the host compiles with (eval (read ...)). It depends only on
clojure.core and clojure.string, so once cross-compiled it runs on Chez and can
emit its own code the bootstrap spine. Quoted forms are walked through the
portable jolt.host form-* contract, the same seam the analyzer uses, so the
emitter never touches a concrete host representation directly."
(:require [clojure.string :as str]
[jolt.host :refer [form-sym? form-sym-name form-sym-ns form-sym-meta
form-list? form-vec? form-map? form-set? form-char?
@ -83,13 +70,13 @@
(def ^:private supported-host-methods #{"isDirectory" "listFiles"})
;; Native-op Scheme procedures that return a genuine Scheme boolean (#t/#f), so an
;; :if test built from them needs no jolt-truthy? wrapper (jolt-nkcb).
;; :if test built from them needs no jolt-truthy? wrapper.
(def ^:private bool-returning-ops
#{"<" "<=" ">" ">=" "jolt=" "jolt-not"
"jolt-even?" "jolt-odd?" "jolt-pos?" "jolt-neg?"
"jolt-zero?" "jolt-empty?" "jolt-contains?"})
;; PRELUDE MODE (inc 3d). The default (subset) mode rejects any clojure.core ref
;; PRELUDE MODE. The default (subset) mode rejects any clojure.core ref
;; that isn't a native-op — a clean "out of subset" signal for user-facing `-e`.
;; When emitting clojure.core ITSELF as a prelude, core fns reference each other
;; constantly; those lower to var-deref (resolved at runtime).
@ -131,7 +118,7 @@
(declare emit)
;; A Chez string literal (jolt-x0os). Every char outside printable ASCII becomes a
;; A Chez string literal. Every char outside printable ASCII becomes a
;; codepoint hex escape \x<cp>; ; the named escapes (\n \t \r \" \\) match what
;; Chez's reader accepts. For pure printable ASCII this is byte-identical to %j.
(defn- char-escape [cp]
@ -151,7 +138,7 @@
(cond
(nil? v) "jolt-nil"
(boolean? v) (if v "#t" "#f")
;; Numeric tower (jolt-n6al): emit a literal Chez re-reads as the SAME number.
;; Numeric tower: emit a literal Chez re-reads as the SAME number.
;; Exact integers -> "42", exact ratios -> "1/2" (str renders both faithfully);
;; a flonum must carry a decimal point/exponent or Chez reads it back as exact,
;; so a whole flonum (str drops its .0) gets ".0" appended. ##Inf/##-Inf/##NaN
@ -169,9 +156,9 @@
(str "(keyword " (chez-str-lit kns) " " (chez-str-lit (name v)) ")")
(str "(keyword #f " (chez-str-lit (name v)) ")"))
;; char literal -> (integer->char <codepoint>). Get the codepoint via the host
;; contract (form-char-code), NOT (get v :ch): on Janet a char is a struct with
;; a :ch field, but on Chez (the self-hosted spine) it's a native char, so the
;; struct-field read returns nil and emits (integer->char) with no arg.
;; contract (form-char-code), NOT (get v :ch): on Chez (the self-hosted spine)
;; a char is a native char, so a struct-field read returns nil and would emit
;; (integer->char) with no arg.
(form-char? v) (str "(integer->char " (form-char-code v) ")")
:else (throw (ex-info (str "emit-const: unsupported literal " (pr-str v)) {}))))
@ -211,7 +198,7 @@
(str "(let* (" binds ") " (build tmps) ")"))
(build strs)))
;; Quoted literals (jolt-u8j7). A :quote node's :form is the RAW reader form;
;; Quoted literals. A :quote node's :form is the RAW reader form;
;; reconstruct each as the matching Chez RT constructor — the runtime value of a
;; quote is just that literal data. The form is walked via the jolt.host form-*
;; contract (the portable seam the analyzer uses), NOT host-native predicates, so
@ -417,7 +404,7 @@
:else
(invoke))))
;; try/catch/finally (jolt-vcsl). throw raises the jolt value RAW (jolt-throw =
;; try/catch/finally. throw raises the jolt value RAW (jolt-throw =
;; Scheme `raise`); catch lowers to `guard` with an `else` clause (the IR drops
;; the class), finally to `dynamic-wind`'s after-thunk (runs on success, catch and
;; escape — Clojure finally semantics). Both keys optional on the node.
@ -496,7 +483,7 @@
;; a namespace value spliced into a form (~*ns*) -> reconstruct by name.
:the-ns (str "(intern-ns! " (chez-str-lit (:name node)) ")")
;; (.method target arg*) -> jolt-host-call for an rt-shimmed method, else
;; record-method-dispatch (a reify/record protocol method, jolt-jgoc).
;; record-method-dispatch (a reify/record protocol method).
:host-call (let [m (:method node)
target (emit (:target node))
args (map emit (:args node))]

View file

@ -23,8 +23,8 @@
;; A runtime primitive (cons, +, get, apply, …) the back end maps to the host RT.
(defn rt [name] {:op :rt :name name})
;; A name that resolves only via the host's own environment (e.g. + or int? on
;; Janet) — the back end emits a host-appropriate reference.
;; A name that resolves only via the host's own environment (e.g. + or int?) —
;; the back end emits a host-appropriate reference.
(defn host-ref [name] {:op :host :name name})
;; A qualified static reference to a host class member, `Class/member` (e.g.
@ -72,7 +72,7 @@
(defn op [node] (:op node))
;; ---------------------------------------------------------------------------
;; Structural recursion over IR child nodes (jolt-26dm / phase 3a).
;; Structural recursion over IR child nodes.
;;
;; A tree-rewriting pass recurses into each op's child NODE positions and
;; rebuilds the node; this combinator does that one place, so the per-op child

View file

@ -112,6 +112,28 @@
(map? task) (do (apply-project! resolved) (apply-main-opts (:main-opts task) more))
:else (throw (ex-info (str "bad task " name) {})))))
;; build [-m NS | FILE] [-o OUT] [--opt | --dev] — AOT-compile the app into a
;; standalone executable. Resolves deps + roots like `run`, then hands the entry
;; namespace to the host build driver (jolt.host/build-binary, defined by
;; build.ss). Default mode is release; --opt selects optimized, --dev unoptimized.
(defn- cmd-build [more]
(apply-project! (deps/resolve-project (project-dir)))
(let [opts (loop [a more, entry nil, out nil]
(cond
(empty? a) {:entry entry :out out}
(= "-m" (first a)) (recur (drop 2 a) (second a) out)
(= "-o" (first a)) (recur (drop 2 a) entry (second a))
(str/starts-with? (first a) "-") (recur (rest a) entry out)
:else (recur (rest a) (or entry (first a)) out)))
entry (:entry opts)
mode (cond (some #{"--opt"} more) "optimized"
(some #{"--dev"} more) "dev"
:else "release")]
(when (nil? entry)
(throw (ex-info "build needs an entry: -m NS" {})))
(let [out (or (:out opts) (first (str/split entry #"\.")))]
(jolt.host/build-binary entry out mode))))
(defn- nrepl [more]
;; resolve the project (deps on the roots, native libs loaded), then start the
;; nREPL server so an editor can connect and (require '[some.lib]) live. A
@ -128,6 +150,7 @@
(println "usage: jolt <command> [args]")
(println " run -m NS [args] resolve deps.edn, load NS, call its -main")
(println " run FILE load a Clojure file")
(println " build -m NS [-o OUT] [--opt|--dev] compile a standalone binary")
(println " -M:alias [args] run the alias's :main-opts")
(println " -A:alias [args] add the alias's paths/deps")
(println " repl start a line REPL")
@ -146,4 +169,5 @@
(str/starts-with? cmd "-M") (cmd-M cmd more)
(str/starts-with? cmd "-A") (cmd-A cmd more)
(= cmd "-m") (cmd-run (cons "-m" more))
(= cmd "build") (cmd-build more)
:else (run-task cmd more))))

View file

@ -1,11 +1,11 @@
(ns jolt.passes
"IR optimization passes (nanopass-lite, jolt-2om) + the inference/checking
"IR optimization passes (nanopass-lite) + the inference/checking
driver. Façade over three weakly-coupled namespaces, loaded with the compiler:
jolt.passes.fold const-fold (always-on) + the shared const-shape predicate.
jolt.passes.inline inline + flatten-lets + scalar-replace (direct-link only).
jolt.passes.types collection-type inference + success-type checking
(RFC 0006) + the inter-procedural driver API (jolt-767).
(RFC 0006) + the inter-procedural driver API.
run-passes (below) is the single entry the back end applies to every analyzed
form. The driver/checker fns the back end looks up by name (check-form,
@ -25,18 +25,18 @@
(defn run-passes
"All passes, in order. The back end applies this to every analyzed form. When
inlining is enabled for the unit (user code under direct-linking, jolt-87f),
inlining is enabled for the unit (user code under direct-linking),
run inline + flatten + scalar-replace + const-fold to a capped fixpoint
inlining exposes map literals to lookups, scalar-replace collapses them, which
may expose more then a collection-type inference pass (jolt-99x, optionally
may expose more then a collection-type inference pass (optionally
also emitting success diagnostics) that auto-drops the lookup guard where the
type is proven. Otherwise (core + bootstrap) just const-fold, as before."
[node ctx]
(if (inline-enabled? ctx)
(let [_ (set-rec-shapes! (record-shapes ctx)) ;; record ctor fold (jolt-15jq)
(let [_ (set-rec-shapes! (record-shapes ctx)) ;; record ctor fold
;; resolve ^Record param hints (incl. defrecord/extend-type method
;; `this`) to bare field reads per-form, not only under whole-program
;; (jolt-3ko). Same shapes the inline pass uses.
;; `this`) to bare field reads per-form, not only under whole-program.
;; Same shapes the inline pass uses.
_ (set-record-shapes! (record-shapes ctx))
opt (loop [i 0 n (const-fold node)]
(reset! dirty false)
@ -45,6 +45,6 @@
(recur (inc i) n2)
n2)))]
;; a final const-fold after inference propagates any predicate folded to a
;; constant (jolt-wcw), collapsing the `if` it gates to the taken branch.
;; constant, collapsing the `if` it gates to the taken branch.
(const-fold (run-inference opt)))
(const-fold node)))

View file

@ -1,6 +1,6 @@
(ns jolt.passes.inline
"Inlining + flatten-lets + scalar-replace (AOT escape analysis). These run only
when host/inline-enabled? (user code opted into direct-linking, jolt-87f); they
when host/inline-enabled? (user code opted into direct-linking); they
share the alpha-rename invariant (every spliced binder is made globally fresh)
and the `dirty` fixpoint flag. Portable Clojure (compiler-tier)."
(:require [jolt.host :refer [inline-ir]]
@ -18,10 +18,10 @@
;; per unit by run-passes (set-rec-shapes!) before the fixpoint so scalar-replace
;; can recognize a (->Rec ..) call and map its positional args to declared fields
;; — the record analogue of the inline keys a map literal already carries in the
;; IR (jolt-15jq).
;; IR.
(def ^:private rec-shapes (atom {}))
(defn set-rec-shapes!
"Install the record-ctor shape registry the record fold consults (jolt-15jq)."
"Install the record-ctor shape registry the record fold consults."
[m] (reset! rec-shapes (or m {})))
(def ^:private fresh-counter (atom 0))
@ -31,7 +31,7 @@
(str base "__il" n)))
;; ---------------------------------------------------------------------------
;; Inlining (jolt-87f). The back end stashes {:params [..] :body ir} on the var
;; Inlining. The back end stashes {:params [..] :body ir} on the var
;; cell of each single-fixed-arity defn compiled under :inline?; here we splice
;; that body at a call site. To stay capture-safe we ALPHA-RENAME the body —
;; every param and every inner let-bound name becomes a globally fresh name —
@ -89,7 +89,7 @@
(= op :local) (let [r (get env (get node :name))]
;; carry the param's ^:struct hint onto a let-bound fresh
;; local, so lookups inside the inlined body keep the bare
;; (no-guard) path (jolt-dad). The param hint asserts the
;; (no-guard) path. The param hint asserts the
;; arg is a struct; inlining doesn't change that contract.
(if r
(if (and (= :local (get r :op)) (get node :hint) (not (get r :hint)))
@ -256,7 +256,7 @@
;; forward ref: a record ctor (allocating an immutable struct from its args) is
;; side-effect-free, so pure? treats (->Rec pure-args..) as pure — which lets a
;; nested record (a Ray holding a Vec3) fold bottom-up (jolt-15jq).
;; nested record (a Ray holding a Vec3) fold bottom-up.
(declare ctor-shape)
(defn- pure?
@ -427,7 +427,7 @@
(= op :def) (local-escapes? (get node :init) nm)
:else true)))
;; --- record constructors as foldable struct sources (jolt-15jq) -------------
;; --- record constructors as foldable struct sources -------------------------
;; A record ctor (->Rec a b ..) is a positional struct: the registry maps its
;; ctor key ("ns/->Name", exactly how the IR names the call head) to the DECLARED
;; field order. A field read on a non-escaping ctor folds to the matching arg,

View file

@ -1,14 +1,14 @@
(ns jolt.passes.types
"Collection-type inference (jolt-99x) and success-type checking (RFC 0006).
"Collection-type inference and success-type checking (RFC 0006).
A forward, soft-typing pass (simplified HM: monovariant, never-fails, lattice
top = :any) that types expressions and reuses the SAME walk as a loose success
checker. Also the inter-procedural driver API (jolt-767) the back end calls to
top = :any) that types expressions and reuses the same walk as a loose success
checker. Also the inter-procedural driver API the back end calls to
propagate param types across a unit / the whole program. Weakly coupled to the
IR-rewriting passes shares only the const-shape predicate (jolt.passes.fold)."
(:require [jolt.passes.fold :refer [scalar-const?]]))
;; ---------------------------------------------------------------------------
;; Collection-type inference (jolt-99x), Phase 0: intra-procedural. A forward,
;; Collection-type inference, intra-procedural. A forward,
;; soft-typing-style pass (simplified HM: monovariant, never-fails, lattice top
;; = :any) that types expressions from literals/arithmetic and flows the type
;; through let bindings and if-joins. Where a keyword-lookup subject is PROVEN a
@ -39,7 +39,7 @@
(defn- mk-set [t] {:set (if t t :any)})
(defn- mk-struct [fs] {:struct fs})
;; Bounded union types (RFC 0006 / jolt-pz5). A union {:union #{T...}} records
;; Bounded union types (RFC 0006). A union {:union #{T...}} records
;; that a value is provably one of a small, fixed set of SCALAR types — what
;; differing if-branches used to collapse to :any. It exists so the success
;; checker can reject a use where EVERY member is in the op's error domain
@ -86,7 +86,7 @@
(and (struct-type? a) (struct-type? b))
(let [merged (mk-struct (merge-fields (sfields a) (sfields b)))]
;; joining two values of the SAME complete shape preserves it — the
;; merged struct has the same key set (jolt-t34 R2). Different shapes
;; merged struct has the same key set. Different shapes
;; (or an incomplete side) drop it, as the layout is no longer proven.
(if (and (get a :shape) (= (get a :shape) (get b :shape)))
(assoc merged :shape (get a :shape))
@ -94,7 +94,7 @@
(and (vec-type? a) (vec-type? b)) (mk-vec (join-t (velem a) (velem b)))
(and (set-type? a) (set-type? b)) (mk-set (join-t (selem a) (selem b)))
;; differing kinds: form a scalar union when both sides reduce to scalars
;; (or scalar unions); anything compound on either side stays :any (jolt-pz5)
;; (or scalar unions); anything compound on either side stays :any
:else (let [ma (cond (union-type? a) (umembers a) (scalar-t? a) #{a} :else nil)
mb (cond (union-type? b) (umembers b) (scalar-t? b) #{b} :else nil)]
(if (and ma mb) (union-of (reduce conj ma mb)) :any))))
@ -108,25 +108,25 @@
(struct-type? t)
;; capping truncates VALUES below depth d, but the KEY SET is unchanged, so
;; a complete :shape survives — keep it so nested/container field reads can
;; still bare-index (jolt-t34 R2). cap recurses into fields, so a nested
;; still bare-index. cap recurses into fields, so a nested
;; shaped value (a vec3 inside a hit-info) keeps its own :shape too.
(let [capped (mk-struct (reduce (fn [m k] (assoc m k (cap (get (sfields t) k) (dec d))))
{} (keys (sfields t))))
;; the record :type tag (and :shape) are independent of field-value
;; depth, so they survive truncation — a record read from a deep
;; container keeps its identity, so devirtualization (jolt-41m),
;; record? folding, and the record fast path still fire on it.
;; container keeps its identity, so devirtualization, record? folding,
;; and the record fast path still fire on it.
capped (if (get t :shape) (assoc capped :shape (get t :shape)) capped)
capped (if (get t :type) (assoc capped :type (get t :type)) capped)]
capped)
(vec-type? t) (mk-vec (cap (velem t) (dec d)))
(set-type? t) (mk-set (cap (selem t) (dec d)))
:else t))
;; raw-get-safe (a Janet struct / record): a struct type. The field type of key
;; raw-get-safe (a struct / record): a struct type. The field type of key
;; k, if known, else :any.
(defn- struct-safe? [t] (struct-type? t))
(defn- field-type [t k] (if (struct-type? t) (get (sfields t) k :any) :any))
;; Shape (hidden class, jolt-t34). A struct type built from a map LITERAL carries
;; Shape (hidden class). A struct type built from a map LITERAL carries
;; its complete layout — :shape, the canonical (str-sorted) key vector. The back
;; end represents such a map as a shape tuple and reads a field by bare index.
;; A struct type from a JOIN or from field-access inference has no :shape
@ -141,7 +141,7 @@
;; a lookup whose SUBJECT is that node — this is what makes nested access work:
;; (:direction ray) is tagged struct, so (:r (:direction ray)) drops its guard.
;; tag a lookup subject as a struct, carrying the complete shape when known
;; (so the back end bare-indexes) — jolt-t34
;; (so the back end bare-indexes).
(defn- mark-struct [node t]
(let [n (assoc node :hint :struct)]
(if (get t :shape) (assoc n :shape (get t :shape)) n)))
@ -159,7 +159,7 @@
"bit-and" "bit-or" "bit-xor" "count"})
(def ^:private vector-ret-fns #{"vec" "vector" "mapv" "filterv" "subvec"})
;; Inter-procedural state (jolt-767, Phase 1). The Janet orchestrator (backend
;; Inter-procedural state. The orchestrator (backend
;; infer-unit!) drives a whole-unit fixpoint: before typing a fn body it installs
;; the current return-type estimates of all unit fns here, and after typing it
;; reads back the call sites this body made (callee + inferred arg types) to
@ -168,12 +168,12 @@
(def ^:private calls-box (atom [])) ;; collected [ "ns/name" [arg-types...] ]
(def ^:private escapes-box (atom #{})) ;; var-keys used as a VALUE (not a call head)
(def ^:private diag-box (atom [])) ;; success-type-check diagnostics (RFC 0006)
;; jolt-d6u: a var reference's VALUE type — a fn var is :truthy (non-nil), a def
;; a var reference's VALUE type — a fn var is :truthy (non-nil), a def
;; var carries its inferred init type (e.g. a color table -> {:vec :struct-map}).
;; The orchestrator populates this from sealed (opt-mode) cell roots + def inits.
(def ^:private vtype-box (atom {})) ;; "ns/name" -> value type
;; User-function error domains (jolt-zo1), opt-in. As the checker walks defs it
;; User-function error domains, opt-in. As the checker walks defs it
;; registers each non-redefinable single-fixed-arity user fn's {:params :body}
;; here, keyed "ns/name". At a later call site (strict mode only) the body is
;; re-checked with ONE parameter bound to its concrete argument type — if that
@ -181,17 +181,17 @@
;; provably wrong and the CALL is reported. Module state, like rtenv-box: a def
;; must precede its call (the same closed-world ordering RFC 0005 assumes).
(def ^:private user-sig-box (atom {})) ;; "ns/name" -> {:params [..] :body ir}
;; jolt-t34: a record constructor's return shape. "ns/->Name" -> [field-kw ...]
;; a record constructor's return shape. "ns/->Name" -> [field-kw ...]
;; in DECLARED order (the runtime lays records out in declared field order, so
;; the back end bare-indexes by that order). A call (->Point a b) types as a
;; struct of this shape, so field reads on the result bare-index — declared
;; shapes are clean fuel: a lookup, not fragile inference.
(def ^:private record-shapes-box (atom {}))
;; jolt-41m: protocol-method registry "ns/method" -> [proto method], for
;; protocol-method registry "ns/method" -> [proto method], for
;; devirtualizing a protocol call whose receiver is a known record type.
(def ^:private protocol-methods-box (atom {}))
;; jolt-3ko: build a record's struct TYPE from its registry entry, resolving each
;; build a record's struct TYPE from its registry entry, resolving each
;; field's declared type hint. A field tagged with a record type (its ctor-key)
;; recurses, so a Vec3 stored in a Ray field reads back as Vec3 — not :any —
;; which is what lets nested-record code prove its reads. Depth-bounded so a
@ -211,7 +211,7 @@
(field-type-from-tag (when tags (nth tags i)) (dec depth))))
{} (range (count fields)))]
(assoc (mk-struct fmap) :shape (vec fields) :type (get rs :type))))
;; jolt-t34: whether to shape generic const-key MAP literals (opt-in, JOLT_SHAPE).
;; whether to shape generic const-key MAP literals (opt-in, JOLT_SHAPE).
;; Records are shaped regardless; maps only when this is on.
(def ^:private map-shapes-box (atom false))
(def ^:private checking-box (atom #{})) ;; keys mid-recheck — cycle guard
@ -238,10 +238,10 @@
;; a user fn whose return type the fixpoint has estimated
(= op :var) (let [rs (get @record-shapes-box (var-key fnode))]
(if rs
;; record ctor -> struct of declared shape (jolt-t34); :shape
;; record ctor -> struct of declared shape; :shape
;; is the DECLARED field order the back end indexes by, :type
;; the record tag (devirt), and field types come from the
;; declared hints so nested records stay typed (jolt-3ko)
;; declared hints so nested records stay typed
(record-type-from-entry rs type-depth)
(let [r (get @rtenv-box (var-key fnode))]
(if r r (let [nm (and (= "clojure.core" (get fnode :ns)) (get fnode :name))]
@ -255,7 +255,7 @@
:else :any))
:else :any)))
;; Predicate folding (jolt-wcw): a type predicate whose argument's type is
;; Predicate folding: a type predicate whose argument's type is
;; PROVEN folds to a compile-time boolean. Only the precise tags are folded —
;; :num/:str/:kw mean exactly that scalar, and a record carries its defrecord
;; :type tag. NOT folded: vector?/set?/map?, because the :vec tag conflates a
@ -289,8 +289,8 @@
(declare infer)
;; HOFs that apply their fn arg to the ELEMENTS of a collection (jolt-d6u,
;; Phase 3). :epos is which param of the fn receives an element. reduce is
;; HOFs that apply their fn arg to the ELEMENTS of a collection. :epos is which
;; param of the fn receives an element. reduce is
;; handled separately (its arity changes the coll position, and its closure
;; also takes an accumulator).
(def ^:private hof-table
@ -353,7 +353,7 @@
base (when struct?
(cap (mk-struct (reduce (fn [m r] (assoc m (nth r 3) (nth r 2))) {} res)) type-depth))
;; a literal is a COMPLETE shape: carry its sorted key vector so the
;; back end can lay it out and bare-index lookups (jolt-t34)
;; back end can lay it out and bare-index lookups
shp (when (and @map-shapes-box base (struct-type? base)) (shape-order (keys (sfields base))))
t (if base (if shp (assoc base :shape shp) base) :any)
node' (assoc node :pairs (mapv (fn [r] [(nth r 0) (nth r 1)]) res))]
@ -393,7 +393,7 @@
args (get node :args)
n (count args)]
(cond
;; predicate folding (jolt-wcw): a type predicate over a single,
;; predicate folding: a type predicate over a single,
;; side-effect-free argument whose type PROVES the answer becomes a
;; boolean constant — eliminating the call, and (once const-fold runs
;; after inference) collapsing any `if` it gates. Falls through to the
@ -427,7 +427,7 @@
dr (when (= n 3) (infer (nth args 2) tenv))]
[(if dr (join ft (nth dr 0)) ft)
(assoc node :args (if dr [msub (nth kr 1) (nth dr 1)] [msub (nth kr 1)]))])
;; reduce over a typed vector with a fn-literal (jolt-d6u): seed the
;; reduce over a typed vector with a fn-literal: seed the
;; closure's accumulator (param 0) to the init type and its element
;; (param 1) to the vector's element type, so its body — and any calls
;; it makes — see those types.
@ -471,7 +471,7 @@
fnode' (if iscall-var fnode (nth fr 1))
;; the callee's value type: a var's from vtype-box (a fn is
;; :truthy, a def carries its inferred type), else the inferred
;; type of the callee expression (jolt-wwy)
;; type of the callee expression
callee-t (if iscall-var (get @vtype-box (var-key fnode)) (nth fr 0))
ares (mapv (fn [a] (infer a tenv)) args)]
(when iscall-var
@ -482,7 +482,7 @@
(when @checking?
(let [ats (mapv (fn [r] (nth r 0)) ares) pos (get node :pos)]
(when cn (check-invoke cn args ats pos))
;; calling a provably non-function (jolt-wwy)
;; calling a provably non-function
(when (not-callable? callee-t)
(swap! diag-box conj
{:op :call :type (type-name callee-t) :pos pos
@ -490,7 +490,7 @@
(when (and @strict-box iscall-var)
(let [k (var-key fnode) usig (get @user-sig-box k)]
(when usig (check-user-call k usig ats pos))))))
;; devirtualization (jolt-41m): a protocol-method call whose receiver
;; devirtualization: a protocol-method call whose receiver
;; (arg 0) is a known record type resolves to a direct method call.
;; Annotate the node with [type-tag proto method]; the back end looks
;; up the impl at emit time and calls it directly, skipping the
@ -517,7 +517,7 @@
[(nth br 0) (assoc node :bindings (nth res 1) :body (nth br 1))])
(= op :loop)
;; conservative + sound: loop bindings join across recur, which we don't
;; track in Phase 0, so they stay :any. Still descend to annotate any
;; track here, so they stay :any. Still descend to annotate any
;; known-type lookups inside the body.
[:any (assoc node
:bindings (mapv (fn [b] [(nth b 0) (nth (infer (nth b 1) tenv) 1)]) (get node :bindings))
@ -531,7 +531,7 @@
;; (:phints, name -> ctor-key) — then seed it to that record type so field
;; reads off it bare-index per-form, not only under whole-program. This is
;; what makes a protocol method's `this` (hinted by defrecord/extend-type)
;; read its fields without the runtime tag guard (jolt-3ko).
;; read its fields without the runtime tag guard.
[:any (assoc node :arities
(mapv (fn [a]
(let [phm (reduce (fn [m pr] (assoc m (nth pr 0) (nth pr 1)))
@ -565,7 +565,7 @@
;; cases; lenient ops ((get 5 :k) -> nil, (:k 5) -> nil) are NOT listed.
;; concrete non-numbers: arithmetic provably throws on these. A union is in the
;; error domain only when EVERY member is (jolt-pz5) — if any member is an
;; error domain only when EVERY member is — if any member is an
;; accepted type the call is accepted (no false positive).
(defn- not-number? [t]
(if (union-type? t)
@ -581,7 +581,7 @@
(every? not-seqable? (umembers t))
(or (= t :num) (= t :kw))))
;; concrete non-callable values (jolt-wwy): calling them throws "Cannot call X
;; concrete non-callable values: calling them throws "Cannot call X
;; as a function". Only :num and :str — keywords/maps/vectors/sets are IFn,
;; :truthy/:any/:nil are ambiguous (accepted). A union is non-callable only when
;; every member is.
@ -615,7 +615,7 @@
(defn- check-invoke
"If node is a core-op call whose argument type is provably in the error domain,
conj a diagnostic. arg-types is the vector of inferred argument types; pos is
the call form's source offset (jolt-fqy), carried into each diagnostic."
the call form's source offset, carried into each diagnostic."
[cn args arg-types pos]
(cond
(contains? num-ops cn)
@ -638,7 +638,7 @@
", but argument 1 is " (type-name t))})))
:else nil))
;; --- user-function error domains (jolt-zo1), opt-in --------------------------
;; --- user-function error domains, opt-in -------------------------------------
(defn- all-any-env
"tenv binding every param name to :any (the all-ambiguous baseline)."
[params]
@ -677,7 +677,7 @@
(defn- check-user-call
"Strict mode: report a call to a registered user fn that provably throws
either a WRONG ARITY (the registered fn has one fixed arity, so a different
arg count always throws, jolt-wwy) or an argument whose concrete type the body
arg count always throws) or an argument whose concrete type the body
rejects. For the latter, re-check the body with ONLY that parameter bound to
its arg type (others :any); a diagnostic the all-:any body did not already
have means the argument alone is provably wrong. Monotonic binding a
@ -715,13 +715,13 @@
nil (range npar)))))
(reset! checking-box prev))))
;; --- Inter-procedural driver API (jolt-767) consumed by the back end --------
;; --- Inter-procedural driver API consumed by the back end -------------------
(defn set-rtenv!
"Install the current return-type estimates (a map \"ns/name\" -> type) used to
type call results during the fixpoint."
[m] (reset! rtenv-box m))
;; jolt-t34: install record-ctor shapes ("ns/->Name" -> [field-kw ...]) and the
;; install record-ctor shapes ("ns/->Name" -> [field-kw ...]) and the
;; map-shaping flag (opt-in JOLT_SHAPE), both read by infer.
(defn set-record-shapes! [m] (reset! record-shapes-box (or m {})))
(defn set-protocol-methods! [m] (reset! protocol-methods-box (or m {})))
@ -729,7 +729,7 @@
(defn set-vtypes!
"Install var VALUE types (a map \"ns/name\" -> type): fn vars are :truthy
(non-nil), def vars carry their inferred init type (jolt-d6u)."
(non-nil), def vars carry their inferred init type."
[m] (reset! vtype-box m))
(defn join-types
@ -747,7 +747,7 @@
usable in normal builds (the decoupled checking path).
With strict? true, also reports calls to registered user functions whose
concrete argument types provably make the body throw (jolt-zo1, opt-in,
concrete argument types provably make the body throw (opt-in,
closed-world). user-sig-box accumulates registered defs across forms, so a
def must precede its call the same ordering RFC 0005 already assumes."
([node] (check-form node false))
@ -810,7 +810,7 @@
propagates to a fn's callees DURING inference not only at the final re-emit
(reinfer-def). Without it a hinted param with no callers stays :any through the
fixpoint, so a field read off it (e.g. (:origin ^Ray r)) never tells a shared
callee its arg is a Vec3 (jolt-3ko)."
callee its arg is a Vec3."
[params phints]
(let [m (reduce (fn [acc pr] (assoc acc (nth pr 0) (nth pr 1))) {} phints)]
(mapv (fn [nm]

View file

@ -1,22 +1,16 @@
(ns jolt.reader
"Portable Clojure reader: source text -> reader forms (Chez Phase 3, jolt-cf1q.4).
"Reads Clojure source text into reader forms.
All the lexing/parsing LOGIC
is portable Clojure; form CONSTRUCTION and string->number parsing delegate to the
jolt.host contract (form-make-symbol/char, form-char-from-name, form-scan-number)
a Clojure source file cannot write a {:jolt/type :symbol} literal (it parses as
a tagged reader form), and the concrete form representation is the host's to own.
Same split the analyzer uses for the form-* readers. Once cross-compiled this runs
ON Chez to drive compile-from-source.
The lexing and parsing is portable Clojure; form construction and
string->number parsing delegate to the jolt.host contract (form-make-symbol/
char, form-char-from-name, form-scan-number). A Clojure source file can't write
a {:jolt/type :symbol} literal it would parse as a tagged reader form and
the concrete form representation belongs to the host. The analyzer uses the same
split. Once cross-compiled this runs on Chez to drive compile-from-source.
Positions are CHARACTER indices; for ASCII
source they coincide with byte indices, and form VALUES are identical either way the parity gate
compares values, not positions.
INCREMENT 5a (jolt-50xx): the ATOM layer whitespace/comments, symbols (+ nil/
true/false), keywords, strings, numbers (sign/hex/radix/ratio/fractional/
exponent, trailing N/M), characters. Collections, quote/deref/meta, and dispatch
(#) land in 5b/5c (they throw not-yet-ported so a hit is loud)."
Positions are character indices; for ASCII source they coincide with byte
indices, and form values are identical either way the parity gate compares
values, not positions."
(:require [clojure.string :as str]
[jolt.host :refer [form-make-symbol form-make-char form-char-from-name
form-scan-number form-make-list form-make-vector
@ -213,8 +207,8 @@
[:form (form-make-vector items) end]))
;; Map: pair up keys and values, skipping comments/#_ in either slot while keeping
;; the pending key (dropping both desyncs the pairing). Splice in a map slot lands
;; in inc 5c; here a key/value is always a single :form (or :skip).
;; the pending key (dropping both desyncs the pairing). A key/value is always a
;; single :form (or :skip) — splice in a map slot is not supported.
(defn- read-map* [s pos]
(loop [pos (inc pos) kvs []]
(let [pos (skip-whitespace s pos)]

View file

@ -1,119 +0,0 @@
;; Phase 0c — persistent-collection perf experiment.
;;
;; Decides shim-vs-self-hosted for collections: is a persistent HAMT fast enough
;; on the Chez substrate that we can afford to SELF-HOST it (in Clojure compiled
;; by Chez) rather than keep it in the Scheme shim? This measures the substrate
;; ceiling with a hand-written Scheme HAMT (what the backend would emit) against
;; Chez's native mutable hashtable (the non-persistent lower bound), on the
;; collections-bench map workload (freq-map + sum-vals, n keys mod 4096).
;; chez --script collections-experiment.ss [n=30000] [optlevel=2]
(import (chezscheme))
(optimize-level
(let ((a (command-line-arguments)))
(if (and (pair? a) (pair? (cdr a))) (string->number (cadr a)) 2)))
;; ---- persistent bitmap HAMT (assoc/get), 5 bits/level, integer-key hash ------
(define-record-type hnode (fields bitmap arr) (nongenerative hnode-v1))
(define empty-map (make-hnode 0 (vector)))
(define (popcount n)
(let loop ((n n) (c 0)) (if (fx=? n 0) c (loop (fxand n (fx- n 1)) (fx+ c 1)))))
(define (mask h shift) (fxand (fxsra h shift) 31))
(define (idxof bitmap bit) (popcount (fxand bitmap (fx- bit 1))))
(define (vec-insert v i x)
(let* ((n (vector-length v)) (out (make-vector (fx+ n 1))))
(let loop ((j 0))
(when (fx<? j i) (vector-set! out j (vector-ref v j)) (loop (fx+ j 1))))
(vector-set! out i x)
(let loop ((j i)) (when (fx<? j n) (vector-set! out (fx+ j 1) (vector-ref v j)) (loop (fx+ j 1))))
out))
(define (vec-set v i x)
(let ((out (vector-copy v))) (vector-set! out i x) out))
;; leaf = (cons key val); subtree = hnode
(define (merge-leaves shift k1h e1 k2h k2 v2)
(if (fx>? shift 30)
;; hash exhausted (won't happen for distinct small ints) — chain via assoc-list leaf
(cons 'collision (list e1 (cons k2 v2)))
(let ((i1 (mask k1h shift)) (i2 (mask k2h shift)))
(if (fx=? i1 i2)
(let ((sub (merge-leaves (fx+ shift 5) k1h e1 k2h k2 v2)))
(make-hnode (fxsll 1 i1) (vector sub)))
(let* ((b1 (fxsll 1 i1)) (b2 (fxsll 1 i2)))
(if (fx<? i1 i2)
(make-hnode (fxior b1 b2) (vector e1 (cons k2 v2)))
(make-hnode (fxior b1 b2) (vector (cons k2 v2) e1))))))))
(define (assoc-h node shift h key val)
(let* ((bit (fxsll 1 (mask h shift)))
(bm (hnode-bitmap node))
(arr (hnode-arr node)))
(if (fx=? 0 (fxand bm bit))
(make-hnode (fxior bm bit) (vec-insert arr (idxof bm bit) (cons key val)))
(let* ((i (idxof bm bit)) (child (vector-ref arr i)))
(cond
((hnode? child)
(make-hnode bm (vec-set arr i (assoc-h child (fx+ shift 5) h key val))))
((eqv? (car child) key) ; leaf, same key -> replace
(make-hnode bm (vec-set arr i (cons key val))))
(else ; leaf, diff key -> split
(make-hnode bm (vec-set arr i (merge-leaves (fx+ shift 5)
(car child) child h key val)))))))))
(define (assoc-map m key val) (assoc-h m 0 key key val)) ; hash = key (distinct small ints)
(define (get-h node shift h key default)
(let* ((bit (fxsll 1 (mask h shift))) (bm (hnode-bitmap node)))
(if (fx=? 0 (fxand bm bit)) default
(let ((child (vector-ref (hnode-arr node) (idxof bm bit))))
(cond ((hnode? child) (get-h child (fx+ shift 5) h key default))
((eqv? (car child) key) (cdr child))
(else default))))))
(define (get-map m key default) (get-h m 0 key key default))
;; ---- workloads (mirror bench/collections.clj freq-map + sum-vals) ------------
(define buckets 4096)
(define (freq-hamt n)
(let loop ((i 0) (m empty-map))
(if (fx<? i n)
(let ((k (fxmod (fx* i 2654435761) buckets)))
(loop (fx+ i 1) (assoc-map m k (fx+ 1 (get-map m k 0)))))
m)))
(define (freq-native n)
(let ((m (make-eqv-hashtable)))
(let loop ((i 0))
(if (fx<? i n)
(let ((k (fxmod (fx* i 2654435761) buckets)))
(hashtable-set! m k (fx+ 1 (hashtable-ref m k 0)))
(loop (fx+ i 1)))
m))))
;; sum back: HAMT walk vs native walk
(define (sum-hamt m)
(let walk ((node m) (acc 0))
(let ((arr (hnode-arr node)))
(let loop ((j 0) (acc acc))
(if (fx<? j (vector-length arr))
(let ((c (vector-ref arr j)))
(loop (fx+ j 1) (if (hnode? c) (walk c acc) (fx+ acc (cdr c)))))
acc)))))
(define (sum-native m) (call-with-values (lambda () (hashtable-entries m))
(lambda (ks vs) (let ((acc 0)) (vector-for-each (lambda (v) (set! acc (fx+ acc v))) vs) acc))))
;; ---- bench harness -----------------------------------------------------------
(define (now-ns) (let ((t (current-time 'time-monotonic)))
(+ (* (time-second t) 1000000000) (time-nanosecond t))))
(define (bench name build sum n)
(sum (build (quotient n 4))) (sum (build (quotient n 4))) ; warmup
(let loop ((k 0) (acc '()) (r 0))
(if (fx<? k 3)
(let* ((t0 (now-ns)) (m (build n)) (s (sum m))
(ms (/ (- (now-ns) t0) 1000000.0)))
(loop (fx+ k 1) (cons ms acc) s))
(printf "~a result ~a mean ~a ms\n" name r
(exact->inexact (/ (apply + acc) 3.0))))))
(let* ((a (command-line-arguments))
(n (if (pair? a) (string->number (car a)) 30000)))
(printf "collections map-churn (n=~a, ~a buckets)\n" n buckets)
(bench "persistent HAMT (self-hostable) " freq-hamt sum-hamt n)
(bench "native hashtable (mutable, ceil)" freq-native sum-native n))

View file

@ -1,25 +0,0 @@
;; fib spike — translated from bench/fib.clj. Pure call + integer arith.
;; chez --script fib.ss [n=30] [optlevel=2]
(import (chezscheme))
(optimize-level
(let ((a (command-line-arguments)))
(if (and (pair? a) (pair? (cdr a))) (string->number (cadr a)) 2)))
(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
(define (run n) (fib n))
(define (now-ns)
(let ((t (current-time 'time-monotonic)))
(+ (* (time-second t) 1000000000) (time-nanosecond t))))
(let* ((a (command-line-arguments))
(n (if (pair? a) (string->number (car a)) 30)))
(run (- n 6)) (run (- n 6)) ; warmup
(let loop ((k 0) (acc '()))
(if (< k 3)
(let* ((t0 (now-ns)) (r (run n)) (ms (/ (- (now-ns) t0) 1000000.0)))
(loop (+ k 1) (cons ms acc)))
(begin
(printf "fib n ~a result ~a\n" n (run n))
(printf "runs: ~a\n" (reverse acc))
(printf "mean: ~a ms\n" (exact->inexact (/ (apply + acc) 3.0)))))))

View file

@ -1,45 +0,0 @@
;; mandelbrot, flonum-specialized — what a type-aware jolt->Chez backend would
;; emit (fl*/fl+/fl< unbox; fx ops for the integer counter). This is the real
;; substrate ceiling vs the generic version (which boxes every flonum).
;; chez --script mandelbrot-fl.ss [n=200] [optlevel=3]
(import (chezscheme))
(optimize-level
(let ((a (command-line-arguments)))
(if (and (pair? a) (pair? (cdr a))) (string->number (cadr a)) 3)))
(define (count-point cr ci cap)
(let loop ((i 0) (zr 0.0) (zi 0.0))
(if (or (fx>= i cap) (fl> (fl+ (fl* zr zr) (fl* zi zi)) 4.0))
i
(loop (fx+ i 1)
(fl+ (fl- (fl* zr zr) (fl* zi zi)) cr)
(fl+ (fl* 2.0 (fl* zr zi)) ci)))))
(define (run n)
(let ((cap 200) (nd (fixnum->flonum n)))
(let loopy ((y 0) (acc 0))
(if (fx< y n)
(let* ((ci (fl- (fl/ (fl* 2.0 (fixnum->flonum y)) nd) 1.0))
(row (let loopx ((x 0) (a 0))
(if (fx< x n)
(let ((cr (fl- (fl/ (fl* 2.0 (fixnum->flonum x)) nd) 1.5)))
(loopx (fx+ x 1) (fx+ a (count-point cr ci cap))))
a))))
(loopy (fx+ y 1) (fx+ acc row)))
acc))))
(define (now-ns)
(let ((t (current-time 'time-monotonic)))
(+ (* (time-second t) 1000000000) (time-nanosecond t))))
(let* ((a (command-line-arguments))
(n (if (pair? a) (string->number (car a)) 200)))
(run n) (run n)
(let loop ((k 0) (acc '()))
(if (< k 3)
(let* ((t0 (now-ns)) (r (run n)) (ms (/ (- (now-ns) t0) 1000000.0)))
(loop (+ k 1) (cons ms acc)))
(begin
(printf "mandelbrot-fl n ~a result ~a\n" n (run n))
(printf "runs: ~a\n" (reverse acc))
(printf "mean: ~a ms\n" (exact->inexact (/ (apply + acc) 3.0)))))))

View file

@ -1,44 +0,0 @@
;; mandelbrot spike — translated from bench/mandelbrot.clj. Pure float compute,
;; tight recur loops (here named-let tail loops). cap=200 like the .clj.
;; chez --script mandelbrot.ss [n=200] [optlevel=2]
(import (chezscheme))
(optimize-level
(let ((a (command-line-arguments)))
(if (and (pair? a) (pair? (cdr a))) (string->number (cadr a)) 2)))
(define (count-point cr ci cap)
(let loop ((i 0) (zr 0.0) (zi 0.0))
(if (or (>= i cap) (> (+ (* zr zr) (* zi zi)) 4.0))
i
(loop (+ i 1)
(+ (- (* zr zr) (* zi zi)) cr)
(+ (* 2.0 (* zr zi)) ci)))))
(define (run n)
(let ((cap 200) (nd (* 1.0 n)))
(let loopy ((y 0) (acc 0))
(if (< y n)
(let* ((ci (- (/ (* 2.0 y) nd) 1.0))
(row (let loopx ((x 0) (a 0))
(if (< x n)
(let ((cr (- (/ (* 2.0 x) nd) 1.5)))
(loopx (+ x 1) (+ a (count-point cr ci cap))))
a))))
(loopy (+ y 1) (+ acc row)))
acc))))
(define (now-ns)
(let ((t (current-time 'time-monotonic)))
(+ (* (time-second t) 1000000000) (time-nanosecond t))))
(let* ((a (command-line-arguments))
(n (if (pair? a) (string->number (car a)) 200)))
(run n) (run n) ; warmup
(let loop ((k 0) (acc '()))
(if (< k 3)
(let* ((t0 (now-ns)) (r (run n)) (ms (/ (- (now-ns) t0) 1000000.0)))
(loop (+ k 1) (cons ms acc)))
(begin
(printf "mandelbrot n ~a result ~a\n" n (run n))
(printf "runs: ~a\n" (reverse acc))
(printf "mean: ~a ms\n" (exact->inexact (/ (apply + acc) 3.0)))))))

View file

@ -1,165 +0,0 @@
(ns jolt.lang.persistent-vector
"PersistentVector: 32-way branching trie with tail optimization.")
(def branch-factor 32)
(def shift-increment 5)
(def tail-max 31)
(deftype VectorNode [^:volatile-mutable arr])
(deftype PersistentVector [cnt shift root tail _meta])
(def empty-array (object-array 0))
(def EMPTY (PersistentVector. 0 shift-increment nil empty-array nil))
(defn- tailoff [pv]
(int (- (.-cnt pv) (unsigned-bit-shift-right (.-cnt pv) shift-increment))))
(defn- new-path [level node]
(if (= level 0)
node
(let [arr (object-array branch-factor)]
(aset arr 0 (new-path (int (- level shift-increment)) node))
(VectorNode. arr))))
(defn- push-tail [parent level tailnode cnt]
(let [subidx (int (bit-and (unsigned-bit-shift-right (int cnt) (int level)) tail-max))
ret (VectorNode. (aclone (.-arr parent)))]
(if (= level shift-increment)
(do (aset (.-arr ret) subidx tailnode) ret)
(let [child (aget (.-arr parent) subidx)]
(aset (.-arr ret) subidx
(if child
(push-tail child (int (- level shift-increment)) tailnode cnt)
(new-path (int (- level shift-increment)) tailnode)))
ret))))
(defn- do-assoc [level node i val]
(let [ret (VectorNode. (aclone (.-arr node)))]
(if (= level 0)
(do (aset (.-arr ret) (int (bit-and i tail-max)) val) ret)
(let [subidx (int (bit-and (unsigned-bit-shift-right (int i) (int level)) tail-max))]
(aset (.-arr ret) subidx
(do-assoc (int (- level shift-increment)) (aget (.-arr node) subidx) i val))
ret))))
(defn- array-for [pv i]
(if (and (<= 0 i) (< i (.-cnt pv)))
(if (>= i (tailoff pv))
(.-tail pv)
(loop [node (.-root pv) level (.-shift pv)]
(if (> level 0)
(recur (aget (.-arr node)
(int (bit-and (unsigned-bit-shift-right (int i) (int level)) tail-max)))
(int (- level shift-increment)))
(.-arr node))))
nil))
(defn pv-conj [pv val]
(let [cnt (.-cnt pv)]
(if (< (- cnt (tailoff pv)) branch-factor)
(let [old-len (alength (.-tail pv))
new-tail (object-array (+ old-len 1))]
(loop [i 0]
(if (< i old-len)
(do (aset new-tail i (aget (.-tail pv) i)) (recur (unchecked-inc i)))
(do (aset new-tail i val)
(PersistentVector. (unchecked-inc cnt) (.-shift pv) (.-root pv) new-tail (.-_meta pv))))))
(let [tail-node (VectorNode. (.-tail pv))
root-overflow? (> (unchecked-inc (unsigned-bit-shift-right cnt shift-increment))
(bit-shift-left 1 (.-shift pv)))]
(if root-overflow?
(let [nr (object-array branch-factor)]
(aset nr 0 (.-root pv))
(aset nr 1 (new-path (.-shift pv) tail-node))
(let [new-root (VectorNode. nr)
new-shift (+ (.-shift pv) shift-increment)
new-tail (object-array 1)]
(aset new-tail 0 val)
(PersistentVector. (unchecked-inc cnt) new-shift new-root new-tail (.-_meta pv))))
(let [new-root (push-tail (.-root pv) (.-shift pv) tail-node cnt)
new-tail (object-array 1)]
(aset new-tail 0 val)
(PersistentVector. (unchecked-inc cnt) (.-shift pv) new-root new-tail (.-_meta pv))))))))
(defn pv-nth [pv i]
(let [node (array-for pv i)]
(if node
(aget node (int (bit-and i tail-max)))
(throw (str "Index out of bounds: " i)))))
(defn pv-assoc [pv i val]
(let [cnt (.-cnt pv)]
(if (and (<= 0 i) (< i cnt))
(if (>= i (tailoff pv))
(let [new-tail (object-array (alength (.-tail pv)))]
(loop [j 0]
(if (< j (alength new-tail))
(do (aset new-tail j
(if (= j (int (bit-and i tail-max))) val (aget (.-tail pv) j)))
(recur (unchecked-inc j)))
(PersistentVector. cnt (.-shift pv) (.-root pv) new-tail (.-_meta pv)))))
(PersistentVector. cnt (.-shift pv) (do-assoc (.-shift pv) (.-root pv) i val) (.-tail pv) (.-_meta pv)))
(if (= i cnt)
(pv-conj pv val)
(throw (str "Index out of bounds: " i))))))
(defn- pop-tail [level node cnt]
(let [subidx (int (bit-and (unsigned-bit-shift-right (int (- cnt 2)) (int level)) tail-max))]
(if (> level shift-increment)
(let [new-child (pop-tail (int (- level shift-increment)) (aget (.-arr node) subidx) cnt)]
(if (and (nil? new-child) (zero? subidx))
nil
(let [ret (VectorNode. (aclone (.-arr node)))]
(aset (.-arr ret) subidx new-child)
ret)))
(if (zero? subidx)
nil
(let [ret (VectorNode. (aclone (.-arr node)))]
(aset (.-arr ret) subidx nil)
ret)))))
(defn- pv-nth-internal [cnt shift root i]
(if (and (<= 0 i) (< i cnt))
(if (>= i (- cnt (int (bit-and cnt tail-max))))
nil
(loop [node root level shift]
(if (> level 0)
(recur (aget (.-arr node) (int (bit-and (unsigned-bit-shift-right (int i) (int level)) tail-max)))
(int (- level shift-increment)))
(aget (.-arr node) (int (bit-and i tail-max))))))
nil))
(defn pv-pop [pv]
(let [cnt (.-cnt pv)]
(cond
(zero? cnt) (throw "Can't pop empty vector")
(= cnt 1) EMPTY
(> (- cnt (tailoff pv)) 1)
(let [old-tail (.-tail pv)
new-tail (object-array (dec (alength old-tail)))]
(loop [i 0]
(if (< i (alength new-tail))
(do (aset new-tail i (aget old-tail i)) (recur (unchecked-inc i)))
(PersistentVector. (dec cnt) (.-shift pv) (.-root pv) new-tail (.-_meta pv)))))
:else
(let [new-root (pop-tail (.-shift pv) (.-root pv) cnt)
new-cnt (dec cnt)
new-tail-len (int (bit-and new-cnt tail-max))
tail-len (if (zero? new-tail-len) branch-factor new-tail-len)
new-tail (object-array tail-len)]
(loop [i 0]
(if (< i tail-len)
(let [idx (+ (- new-cnt tail-len) i)]
(aset new-tail i (pv-nth-internal new-cnt (.-shift pv) new-root idx))
(recur (unchecked-inc i)))
(PersistentVector. new-cnt (.-shift pv) new-root new-tail (.-_meta pv))))))))
(defn pv-empty [_] EMPTY)
(defn vector [& args]
(loop [acc EMPTY items (seq args)]
(if (seq items)
(recur (pv-conj acc (first items)) (rest items))
acc)))
(defn vector? [x] (instance? PersistentVector x))

View file

@ -1,25 +0,0 @@
; Jolt Standard Library: jolt.http
; HTTP client over spork/http (janet.spork.http/*; requires `jpm install spork`).
; Responses come back as {:status int :headers map :body string}.
(defn- response->map [r]
;; clojure.core/get explicitly: this ns defines an http `get` that shadows it
{:status (clojure.core/get r :status)
:body (str (or (janet.spork.http/read-body r) ""))
:headers (reduce (fn [m kv] (assoc m (str (nth kv 0)) (str (nth kv 1))))
{}
(janet/pairs (or (clojure.core/get r :headers) (janet/struct))))})
(defn- header-struct [headers]
(apply janet/struct
(mapcat (fn [kv] [(str (key kv)) (str (val kv))]) (seq (or headers {})))))
(defn get
[url & {:keys [headers]}]
(response->map
(janet.spork.http/request "GET" url :headers (header-struct headers))))
(defn post
[url body & {:keys [headers]}]
(response->map
(janet.spork.http/request "POST" url :body body :headers (header-struct headers))))

View file

@ -52,7 +52,7 @@
(defn- drain-reader
"All remaining content of a reader as a string. Shim readers (StringReader,
PushbackReader, io/reader results) expose char-wise .read; a raw janet file
PushbackReader, io/reader results) expose char-wise .read; a raw file
handle is read whole."
[reader]
(if (= :core/file (janet/type reader))

Some files were not shown because too many files have changed in this diff Show more