Clean up codebase: rename stdlib layer, strip porting residue, fix tooling

Rename src/jolt -> stdlib (the runtime-loaded layer; jolt-core stays the
seed-baked layer) and update the loader / emit-image / doc paths. Drop dead
code: the spike/ experiments, the duplicate clojuredocs-export.edn (json moves
to tools/), the Janet-era jolt.http binding, and the orphaned
persistent_vector.clj whose ns/path didn't even match.

Strip porting residue from comments and docstrings across host/chez, jolt-core,
stdlib, tests, and docs: internal issue ids, "Phase N" markers, and the "vs
Janet" historical exposition, leaving present-tense descriptions and the real
JVM-Clojure semantic contrasts. Same pass over the corpus suite labels. The seed
is unchanged (docstrings/comments aren't emitted), so the self-host fixpoint and
corpus are untouched.

Port tools/spec_coverage.py off the dead janet probe to bin/joltc and regenerate
coverage.md; drop the dead :host/janet rule from certify.clj and regenerate the
conformance profile. Add docs/host-interop.md (the JVM shims and how to register
your own host class from a library) and a writing-style note in CLAUDE.md.

Stabilize the four racy concurrency corpus cases (future-cancel and agent
send/send-off): give the future a sleeping body and the agent a slow action, so
cancel reliably catches an in-flight future and deref reliably reads the
pre-update snapshot. They certify deterministically now, so drop their :flaky
allowlist entries and the orphaned legend.
This commit is contained in:
Yogthos 2026-06-22 22:18:00 -04:00
parent c18f8087f0
commit 33eff7c7d8
112 changed files with 970 additions and 1621 deletions

98
stdlib/clojure/data.clj Normal file
View file

@ -0,0 +1,98 @@
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
;; Ported from clojure.data (Stuart Halloway). The reference dispatches via the
;; EqualityPartition/Diff protocols extended over host types; Jolt uses a plain
;; equality-partition fn over its own predicates instead — same behaviour, no
;; host-type protocol plumbing.
(ns clojure.data
"Non-core data functions."
(:require [clojure.set :as set]))
(declare diff)
(defn- atom-diff [a b]
(if (= a b) [nil nil a] [a b nil]))
;; Convert an associative-by-numeric-index collection into an equivalent vector,
;; with nil for any missing keys.
(defn- vectorize [m]
(when (seq m)
(reduce
(fn [result [k v]] (assoc result k v))
(vec (repeat (apply max (keys m)) nil))
m)))
(defn- diff-associative-key
"Diff associative things a and b, comparing only the key k."
[a b k]
(let [va (get a k)
vb (get b k)
[a* b* ab] (diff va vb)
in-a (contains? a k)
in-b (contains? b k)
same (and in-a in-b
(or (not (nil? ab))
(and (nil? va) (nil? vb))))]
[(when (and in-a (or (not (nil? a*)) (not same))) {k a*})
(when (and in-b (or (not (nil? b*)) (not same))) {k b*})
(when same {k ab})]))
(defn- diff-associative
"Diff associative things a and b, comparing only keys in ks."
[a b ks]
(reduce
;; mapv (vector result) rather than the reference's (doall (map …)): the diff
;; triples are destructured positionally and a list with a nil middle element
;; mis-binds under jolt destructuring, whereas a vector indexes cleanly.
(fn [diff1 diff2] (mapv merge diff1 diff2))
[nil nil nil]
(mapv (partial diff-associative-key a b) ks)))
(defn- diff-sequential [a b]
(vec (mapv vectorize (diff-associative
(if (vector? a) a (vec a))
(if (vector? b) b (vec b))
(range (max (count a) (count b)))))))
(defn- diff-set [a b]
[(not-empty (set/difference a b))
(not-empty (set/difference b a))
(not-empty (set/intersection a b))])
(defn- equality-partition [x]
(cond
(nil? x) :atom
(map? x) :map
(set? x) :set
(sequential? x) :sequential
:else :atom))
(defn- diff-similar [a b]
((case (equality-partition a)
:atom atom-diff
:set diff-set
:sequential diff-sequential
:map (fn [a b] (diff-associative a b (set/union (keys a) (keys b)))))
a b))
(defn diff
"Recursively compares a and b, returning a tuple of
[things-only-in-a things-only-in-b things-in-both].
Comparison rules:
* For equal a and b, return [nil nil a].
* Maps are subdiffed where keys match and values differ.
* Sets are never subdiffed.
* All sequential things are treated as associative collections
by their indexes, with results returned as vectors.
* Everything else (including strings!) is treated as
an atom and compared for equality."
[a b]
(if (= a b)
[nil nil a]
(if (= (equality-partition a) (equality-partition b))
(diff-similar a b)
(atom-diff a b))))

69
stdlib/clojure/edn.clj Normal file
View file

@ -0,0 +1,69 @@
;; clojure.edn — reading EDN data. Delegates to the Jolt reader via
;; clojure.core/read-string (which parses, never evaluates — safe for EDN), and
;; adds the opts-map arity with :eof plus nil/blank-input handling.
(ns clojure.edn
"Reading EDN data."
(:require [clojure.string :as cstr]))
;; The reader yields set literals as a FORM ({:jolt/type :jolt/set :value [...]})
;; rather than a constructed set, so build the actual values, recursing into
;; maps/vectors/lists. (Lists stay lists — EDN never evaluates them as code.)
(defn- edn->value [opts x]
(cond
;; Reader FORMS are detected by :jolt/type tag, never by map? — strict map?
;; (correctly) excludes tagged structs, so the old (and (map? x) ...) guard
;; would skip them.
(= :jolt/set (get x :jolt/type)) (set (map (fn [v] (edn->value opts v)) (get x :value)))
;; Tagged elements: a reader from the :readers opt wins, then the built-in
;; data readers (#uuid/#inst + registered); an unknown tag falls to the
;; :default opt fn (called with tag and value, as in Clojure) or throws.
(= :jolt/tagged (get x :jolt/type))
(let [tag (get x :tag)
v (edn->value opts (get x :form))
;; the reader stores the tag as a :#name keyword; :readers maps are
;; keyed by the SYMBOL (Clojure's shape) — normalize for lookup
tag-sym (let [n (name tag)]
(symbol (if (= "#" (subs n 0 1)) (subs n 1) n)))
custom (get (get opts :readers) tag-sym)]
(cond
custom (custom v)
(get opts :default) ((get opts :default) tag v)
:else (__read-tagged tag v)))
(map? x)
(into {} (map (fn [e] [(edn->value opts (key e)) (edn->value opts (val e))]) x))
(vector? x) (mapv (fn [v] (edn->value opts v)) x)
(seq? x) (map (fn [v] (edn->value opts v)) x)
:else x))
;; Private helper, NOT named read-string: an unqualified (read-string …) call
;; dispatches the core read-string SPECIAL FORM (by name, regardless of ns), so
;; the 1-arity can't delegate to the 2-arity through that name.
(defn- read-edn [opts s]
(if (or (nil? s) (cstr/blank? s))
(get opts :eof nil)
(edn->value opts (clojure.core/read-string s))))
(defn read-string
"Reads one object from the string s. Returns the :eof option value (default
nil) for nil or blank input. opts is an options map; :eof sets the value
returned at end of input."
([s] (read-edn {} s))
([opts s] (read-edn opts s)))
(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 file
handle is read whole."
[reader]
(if (= :core/file (janet/type reader))
(janet.file/read reader :all)
(loop [acc (transient []) c (.read reader)]
(if (== -1 c)
(apply str (map char (persistent! acc)))
(recur (conj! acc c) (.read reader))))))
(defn read
"Reads one EDN object from reader (a PushbackReader or any jolt reader).
Returns the :eof option value (default nil) at end of input."
([reader] (read {} reader))
([opts reader] (read-edn opts (drain-reader reader))))

33
stdlib/clojure/pprint.clj Normal file
View file

@ -0,0 +1,33 @@
;; clojure.pprint — minimal jolt shim.
;;
;; The real clojure.pprint is a full pretty-printer (thousands of lines of column
;; tracking / dispatch tables). This shim provides just the surface that portable
;; libraries reach for when they "pretty print" a value — notably
;; clojure.tools.logging/spy (pprint + with-pprint-dispatch + code-dispatch).
;; pprint writes the value readably with a trailing newline (no pretty layout);
;; the dispatch vars are recognized but not used for layout.
(ns clojure.pprint)
(def code-dispatch
"Recognized but not used for layout on jolt." :code-dispatch)
(def simple-dispatch
"Recognized but not used for layout on jolt." :simple-dispatch)
(def ^:dynamic *print-pprint-dispatch* simple-dispatch)
(defn pprint
"Print object readably followed by a newline. Not a pretty-printer on jolt
no column-aware layout. jolt routes all printing through the host output seam
(with-out-str captures it), and *out* is not a bindable var, so an explicit
writer arg is accepted for API compatibility but not honored output goes to
the current output. (The old `(binding [*out* writer] ...)` never redirected
anything either, and made the defn fall back to the interpreter; dropping it
lets pprint compile cleanly, which the no-fallback Chez back end requires.)"
([object] (prn object))
([object _writer] (prn object)))
(defmacro with-pprint-dispatch
"Evaluate body with the given pprint dispatch selected. On jolt the dispatch is
recognized but does not affect layout, so this just evaluates body."
[_dispatch & body]
`(do ~@body))

View file

@ -0,0 +1,49 @@
;; Forward-declaration stubs for SCI's host-layer modules.
;;
;; sci.impl.{parser,read,load,macroexpand,resolve,proxy,reify} implement SCI's
;; reader, loader, macroexpander and JVM proxy/reify support. On the JVM those
;; lean on tools.reader, java.io and reflection; Jolt has its own native
;; reader/loader, so rather than load SCI's host implementation we provide just
;; the var names that sci.impl.namespaces references when it builds the
;; clojure.core binding map. The map is built at load time but these are only
;; *called* through SCI's own runtime, which Jolt replaces — so no-op
;; placeholders are sufficient. (They must be non-nil so `:refer` re-interns
;; them: interning a nil-valued var drops the binding.)
(ns sci.impl.parser)
(defn parse-next [& _] nil)
(def data-readers {})
(defn default-data-reader-fn [& _] nil)
(defn read-eval [& _] nil)
(defn reader-resolver [& _] nil)
(defn suppress-read [& _] nil)
(ns sci.impl.read)
(defn read [& _] nil)
(defn read-string [& _] nil)
(defn source-logging-reader [& _] nil)
(ns sci.impl.load)
(defn load-string [& _] nil)
(defn load-reader [& _] nil)
(defn add-loaded-lib [& _] nil)
(defn eval-refer [& _] nil)
(defn eval-refer-global [& _] nil)
(defn eval-require [& _] nil)
(defn eval-require-global [& _] nil)
(defn eval-use [& _] nil)
(ns sci.impl.macroexpand)
(defn macroexpand [& _] nil)
(defn macroexpand-1 [& _] nil)
(ns sci.impl.resolve)
(defn resolve-symbol [& _] nil)
(ns sci.impl.proxy)
(defn proxy [& _] nil)
(defn proxy* [& _] nil)
(ns sci.impl.reify)
(defn reify [& _] nil)
(defn reify* [& _] nil)

View file

@ -0,0 +1,36 @@
; Minimal sci.impl.io stub for the Jolt bootstrap.
; SCI's real io.cljc needs JVM/CLJS-only features (System/getenv, *print-meta*,
; thread-local *out* binding). Jolt has native I/O, so we provide just the names
; that sci.impl.namespaces references when building the clojure.core binding map,
; backed by Jolt's native equivalents. (Only references symbols Jolt defines.)
(ns sci.impl.io)
(def out (atom nil))
(def in (atom nil))
(def err (atom nil))
(def newline (fn newline [] (clojure.core/pr "\n") nil))
(def flush (fn flush [] nil))
(def pr clojure.core/pr)
(def prn clojure.core/prn)
(def print clojure.core/print)
(def println clojure.core/println)
(def pr-str clojure.core/pr-str)
(def prn-str clojure.core/prn-str)
(def print-str clojure.core/str)
(def println-str clojure.core/println-str)
(def printf (fn printf [fmt & args] (clojure.core/print (apply clojure.core/format fmt args)) nil))
(def print-simple (fn print-simple [o w] (clojure.core/pr o) nil))
(def read-line (fn read-line [] nil))
(def with-out-str (fn with-out-str [& body] ""))
(def with-in-str (fn with-in-str [s & body] nil))
(def print-dup-var (atom false))
(def print-meta (atom false))
(def print-readably (atom true))
(def print-length (atom nil))
(def print-level (atom nil))
(def print-namespace-maps (atom false))
(def print-newline (atom true))
(def flush-on-newline (atom true))
(def print-fn (atom nil))
(def print-err-fn (atom nil))
(def file (atom nil))

View file

@ -0,0 +1,32 @@
; Minimal SCI type stubs for Jolt bootstrap
; Provides the deftype definitions that SCI's Clojure source references
; before the full SCI runtime is loaded.
; Protocol stubs for SCI's type system
(defprotocol IBox (setVal [this v]) (getVal [this]))
(defprotocol HasName (getName [this]))
(defprotocol IVar (bindRoot [this v]) (getRawRoot [this]) (toSymbol [this])
(isMacro [this]) (setThreadBound [this v]) (unbind [this]) (hasRoot [this]))
(defprotocol DynVar (dynamic? [this]))
(defprotocol IReified (getInterfaces [this]) (getMethods [this])
(getProtocols [this]) (getFields [this]))
; Unbound sentinel for vars
(deftype SciUnbound [the-var])
; Core SCI types — keep minimal for bootstrap
(deftype Namespace [name mappings aliases imports])
(deftype Var [root name meta macro dynamic ns])
; Store stub (sci.ctx-store provides a global context atom)
(def ctx-store (atom nil))
; Macro helpers from sci.impl.macros
(defn deftime [& body] nil)
(defn usetime [& body] (eval (first body)))
(defmacro ? [& args]
(if (contains? &env (quote &env))
(let [form (first args)]
(if (= :clj (first form))
(second form)
(if (= :cljs (first form)) nil)))))

83
stdlib/clojure/set.clj Normal file
View file

@ -0,0 +1,83 @@
; Jolt Standard Library: clojure.set
; Set operations. Note: no & rest arities (evaluator limitation).
(defn union
([s1] s1)
([s1 s2] (reduce conj s2 s1)))
(defn intersection
([s1] s1)
([s1 s2]
(reduce (fn [acc item] (if (contains? s2 item) acc (disj acc item))) s1 s1)))
(defn difference
([s1] s1)
([s1 s2] (reduce disj s1 s2)))
(defn select
[pred s]
(reduce (fn [acc item] (if (pred item) acc (disj acc item))) s s))
(defn project
[xrel ks]
(set (map #(select-keys % ks) xrel)))
(defn rename-keys
[map kmap]
(reduce (fn [m [old new]]
(if (contains? map old)
(assoc m new (get map old))
m))
(apply dissoc map (keys kmap))
kmap))
(defn map-invert
[m]
(reduce (fn [acc [k v]] (assoc acc v k)) {} m))
(defn rename
[xrel kmap]
(set (map (fn [m]
(reduce (fn [acc [old new]] (if (contains? m old) (assoc acc new (get m old)) acc))
(apply dissoc m (keys kmap)) kmap)) xrel)))
(defn index
[xrel ks]
(reduce (fn [m x] (let [ik (select-keys x ks)] (assoc m ik (conj (get m ik #{}) x)))) {} xrel))
(defn join
"When passed 2 rels, returns the rel corresponding to the natural join.
When passed an additional keymap, joins on the corresponding keys."
([xrel yrel]
(if (and (seq xrel) (seq yrel))
(let [ks (intersection (set (keys (first xrel))) (set (keys (first yrel))))
[r s] (if (<= (count xrel) (count yrel)) [xrel yrel] [yrel xrel])
idx (index r ks)]
(reduce (fn [ret x]
(let [found (idx (select-keys x ks))]
(if found
(reduce (fn [acc y] (conj acc (merge y x))) ret found)
ret)))
#{} s))
#{}))
([xrel yrel km]
(let [[r s k] (if (<= (count xrel) (count yrel))
[xrel yrel (map-invert km)]
[yrel xrel km])
idx (index r (vals k))]
(reduce (fn [ret x]
(let [found (idx (rename-keys (select-keys x (keys k)) k))]
(if found
(reduce (fn [acc y] (conj acc (merge y x))) ret found)
ret)))
#{} s))))
(defn subset?
[set1 set2]
(and (<= (count set1) (count set2))
(every? #(contains? set2 %) set1)))
(defn superset?
[set1 set2]
(and (>= (count set1) (count set2))
(every? #(contains? set1 %) set2)))

157
stdlib/clojure/string.clj Normal file
View file

@ -0,0 +1,157 @@
; Jolt Standard Library: clojure.string
; String manipulation functions using Jolt core string interop.
(defn blank?
[s]
(if (nil? s) true
(= 0 (count (str-trim s)))))
(defn capitalize
[s]
(if (< 1 (count s))
(str (str-upper (subs s 0 1))
(str-lower (subs s 1)))
(str-upper s)))
(defn lower-case
[s]
(str-lower s))
(defn upper-case
[s]
(str-upper s))
(defn includes?
[s substr]
(not (nil? (str-find substr s))))
(defn join
([coll] (str-join coll))
([separator coll] (str-join coll separator)))
(defn replace
[s match replacement]
(str-replace-all match replacement s))
(defn replace-first
[s match replacement]
(str-replace match replacement s))
(defn reverse
[s]
(str-reverse-b s))
(defn str-reverse
[s]
(str-reverse-b s))
(defn split
([s re] (split s re 0))
([s re limit]
;; Java Pattern.split semantics: limit > 0 caps the parts (trailing empties
;; kept); limit < 0 splits fully and keeps trailing empties; limit 0 (the
;; default) splits fully then drops trailing empty strings — but a no-match
;; result ([input], the only 1-element case) is returned as-is.
(let [parts (vec (str-split re s (if (pos? limit) limit nil)))]
(if (and (zero? limit) (> (count parts) 1))
(loop [v parts] (if (and (seq v) (= "" (peek v))) (recur (pop v)) v))
parts))))
(defn split-lines
"Split s on \\n or \\r\\n, returning a vector of lines."
[s]
(vec (str-split #"\r?\n" s)))
(defn starts-with?
[s substr]
(let [slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s 0 slen2) substr))))
(defn ends-with?
[s substr]
(let [slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s (- slen slen2)) substr))))
(defn trim
[s]
(str-trim s))
(defn triml
[s]
(str-triml s))
(defn trimr
[s]
(str-trimr s))
(defn trim-newline
[s]
(var result s)
(while (or (= (subs result (dec (count result))) "\n")
(= (subs result (dec (count result))) "\r"))
(set result (subs result 0 (dec (count result)))))
result)
(defn escape
[s cmap]
(apply str
(map (fn [ch]
(if-let [rep (cmap ch)] rep (str ch)))
s)))
(defn index-of
"0-based index of the first occurrence of value in s, or nil."
([s value]
(str-find value s))
([s value from]
(let [idx (str-find value (subs s from))]
(when idx (+ from idx)))))
(defn last-index-of
([s value]
(let [r (str-reverse-b s) sval (str-reverse-b value)
idx (str-find sval r)]
(when idx (- (count s) (+ idx (count value))))))
([s value from]
(let [sub (subs s 0 from) r (str-reverse-b sub) sval (str-reverse-b value)
idx (str-find sval r)]
(when idx (- from (+ idx (count value)))))))
(defn re-quote-replacement
"Escape special characters (backslash and dollar) in a regex replacement
string so it is used literally rather than interpreted."
[replacement]
(apply str
(map (fn [ch]
(let [c (str ch)]
(if (or (= c "\\") (= c "$")) (str "\\" c) c)))
(seq replacement))))
;; Ported from clojure.string/trim-newline (CharSequence interop replaced with
;; portable count/subs). Removes all trailing \n or \r characters.
(defn trim-newline
"Removes all trailing newline \\n or return \\r characters from
string. Similar to Perl's chomp."
[s]
(loop [index (count s)]
(if (zero? index)
""
(let [c (subs s (dec index) index)]
(if (or (= c "\n") (= c "\r"))
(recur (dec index))
(subs s 0 index))))))

View file

@ -0,0 +1,33 @@
;; Verbatim from clojure.template (Stuart Sierra) — pure Clojure over
;; clojure.walk, which jolt ships. Added so honeysql's :clj branch (which
;; requires clojure.template) loads under JOLT_FEATURES including clj.
(ns clojure.template
"Macros that expand to repeated copies of a template expression."
(:require [clojure.walk :as walk]))
(defn apply-template
"For use in macros. argv is an argument list, as in defn. expr is
a quoted expression using the symbols in argv. values is a sequence
of values to be used for the arguments.
apply-template will recursively replace argument symbols in expr
with their corresponding values, returning a modified expr.
Example: (apply-template '[x] '(+ x x) '[2])
;=> (+ 2 2)"
[argv expr values]
(assert (vector? argv))
(assert (every? symbol? argv))
(walk/postwalk-replace (zipmap argv values) expr))
(defmacro do-template
"Repeatedly copies expr (in a do block) for each group of arguments
in values. values are automatically partitioned by the number of
arguments in argv, an argument vector as in defn.
Example: (macroexpand '(do-template [x y] (+ y x) 2 4 3 5))
;=> (do (+ 4 2) (+ 5 3))"
[argv expr & values]
(let [c (count argv)]
`(do ~@(map (fn [a] (apply-template argv expr a))
(partition c values)))))

171
stdlib/clojure/test.clj Normal file
View file

@ -0,0 +1,171 @@
; Jolt Standard Library: clojure.test
;
; A practical subset of clojure.test for running real test suites under Jolt:
; deftest / is / testing / are / use-fixtures / run-tests, with class-aware
; (thrown? Class body) and (thrown-with-msg? Class re body) inside `is`. Class
; matching is by simple-name (last dotted segment), since Jolt has no JVM class
; objects — Exception/Throwable match any thrown value.
;
; Also exposes the counter/registry API the internal clojure-test-suite harness
; uses (reset-report!, run-registered, n-pass/n-fail/n-error, failures), so this
; is a drop-in superset.
(ns clojure.test
(:require [clojure.string :as str]))
;; --- state -----------------------------------------------------------------
(def counters (atom {:test 0 :pass 0 :fail 0 :error 0 :fails []}))
(def jolt-report counters) ;; alias used by the suite harness
(def ctx-stack (atom []))
(def registry (atom [])) ;; [{:name sym :fn thunk}]
(def once-fixtures (atom []))
(def each-fixtures (atom []))
(defn reset-report! []
(reset! counters {:test 0 :pass 0 :fail 0 :error 0 :fails []})
(reset! ctx-stack [])
(reset! registry [])
(reset! once-fixtures [])
(reset! each-fixtures []))
(defn- ctx-str [] (str/join " " @ctx-stack))
(defn inc-pass! [] (swap! counters update :pass inc))
(defn fail! [form]
(let [line (str (ctx-str) (when (seq @ctx-stack) " ") "FAIL: " form)]
(swap! counters (fn [r] (-> r (update :fail inc) (update :fails conj line))))
(println line)))
(defn err! [form]
(let [line (str (ctx-str) (when (seq @ctx-stack) " ") "ERROR: " form)]
(swap! counters (fn [r] (-> r (update :error inc) (update :fails conj line))))
(println line)))
(defn n-pass [] (:pass @counters))
(defn n-fail [] (:fail @counters))
(defn n-error [] (:error @counters))
(defn failures [] (:fails @counters))
;; clojure.test/report multimethod — present so suites that add reporting
;; methods (defmethod clojure.test/report :begin-test-var ...) load. The runner
;; below does its own console output and doesn't dispatch through it.
(defmulti report :type)
(defmethod report :default [_m] nil)
;; --- class matching for thrown? --------------------------------------------
(defn- last-seg [s]
(let [s (str s)
i (str/last-index-of s ".")]
(if i (subs s (inc i)) s)))
(defn class-match?
"True if thrown value `e` matches the wanted class simple-name `wanted`.
Exception/Throwable match anything."
[e wanted]
(let [w (last-seg wanted)]
(if (or (= w "Exception") (= w "Throwable"))
true
(let [c (class e)]
(and (string? c) (= (last-seg c) w))))))
;; --- assertion macros ------------------------------------------------------
(defn- thrown-form? [form sym]
(and (seq? form) (symbol? (first form)) (= sym (name (first form)))))
(defmacro is
([form] `(is ~form nil))
([form msg]
(cond
;; (is (thrown? Class body...))
(thrown-form? form "thrown?")
(let [klass (name (second form))
body (nthrest form 2)]
`(try
~@body
(clojure.test/fail! (str "expected " '~form " to throw" (when ~msg (str " — " ~msg))))
(catch Throwable e#
(if (clojure.test/class-match? e# ~klass)
(clojure.test/inc-pass!)
(clojure.test/fail! (str "expected throw of " ~klass " but got " (clojure.core/class e#)))))))
;; (is (thrown-with-msg? Class re body...))
(thrown-form? form "thrown-with-msg?")
(let [klass (name (second form))
re (nth form 2)
body (nthrest form 3)]
`(try
~@body
(clojure.test/fail! (str "expected " '~form " to throw"))
(catch Throwable e#
(let [m# (or (clojure.core/ex-message e#) (str e#))]
(if (and (clojure.test/class-match? e# ~klass) (re-find ~re m#))
(clojure.test/inc-pass!)
(clojure.test/fail! (str "expected throw of " ~klass " matching " ~re " but got " (clojure.core/class e#) ": " m#)))))))
:else
`(try
(if ~form
(clojure.test/inc-pass!)
(clojure.test/fail! (str (pr-str '~form) (when ~msg (str " — " ~msg)))))
(catch Throwable e#
(clojure.test/err! (str (pr-str '~form) " threw: " (clojure.core/ex-message e#))))))))
(defmacro testing [s & body]
`(do
(swap! clojure.test/ctx-stack conj ~s)
(try
(do ~@body)
(finally (swap! clojure.test/ctx-stack pop)))))
(defmacro deftest [name & body]
`(do
(defn ~name [] ~@body)
(swap! clojure.test/registry conj {:name '~name :fn ~name})
(var ~name)))
(defmacro are [argv expr & data]
(let [n (count argv)
rows (partition n data)]
`(do ~@(map (fn [row]
`(let [~@(interleave argv row)]
(clojure.test/is ~expr)))
rows))))
;; --- fixtures + run --------------------------------------------------------
(defn use-fixtures [kind & fns]
(cond
(= kind :once) (reset! once-fixtures (vec fns))
(= kind :each) (reset! each-fixtures (vec fns))))
(defn- wrap-fixtures [fixtures body-fn]
(if (empty? fixtures)
(body-fn)
((first fixtures) (fn [] (wrap-fixtures (rest fixtures) body-fn)))))
(defn- run-one [t]
(swap! counters update :test inc)
(wrap-fixtures @each-fixtures
(fn []
(try
((:fn t))
(catch Throwable e
(err! (str (:name t) " crashed: " (clojure.core/ex-message e))))))))
(defn run-registered []
(doseq [t @registry] (run-one t))
nil)
(defn run-tests [& _nses]
(wrap-fixtures @once-fixtures (fn [] (run-registered)))
(let [r @counters]
(println)
(println (str "Ran " (:test r) " tests. "
(:pass r) " assertions passed, "
(:fail r) " failures, " (:error r) " errors."))
r))
(defn run-test [& _] nil)
(defn test-var [& _] nil)

52
stdlib/clojure/walk.clj Normal file
View file

@ -0,0 +1,52 @@
; Jolt Standard Library: clojure.walk
; Tree walking for Clojure data structures.
(defn walk
[inner outer form]
(cond
; vectors/maps first so seq? can't swallow them (a vector is not seq? on
; jolt, but keep the concrete branches authoritative)
(vector? form) (outer (vec (map inner form)))
(map? form) (outer (into (empty form) (map inner form)))
; lists rebuild as lists, other seqs (incl. macro/template output: cons/
; concat/lazy-seq) walk too — without this, postwalk-replace silently no-op'd
; a quoted list, breaking clojure.template/apply-template
(list? form) (outer (apply list (map inner form)))
(seq? form) (outer (map inner form))
:else (outer form)))
(defn postwalk
[f form]
(walk (partial postwalk f) f form))
(defn prewalk
[f form]
(walk (partial prewalk f) identity (f form)))
(defn postwalk-demo
"Demonstrates the behavior of postwalk by printing each form as it is walked."
[form]
(postwalk (fn [x] (print "Walked: ") (prn x) x) form))
(defn prewalk-demo
"Demonstrates the behavior of prewalk by printing each form as it is walked."
[form]
(prewalk (fn [x] (print "Walked: ") (prn x) x) form))
(defn postwalk-replace
[smap form]
(postwalk (fn [x] (if (contains? smap x) (get smap x) x)) form))
(defn prewalk-replace
[smap form]
(prewalk (fn [x] (if (contains? smap x) (get smap x) x)) form))
(defn keywordize-keys
[m]
(let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn stringify-keys
[m]
(let [f (fn [[k v]] (if (keyword? k) [(name k) v] [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))

177
stdlib/clojure/zip.clj Normal file
View file

@ -0,0 +1,177 @@
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; Ported from clojure.zip (Rich Hickey). A loc is a vector [node path] carrying
;; the zipper fns (:zip/branch? :zip/children :zip/make-node) as metadata. The
;; reference indexes a loc with (loc 0)/(loc 1); Jolt uses (nth loc ...) because a
;; metadata-bearing vector is not currently invocable as a fn.
(ns clojure.zip
"Functional hierarchical zipper, with navigation, editing, and enumeration.")
(defn zipper
"Creates a new zipper structure. branch? is a fn that, given a node, returns
true if it can have children. children returns a seq of a branch node's
children. make-node, given an existing node and a seq of children, returns a
new branch node. root is the root node."
[branch? children make-node root]
(with-meta [root nil]
{:zip/branch? branch? :zip/children children :zip/make-node make-node}))
(defn seq-zip
"Returns a zipper for nested sequences, given a root sequence"
[root]
(zipper seq? identity (fn [node children] (with-meta children (meta node))) root))
(defn vector-zip
"Returns a zipper for nested vectors, given a root vector"
[root]
(zipper vector? seq (fn [node children] (with-meta (vec children) (meta node))) root))
(defn node "Returns the node at loc" [loc] (nth loc 0))
(defn branch? "Returns true if the node at loc is a branch"
[loc] ((:zip/branch? (meta loc)) (node loc)))
(defn children "Returns a seq of the children of node at loc, which must be a branch"
[loc]
(if (branch? loc)
((:zip/children (meta loc)) (node loc))
(throw "called children on a leaf node")))
(defn make-node "Returns a new branch node, given an existing node and new children."
[loc node children] ((:zip/make-node (meta loc)) node children))
(defn path "Returns a seq of nodes leading to this loc" [loc] (:pnodes (nth loc 1)))
(defn lefts "Returns a seq of the left siblings of this loc" [loc] (seq (:l (nth loc 1))))
(defn rights "Returns a seq of the right siblings of this loc" [loc] (:r (nth loc 1)))
(defn down "Returns the loc of the leftmost child of the node at this loc, or nil"
[loc]
(when (branch? loc)
(let [[node path] loc
[c & cnext :as cs] (children loc)]
(when cs
(with-meta [c {:l []
:pnodes (if path (conj (:pnodes path) node) [node])
:ppath path
:r cnext}]
(meta loc))))))
(defn up "Returns the loc of the parent of the node at this loc, or nil if at the top"
[loc]
(let [[node {l :l, ppath :ppath, pnodes :pnodes, r :r, changed? :changed?, :as path}] loc]
(when pnodes
(let [pnode (peek pnodes)]
(with-meta (if changed?
[(make-node loc pnode (concat l (cons node r)))
(and ppath (assoc ppath :changed? true))]
[pnode ppath])
(meta loc))))))
(defn root "Zips all the way up and returns the root node, reflecting any changes."
[loc]
(if (= :end (nth loc 1))
(node loc)
(let [p (up loc)]
(if p (recur p) (node loc)))))
(defn right "Returns the loc of the right sibling of the node at this loc, or nil"
[loc]
(let [[node {l :l, [r & rnext :as rs] :r, :as path}] loc]
(when (and path rs)
(with-meta [r (assoc path :l (conj l node) :r rnext)] (meta loc)))))
(defn rightmost "Returns the loc of the rightmost sibling of the node at this loc, or self"
[loc]
(let [[node {l :l r :r :as path}] loc]
(if (and path r)
(with-meta [(last r) (assoc path :l (apply conj l node (butlast r)) :r nil)] (meta loc))
loc)))
(defn left "Returns the loc of the left sibling of the node at this loc, or nil"
[loc]
(let [[node {l :l r :r :as path}] loc]
(when (and path (seq l))
(with-meta [(peek l) (assoc path :l (pop l) :r (cons node r))] (meta loc)))))
(defn leftmost "Returns the loc of the leftmost sibling of the node at this loc, or self"
[loc]
(let [[node {l :l r :r :as path}] loc]
(if (and path (seq l))
(with-meta [(first l) (assoc path :l [] :r (concat (rest l) [node] r))] (meta loc))
loc)))
(defn insert-left "Inserts the item as the left sibling of the node at this loc, without moving"
[loc item]
(let [[node {l :l :as path}] loc]
(if (nil? path)
(throw "Insert at top")
(with-meta [node (assoc path :l (conj l item) :changed? true)] (meta loc)))))
(defn insert-right "Inserts the item as the right sibling of the node at this loc, without moving"
[loc item]
(let [[node {r :r :as path}] loc]
(if (nil? path)
(throw "Insert at top")
(with-meta [node (assoc path :r (cons item r) :changed? true)] (meta loc)))))
(defn replace "Replaces the node at this loc, without moving"
[loc node]
(let [[_ path] loc]
(with-meta [node (assoc path :changed? true)] (meta loc))))
(defn edit "Replaces the node at this loc with the value of (f node args)"
[loc f & args]
(replace loc (apply f (node loc) args)))
(defn insert-child "Inserts the item as the leftmost child of the node at this loc, without moving"
[loc item]
(replace loc (make-node loc (node loc) (cons item (children loc)))))
(defn append-child "Inserts the item as the rightmost child of the node at this loc, without moving"
[loc item]
(replace loc (make-node loc (node loc) (concat (children loc) [item]))))
(defn next
"Moves to the next loc in the hierarchy, depth-first. At the end, returns a
distinguished loc detectable via end?; if already at the end, stays there."
[loc]
(if (= :end (nth loc 1))
loc
(or
(and (branch? loc) (down loc))
(right loc)
(loop [p loc]
(if (up p)
(or (right (up p)) (recur (up p)))
[(node p) :end])))))
(defn prev
"Moves to the previous loc in the hierarchy, depth-first. At the root, returns nil."
[loc]
(if-let [lloc (left loc)]
(loop [loc lloc]
(if-let [child (and (branch? loc) (down loc))]
(recur (rightmost child))
loc))
(up loc)))
(defn end? "Returns true if loc represents the end of a depth-first walk"
[loc] (= :end (nth loc 1)))
(defn remove
"Removes the node at loc, returning the loc that would have preceded it in a
depth-first walk."
[loc]
(let [[node {l :l, ppath :ppath, pnodes :pnodes, rs :r, :as path}] loc]
(if (nil? path)
(throw "Remove at top")
(if (pos? (count l))
(loop [loc (with-meta [(peek l) (assoc path :l (pop l) :changed? true)] (meta loc))]
(if-let [child (and (branch? loc) (down loc))]
(recur (rightmost child))
loc))
(with-meta [(make-node loc (peek pnodes) rs)
(and ppath (assoc ppath :changed? true))]
(meta loc))))))

35
stdlib/jolt/ffi.clj Normal file
View file

@ -0,0 +1,35 @@
(ns jolt.ffi
"Foreign-function interface for jolt libraries. A library loads a shared object
and declares typed foreign functions, then exposes a Clojure API over them no
jolt built-in required.
(require '[jolt.ffi :as ffi])
(ffi/load-library {:darwin \"libsqlite3.0.dylib\" :linux \"libsqlite3.so.0\"})
(ffi/defcfn sqlite3-open \"sqlite3_open\" [:string :pointer] :int)
(let [pp (ffi/alloc (ffi/sizeof :pointer))]
(sqlite3-open \"x.db\" pp)
(let [db (ffi/read pp :pointer)] ...)
(ffi/free pp))
Types (keywords): :int :uint :long :ulong :int64 :uint64 :size_t :ssize_t
:iptr :uptr :double :float :pointer :string :void :uint8 :char.
The memory/library primitives (alloc/free/read/write/sizeof/load-library/
ptr->string/string->ptr/null/null?) are provided by the host. foreign-fn lowers
a compile-time-typed signature to a real Chez foreign-procedure.")
;; foreign-fn binds C symbol `csym` to a typed callable. Expands to the __cfn
;; special form (always fully-qualified, so an :as alias on jolt.ffi resolves):
;; the analyzer/back end turn it into a Chez foreign-procedure.
;; An optional trailing :blocking marks a call that may block (accept/recv/...),
;; so it's emitted collect-safe and won't pin the garbage collector.
(defmacro foreign-fn [csym argtypes rettype & [opt]]
(if (= opt :blocking)
(list 'jolt.ffi/__cfn csym argtypes rettype :blocking)
(list 'jolt.ffi/__cfn csym argtypes rettype)))
;; (defcfn name "c_symbol" [argtypes] rettype [:blocking]) — def a foreign function.
(defmacro defcfn [name csym argtypes rettype & [opt]]
(list 'def name (if (= opt :blocking)
(list 'jolt.ffi/__cfn csym argtypes rettype :blocking)
(list 'jolt.ffi/__cfn csym argtypes rettype))))