# [GitHub - yogthos/jolt: A Clojure interpreter running on Janet](https://github.com/yogthos/jolt/)
A Clojure interpreter running on Janet. Jolt reads Clojure source, evaluates it with an interpreter written in pure Janet, and ships a Clojure-compatible standard library. The goal is a Janet-hosted SCI runtime — a minimal bootstrap that loads SCI's Clojure source as its standard library.
## Build
git clone https://github.com/yogthos/jolt.git cd jolt git submodule update --init # pulls vendor/sci jpm build # compiles build/jolt
Requires Janet ≥ 1.36 and jpm.
## Run
build/jolt # start a REPL build/jolt file.clj [args] # run a file (binds *command-line-args* and *file*) build/jolt -e EXPR [args] # evaluate EXPR and print the result build/jolt -h # help
The REPL accumulates multi-line forms until they balance:
user=> (defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) #'user/fib user=> (map fib (range 10)) (0 1 1 2 3 5 8 13 21 34)
Running a file evaluates its top-level forms:
$ echo '(println "hello" (* 6 7))' > hello.clj $ build/jolt hello.clj hello 42
## Use as a library
(use jolt/api) (def ctx (init)) (eval-string ctx "(+ 1 2)") # → 3 (eval-string ctx "(map inc [1 2 3])") # → [2 3 4]
(init) returns a context with clojure.core loaded. Each context is isolated; use separate contexts for separate environments.
## Host interop
Jolt exposes CLJS-style host interop through . on any Janet table or struct — a field holding a function is called with the receiver as the first argument:
(def obj {:greet (fn [self name] (str "Hello " name))}) (. obj greet "Alice") ; → "Hello Alice" (.-greet obj) ; field access (reader sugar for (. obj :greet))
Janet's standard library is reachable through jolt.interop (and the jolt.shell / jolt.http helpers built on it):
(require '[jolt.interop :as j]) (j/janet-type [1 2]) ; → :tuple (j/janet-table-keys {:a 1 :b 2}) ; → [:b :a]
## Differences from Clojure
Jolt targets Clojure semantics but runs on Janet, not the JVM. The notable divergences:
- - Host platform. No JVM and no Java interop — import, gen-class, proxy of Java classes, and java.* are unavailable. instance? recognizes a small set of built-in types (clojure.lang.Atom, Number, String, …).
- - Numbers. Janet integers and doubles. (/ 1 3) is 0.3333… and large products lose precision. No ratios or BigDecimal (ratio? is always false, bigdec falls back to a double); bigint/biginteger use Janet's 64-bit int/s64, not arbitrary precision. The auto-promoting +'/-'/*'/inc'/dec' are aliases for the plain ops, since Janet numbers don't overflow. quot/rem/mod follow Clojure's sign rules. The symbolic values ##Inf/##-Inf/##NaN read, and infinite?/NaN? work. Janet represents an integer and an integer-valued double identically, so 1 and 1.0 are indistinguishable: (float?/double? 1.0) is false and (int? 1.0) is true — float?/double? are true only for values with a fractional part or ##Inf/##NaN.
- - Collections. By default Jolt uses immutable persistent data structures: vectors are 32-way branching tries (structural-sharing persistent vectors with O(log₃₂ n) conj/assoc/nth), lists are persistent singly-linked cons cells (O(1) conj/cons prepend with structural sharing), and maps/sets are persistent hash structures. Value equality and sequence operations are Clojure-compatible, but hash-map/hash-set iteration order is unspecified and differs from Clojure — use sorted-map/sorted-set when order matters.
…
jolt: Clojure interpreter on Janet
https://github.com/yogthos/jolt/ Lobsters: https://lobste.rs/s/gefcox/jolt_clojure_interpreter_on_janet