docs: lean README — drop stale internals/project-structure, document divergences from Clojure (host/numbers/collections/STM/regex), fix interop examples to working calls
This commit is contained in:
parent
e87f6db10e
commit
f20df44d6a
2 changed files with 269 additions and 77 deletions
112
README.md
112
README.md
|
|
@ -1,58 +1,34 @@
|
||||||
# Jolt
|
# Jolt
|
||||||
|
|
||||||
A Clojure interpreter running on [Janet](https://janet-lang.org). Jolt reads Clojure source text, evaluates it using an interpreter written in pure Janet, and exposes a Clojure-compatible standard library. The goal is a Janet-hosted [SCI](https://github.com/borkdude/sci) runtime — minimal bootstrapping, with SCI as the standard library.
|
A Clojure interpreter running on [Janet](https://janet-lang.org). 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](https://github.com/borkdude/sci) runtime — a minimal bootstrap that loads SCI's Clojure source as its standard library.
|
||||||
|
|
||||||
## What's inside
|
## Build
|
||||||
|
|
||||||
Jolt implements the core of Clojure in a single-process Janet project:
|
|
||||||
|
|
||||||
**Reader** — A recursive descent parser for Clojure syntax: symbols, keywords, numbers, strings, characters, lists, vectors, maps, sets, quote forms, reader macros (`#()`, `#_`, `#?`), metadata, deref, and tagged literals.
|
|
||||||
|
|
||||||
**Evaluator** — A tree-walking interpreter with 22 special forms (`quote`, `do`, `if`, `def`, `defmacro`, `fn*`, `let*`, `loop*`/`recur`, `throw`, `try`, `set!`, `var`, `locking`, `instance?`, `defmulti`, `defmethod`, `deftype`, `new`, `.`, etc.), syntax-quote with unquote and unquote-splicing, a macro system with `&env` support, destructuring (`:keys` and sequential), and namespace forms (`ns`, `require`, `in-ns`).
|
|
||||||
|
|
||||||
**Core library** — 145+ bindings from `clojure.core`: predicates, math with Clojure arity semantics, comparison, collections, sequences, higher-order functions, string functions, I/O, atoms, macros (`when`, `when-not`, `if-let`, `when-let`, `if-some`, `when-some`, `doto`, `fn`, `let`, `defn`, `defrecord`, `defprotocol`), and SCI bootstrap stubs.
|
|
||||||
|
|
||||||
**SCI bootstrap** — All 317 forms from SCI's 9 core source files (`macros`, `protocols`, `types`, `unrestrict`, `vars`, `lang`, `utils`, `namespaces`, `core`) load with zero failures. 46 namespaces are populated with 900+ bindings. SCI's `eval-string` is replaced with a Jolt-native implementation.
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/yogthos/jolt.git
|
git clone https://github.com/yogthos/jolt.git
|
||||||
cd jolt
|
cd jolt
|
||||||
git submodule update --init # pulls vendor/sci
|
git submodule update --init # pulls vendor/sci
|
||||||
jpm build # compiles build/jolt
|
jpm build # compiles build/jolt
|
||||||
build/jolt # drops into REPL
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build
|
Requires Janet ≥ 1.36 and `jpm`.
|
||||||
|
|
||||||
```
|
|
||||||
jpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
This compiles `src/jolt/*.janet` into a standalone `build/jolt` executable. Requires Janet ≥ 1.36 and `jpm`.
|
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
```
|
```
|
||||||
build/jolt # start a REPL
|
build/jolt # start a REPL
|
||||||
build/jolt file.clj [args] # run a Clojure file (binds *command-line-args*)
|
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 -e EXPR [args] # evaluate EXPR and print the result
|
||||||
build/jolt -h # help
|
build/jolt -h # help
|
||||||
```
|
```
|
||||||
|
|
||||||
With no arguments it drops into a read-eval-print loop (multi-line forms are
|
The REPL accumulates multi-line forms until they balance:
|
||||||
accumulated until balanced):
|
|
||||||
|
|
||||||
```
|
```
|
||||||
user=> (+ 1 2)
|
|
||||||
3
|
|
||||||
user=> (map inc [1 2 3])
|
|
||||||
[2 3 4]
|
|
||||||
user=> (defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
|
user=> (defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
|
||||||
#'user/fib
|
#'user/fib
|
||||||
user=> (fib 10)
|
user=> (map fib (range 10))
|
||||||
55
|
(0 1 1 2 3 5 8 13 21 34)
|
||||||
```
|
```
|
||||||
|
|
||||||
Running a file evaluates its top-level forms:
|
Running a file evaluates its top-level forms:
|
||||||
|
|
@ -63,72 +39,54 @@ $ build/jolt hello.clj
|
||||||
hello 42
|
hello 42
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
```
|
|
||||||
jpm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Runs all tests: API, bootstrap, core, evaluator, macro, namespace, reader, types, and SCI load.
|
|
||||||
|
|
||||||
## Use as a library
|
## Use as a library
|
||||||
|
|
||||||
```janet
|
```janet
|
||||||
(use jolt/api)
|
(use jolt/api)
|
||||||
|
|
||||||
(def ctx (init))
|
(def ctx (init))
|
||||||
(eval-string ctx "(+ 1 2)") ;; → 3
|
(eval-string ctx "(+ 1 2)") # → 3
|
||||||
(eval-string ctx "(map inc [1 2 3])") ;; → [2 3 4]
|
(eval-string ctx "(map inc [1 2 3])") # → [2 3 4]
|
||||||
(eval-string ctx "(def x 42)") ;; → #'user/x
|
|
||||||
(eval-string ctx "x") ;; → 42
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`(init)` returns a context with `clojure.core` loaded. Pass it to `eval-string` to evaluate Clojure source. Each context is isolated — use separate contexts for separate evaluation environments.
|
`(init)` returns a context with `clojure.core` loaded. Each context is isolated; use separate contexts for separate environments.
|
||||||
|
|
||||||
## Janet-native interop
|
## Host interop
|
||||||
|
|
||||||
Jolt provides CLJS-style host interop through the `.` special form on any Janet table or struct:
|
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:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
;; Field access on tables and structs
|
(def obj {:greet (fn [self name] (str "Hello " name))})
|
||||||
user=> (def t {:a 1 :b 2})
|
(. obj greet "Alice") ; → "Hello Alice"
|
||||||
user=> (. t :a) ;; → 1
|
(.-greet obj) ; field access (reader sugar for (. obj :greet))
|
||||||
user=> (.-a t) ;; → 1 (reader sugar)
|
|
||||||
|
|
||||||
;; Method calls — self is passed as first arg
|
|
||||||
user=> (def obj {:greet (fn [self name] (str "Hello " name))})
|
|
||||||
user=> (. obj greet "Alice") ;; → "Hello Alice"
|
|
||||||
|
|
||||||
;; Multi-arg methods
|
|
||||||
user=> (def calc {:add (fn [_ a b] (+ a b))})
|
|
||||||
user=> (. calc add 3 4) ;; → 7
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Any table or struct field that holds a Janet function or C function can be called via `.` with implicit `self` dispatch. This pattern mirrors CLJS `.method` call semantics and unifies deftype protocol dispatch with plain Janet host interop.
|
Janet's standard library is reachable through `jolt.interop` (and the `jolt.shell` / `jolt.http` helpers built on it):
|
||||||
|
|
||||||
**Janet host functions** — Janet's standard library (`os/shell`, `net/request`, etc.) is accessible through Jolt's `jolt.interop` namespace:
|
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user=> (require '[jolt.interop :as j])
|
(require '[jolt.interop :as j])
|
||||||
user=> (j/janet-eval "(+ 1 2)") ;; → 3
|
(j/janet-type [1 2]) ; → :tuple
|
||||||
user=> (j/janet-table-keys {:a 1 :b 2}) ;; → [:a :b]
|
(j/janet-table-keys {:a 1 :b 2}) ; → [:b :a]
|
||||||
user=> (j/janet-describe "hello") ;; → Janet type info
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The existing `jolt.shell`, `jolt.http`, and `jolt.interop` modules demonstrate the pattern: Clojure functions call Janet C functions through the Jolt bridge.
|
## Differences from Clojure
|
||||||
|
|
||||||
## Project structure
|
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 only — no bignums, ratios, or `BigDecimal`. `(/ 1 3)` is `0.3333…`, large products lose precision, and there are no auto-promoting `+'`/`*'`. `quot`/`rem`/`mod` follow Clojure's sign rules. `bigint`, `rational?`, and `class` are not provided.
|
||||||
|
- **Collections.** Vectors are Janet tuples, lists are Janet arrays; maps and 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.
|
||||||
|
- **Concurrency / STM.** Single-threaded. No refs, `dosync`, agents, or `send`; `locking` evaluates its body without real locking. Atoms, volatiles, and delays are supported.
|
||||||
|
- **Regex.** Compiled to Janet's PEG engine (Janet has no regex). Supported: capturing groups (`[whole g1 …]`), greedy and lazy quantifiers with backtracking, `(?:…)`, lookahead `(?=…)`/`(?!…)`, alternation, anchors `^ $ \b \B`, character classes, and the `(?i)` flag. Not supported: lookbehind, backreferences (`\1`), and named groups (`(?<name>…)`).
|
||||||
|
- **Not implemented.** Transients (`transient`/`persistent!`), JVM reflection, and `proxy`. (`reify` and `extend-protocol` work for Jolt protocols.)
|
||||||
|
|
||||||
|
Supported and Clojure-compatible: chars as a distinct type, lazy/infinite sequences, transducers, destructuring, multimethods with hierarchies, protocols/records, metadata, namespaces, and the reader (`#()`, `#_`, `#?`, tagged literals, `#"…"`).
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
```
|
```
|
||||||
src/jolt/
|
jpm test # full test suite
|
||||||
types.janet — Var, Namespace, Context, symbol helpers
|
janet test/conformance.janet # Clojure-conformance battery
|
||||||
reader.janet — recursive descent parser for Clojure syntax
|
|
||||||
evaluator.janet — tree-walking interpreter
|
|
||||||
core.janet — 145+ clojure.core bindings
|
|
||||||
api.janet — public API: init, eval-string, eval-string*
|
|
||||||
main.janet — REPL entry point
|
|
||||||
test/ — 8 test suites + SCI load test
|
|
||||||
vendor/sci/ — SCI submodule (git submodule)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
||||||
234
foo.clj
Normal file
234
foo.clj
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
;; ============================================================
|
||||||
|
;; Comprehensive Clojure Features Demo
|
||||||
|
;; ============================================================
|
||||||
|
|
||||||
|
;; 1. Destructuring (sequential & associative)
|
||||||
|
(defn destructure-demo []
|
||||||
|
(println "\n--- Destructuring ---")
|
||||||
|
|
||||||
|
;; Sequential destructuring
|
||||||
|
(let [[a b c] [10 20 30]]
|
||||||
|
(println (str "Seq destructure: a=" a ", b=" b ", c=" c)))
|
||||||
|
|
||||||
|
;; Associative destructuring with defaults
|
||||||
|
(let [{:keys [name age city] :or {city "Unknown"}} {:name "Alice" :age 30}]
|
||||||
|
(println (str "Map destructure: name=" name ", age=" age ", city=" city)))
|
||||||
|
|
||||||
|
;; Nested destructuring
|
||||||
|
(let [{[x y] :coords} {:coords [1.0 2.5]}]
|
||||||
|
(println (str "Nested destructure: x=" x ", y=" y))))
|
||||||
|
|
||||||
|
;; 2. Atoms – state management
|
||||||
|
(defn atom-demo []
|
||||||
|
(println "\n--- Atoms ---")
|
||||||
|
(def counter (atom 0))
|
||||||
|
|
||||||
|
;; swap! (function-based update)
|
||||||
|
(swap! counter inc)
|
||||||
|
(println (str "After swap! inc: " @counter))
|
||||||
|
|
||||||
|
;; reset! (set new value)
|
||||||
|
(reset! counter 100)
|
||||||
|
(println (str "After reset! to 100: " @counter))
|
||||||
|
|
||||||
|
;; compare-and-set! (CAS)
|
||||||
|
(let [old @counter]
|
||||||
|
(if (compare-and-set! counter old (+ old 5))
|
||||||
|
(println (str "CAS success: " @counter))
|
||||||
|
(println "CAS failed")))
|
||||||
|
|
||||||
|
;; Using atom with swap! and multiple updates
|
||||||
|
(swap! counter #(-> % (* 2) (+ 3)))
|
||||||
|
(println (str "After thread-first swap!: " @counter)))
|
||||||
|
|
||||||
|
;; 3. Lazy sequences – infinite & transformed
|
||||||
|
(defn lazy-seq-demo []
|
||||||
|
(println "\n--- Lazy Sequences ---")
|
||||||
|
|
||||||
|
;; Infinite lazy seq: natural numbers
|
||||||
|
(def naturals (iterate inc 0))
|
||||||
|
|
||||||
|
;; Take first 10 even numbers using filter (lazy)
|
||||||
|
(def first-ten-evens (take 10 (filter even? naturals)))
|
||||||
|
(println (str "First 10 evens: " (pr-str first-ten-evens)))
|
||||||
|
|
||||||
|
;; Map and take-while (lazy)
|
||||||
|
(def squares-under-50
|
||||||
|
(take-while #(< % 50) (map #(* % %) (range))))
|
||||||
|
(println (str "Squares under 50: " (pr-str squares-under-50)))
|
||||||
|
|
||||||
|
;; Cycle and interpose (lazy)
|
||||||
|
(def repeated-pattern (take 10 (cycle [:a :b :c])))
|
||||||
|
(println (str "Cycled pattern: " (pr-str repeated-pattern)))
|
||||||
|
|
||||||
|
;; Lazy seq from recursion (not fully lazy, but demonstrates lazy cons)
|
||||||
|
(defn my-iterate [f x]
|
||||||
|
(lazy-seq (cons x (my-iterate f (f x)))))
|
||||||
|
(def powers-of-two (take 8 (my-iterate #(* 2 %) 1)))
|
||||||
|
(println (str "Powers of two: " (pr-str powers-of-two))))
|
||||||
|
|
||||||
|
;; 4. Transducers – composable transformations
|
||||||
|
(defn transducer-demo []
|
||||||
|
(println "\n--- Transducers ---")
|
||||||
|
|
||||||
|
;; Compose mapping and filtering as a transducer
|
||||||
|
(def xf (comp (map inc) (filter odd?)))
|
||||||
|
|
||||||
|
;; Apply to a collection (into)
|
||||||
|
(def result (into [] xf (range 10)))
|
||||||
|
(println (str "Transducer result: " (pr-str result)))
|
||||||
|
|
||||||
|
;; Use with sequence (sequence)
|
||||||
|
(def seq-result (sequence xf (range 10)))
|
||||||
|
(println (str "Transducer seq: " (pr-str seq-result))))
|
||||||
|
|
||||||
|
;; 5. Protocols & Records – polymorphism
|
||||||
|
(defprotocol Shape
|
||||||
|
(area [this])
|
||||||
|
(description [this]))
|
||||||
|
|
||||||
|
(defrecord Circle [radius]
|
||||||
|
Shape
|
||||||
|
(area [_] (* Math/PI radius radius))
|
||||||
|
(description [_] (str "Circle with radius " radius)))
|
||||||
|
|
||||||
|
(defrecord Rectangle [width height]
|
||||||
|
Shape
|
||||||
|
(area [_] (* width height))
|
||||||
|
(description [_] (str "Rectangle " width "x" height)))
|
||||||
|
|
||||||
|
(defn protocol-demo []
|
||||||
|
(println "\n--- Protocols & Records ---")
|
||||||
|
(def c (->Circle 5))
|
||||||
|
(def r (->Rectangle 3 4))
|
||||||
|
(println (str (description c) " -> area: " (area c)))
|
||||||
|
(println (str (description r) " -> area: " (area r))))
|
||||||
|
|
||||||
|
;; 6. Multimethods – dispatch on arbitrary values
|
||||||
|
(defmulti shape-type :kind)
|
||||||
|
(defmethod shape-type :circle [_] "round")
|
||||||
|
(defmethod shape-type :rectangle [_] "angular")
|
||||||
|
(defmethod shape-type :default [_] "unknown")
|
||||||
|
|
||||||
|
(defn multimethod-demo []
|
||||||
|
(println "\n--- Multimethods ---")
|
||||||
|
(def s1 {:kind :circle :radius 5})
|
||||||
|
(def s2 {:kind :rectangle :width 3 :height 4})
|
||||||
|
(def s3 {:kind :triangle})
|
||||||
|
(println (str "Circle type: " (shape-type s1)))
|
||||||
|
(println (str "Rectangle type: " (shape-type s2)))
|
||||||
|
(println (str "Triangle type: " (shape-type s3))))
|
||||||
|
|
||||||
|
;; 7. Macros – compile-time code generation
|
||||||
|
(defmacro log-call [expr]
|
||||||
|
`(let [result# ~expr]
|
||||||
|
(println (str "Called: " (pr-str '~expr) " -> " result#))
|
||||||
|
result#))
|
||||||
|
|
||||||
|
(defn macro-demo []
|
||||||
|
(println "\n--- Macros ---")
|
||||||
|
(log-call (* 2 3))
|
||||||
|
(log-call (map inc [1 2 3]))
|
||||||
|
(log-call (reduce + (range 1 6))))
|
||||||
|
|
||||||
|
;; 8. Recursion – linear and tail-recursive
|
||||||
|
(defn recursion-demo []
|
||||||
|
(println "\n--- Recursion ---")
|
||||||
|
;; Linear recursion: factorial
|
||||||
|
(defn fact [n]
|
||||||
|
(if (<= n 1) 1 (* n (fact (dec n)))))
|
||||||
|
(println (str "Factorial 5: " (fact 5)))
|
||||||
|
|
||||||
|
;; Tail recursion with recur
|
||||||
|
(defn fact-tail [n]
|
||||||
|
(loop [i n acc 1]
|
||||||
|
(if (zero? i) acc
|
||||||
|
(recur (dec i) (* acc i)))))
|
||||||
|
(println (str "Tail-factorial 5: " (fact-tail 5)))
|
||||||
|
|
||||||
|
;; Mutual recursion with trampoline
|
||||||
|
(declare even?)
|
||||||
|
(defn odd? [n]
|
||||||
|
(if (zero? n) false (even? (dec n))))
|
||||||
|
(defn even? [n]
|
||||||
|
(if (zero? n) true (odd? (dec n))))
|
||||||
|
(println (str "Is 6 even? " (even? 6))))
|
||||||
|
|
||||||
|
;; 9. Higher-order functions – partial, comp, juxt
|
||||||
|
(defn hof-demo []
|
||||||
|
(println "\n--- Higher-Order Functions ---")
|
||||||
|
(def add5 (partial + 5))
|
||||||
|
(println (str "Partial (+5) applied to 10: " (add5 10)))
|
||||||
|
|
||||||
|
(def inc-and-double (comp #(* 2 %) inc))
|
||||||
|
(println (str "Comp (double∘inc) on 3: " (inc-and-double 3)))
|
||||||
|
|
||||||
|
(def stats (juxt identity inc dec))
|
||||||
|
(println (str "Juxt on 5: " (stats 5))))
|
||||||
|
|
||||||
|
;; 10. Threading macros (-> and ->>)
|
||||||
|
(defn threading-demo []
|
||||||
|
(println "\n--- Threading Macros ---")
|
||||||
|
(def result
|
||||||
|
(->> (range 20)
|
||||||
|
(filter odd?)
|
||||||
|
(map #(* % 3))
|
||||||
|
(take 5)
|
||||||
|
(reduce +)))
|
||||||
|
(println (str "Threaded pipeline result: " result))
|
||||||
|
|
||||||
|
(def threaded-sqrt
|
||||||
|
(-> 25 Math/sqrt long (+ 10)))
|
||||||
|
(println (str "Thread-first sqrt: " threaded-sqrt)))
|
||||||
|
|
||||||
|
;; 11. Exception handling with try/catch/finally
|
||||||
|
(defn exception-demo []
|
||||||
|
(println "\n--- Exception Handling ---")
|
||||||
|
(try
|
||||||
|
(/ 1 0)
|
||||||
|
(catch ArithmeticException e
|
||||||
|
(println (str "Caught exception: " (.getMessage e))))
|
||||||
|
(finally
|
||||||
|
(println "Finally block executed."))))
|
||||||
|
|
||||||
|
;; 12. Clojure's sequence comprehension: for (list comprehension)
|
||||||
|
(defn for-demo []
|
||||||
|
(println "\n--- For Comprehension ---")
|
||||||
|
(def combos
|
||||||
|
(for [x (range 3)
|
||||||
|
y (range 3)
|
||||||
|
:when (not= x y)]
|
||||||
|
[x y]))
|
||||||
|
(println (str "Combinations (x!=y): " (pr-str combos))))
|
||||||
|
|
||||||
|
;; 13. Clojure's core.async? Not pure Clojure, skip.
|
||||||
|
|
||||||
|
;; 14. Java interop (still pure Clojure)
|
||||||
|
(defn java-interop-demo []
|
||||||
|
(println "\n--- Java Interop ---")
|
||||||
|
(def now (java.util.Date.))
|
||||||
|
(println (str "Current date: " (.toString now)))
|
||||||
|
(def sb (StringBuilder. "Hello"))
|
||||||
|
(.append sb " Clojure!")
|
||||||
|
(println (str "StringBuilder: " (.toString sb))))
|
||||||
|
|
||||||
|
;; ---------- Main entry point ----------
|
||||||
|
(defn -main []
|
||||||
|
(println "=== Clojure Features Demo ===")
|
||||||
|
(destructure-demo)
|
||||||
|
(atom-demo)
|
||||||
|
(lazy-seq-demo)
|
||||||
|
(transducer-demo)
|
||||||
|
(protocol-demo)
|
||||||
|
(multimethod-demo)
|
||||||
|
(macro-demo)
|
||||||
|
(recursion-demo)
|
||||||
|
(hof-demo)
|
||||||
|
(threading-demo)
|
||||||
|
(exception-demo)
|
||||||
|
(for-demo)
|
||||||
|
(java-interop-demo)
|
||||||
|
(println "\n=== Demo Complete ==="))
|
||||||
|
|
||||||
|
;; Run if executed as script
|
||||||
|
(-main)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue