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:
commit
b59e5ecc42
120 changed files with 1588 additions and 1655 deletions
18
CLAUDE.md
18
CLAUDE.md
|
|
@ -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.
|
||||
|
|
|
|||
8
Makefile
8
Makefile
|
|
@ -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
|
||||
|
|
|
|||
66
README.md
66
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
175
docs/host-interop.md
Normal 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).
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
186
docs/rfc/0007-compilation-modes-and-binary-output.md
Normal file
186
docs/rfc/0007-compilation-modes-and-binary-output.md
Normal 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 3–4 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.
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 | ✓ |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
63
host/chez/build-smoke.sh
Executable 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
258
host/chez/build.ss
Normal 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))
|
||||
|
|
@ -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)))))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 ------
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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/")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))))
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
@ -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)))))))
|
||||
|
|
@ -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)))))))
|
||||
|
|
@ -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)))))))
|
||||
|
|
@ -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))
|
||||
|
|
@ -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))))
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue