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:
parent
c18f8087f0
commit
33eff7c7d8
112 changed files with 970 additions and 1621 deletions
98
stdlib/clojure/data.clj
Normal file
98
stdlib/clojure/data.clj
Normal 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
69
stdlib/clojure/edn.clj
Normal 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
33
stdlib/clojure/pprint.clj
Normal 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))
|
||||
49
stdlib/clojure/sci/host_stubs.clj
Normal file
49
stdlib/clojure/sci/host_stubs.clj
Normal 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)
|
||||
36
stdlib/clojure/sci/io_stubs.clj
Normal file
36
stdlib/clojure/sci/io_stubs.clj
Normal 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))
|
||||
32
stdlib/clojure/sci/lang_stubs.clj
Normal file
32
stdlib/clojure/sci/lang_stubs.clj
Normal 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
83
stdlib/clojure/set.clj
Normal 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
157
stdlib/clojure/string.clj
Normal 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))))))
|
||||
33
stdlib/clojure/template.clj
Normal file
33
stdlib/clojure/template.clj
Normal 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
171
stdlib/clojure/test.clj
Normal 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
52
stdlib/clojure/walk.clj
Normal 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
177
stdlib/clojure/zip.clj
Normal 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
35
stdlib/jolt/ffi.clj
Normal 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))))
|
||||
Loading…
Add table
Add a link
Reference in a new issue