From f20df44d6a3e4e2d50207995838fc6ea47e715cd Mon Sep 17 00:00:00 2001 From: Yogthos Date: Thu, 4 Jun 2026 16:48:45 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20lean=20README=20=E2=80=94=20drop=20stal?= =?UTF-8?q?e=20internals/project-structure,=20document=20divergences=20fro?= =?UTF-8?q?m=20Clojure=20(host/numbers/collections/STM/regex),=20fix=20int?= =?UTF-8?q?erop=20examples=20to=20working=20calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 112 ++++++++------------------ foo.clj | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+), 77 deletions(-) create mode 100644 foo.clj diff --git a/README.md b/README.md index 5e8f539..67e7a00 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,34 @@ # 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 - -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 +## Build ```bash git clone https://github.com/yogthos/jolt.git cd jolt git submodule update --init # pulls vendor/sci -jpm build # compiles build/jolt -build/jolt # drops into REPL +jpm build # compiles build/jolt ``` -## Build - -``` -jpm build -``` - -This compiles `src/jolt/*.janet` into a standalone `build/jolt` executable. Requires Janet ≥ 1.36 and `jpm`. +Requires Janet ≥ 1.36 and `jpm`. ## Run ``` 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 -h # help ``` -With no arguments it drops into a read-eval-print loop (multi-line forms are -accumulated until balanced): +The REPL accumulates multi-line forms until they balance: ``` -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/fib -user=> (fib 10) -55 +user=> (map fib (range 10)) +(0 1 1 2 3 5 8 13 21 34) ``` Running a file evaluates its top-level forms: @@ -63,72 +39,54 @@ $ build/jolt hello.clj hello 42 ``` -## Test - -``` -jpm test -``` - -Runs all tests: API, bootstrap, core, evaluator, macro, namespace, reader, types, and SCI load. - ## Use as a library ```janet (use jolt/api) (def ctx (init)) -(eval-string ctx "(+ 1 2)") ;; → 3 -(eval-string ctx "(map inc [1 2 3])") ;; → [2 3 4] -(eval-string ctx "(def x 42)") ;; → #'user/x -(eval-string ctx "x") ;; → 42 +(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. 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 -;; Field access on tables and structs -user=> (def t {:a 1 :b 2}) -user=> (. t :a) ;; → 1 -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 +(def obj {:greet (fn [self name] (str "Hello " name))}) +(. obj greet "Alice") ; → "Hello Alice" +(.-greet obj) ; field access (reader sugar for (. obj :greet)) ``` -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 host functions** — Janet's standard library (`os/shell`, `net/request`, etc.) is accessible through Jolt's `jolt.interop` namespace: +Janet's standard library is reachable through `jolt.interop` (and the `jolt.shell` / `jolt.http` helpers built on it): ```clojure -user=> (require '[jolt.interop :as j]) -user=> (j/janet-eval "(+ 1 2)") ;; → 3 -user=> (j/janet-table-keys {:a 1 :b 2}) ;; → [:a :b] -user=> (j/janet-describe "hello") ;; → Janet type info +(require '[jolt.interop :as j]) +(j/janet-type [1 2]) ; → :tuple +(j/janet-table-keys {:a 1 :b 2}) ; → [:b :a] ``` -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 (`(?…)`). +- **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/ - types.janet — Var, Namespace, Context, symbol helpers - 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) +jpm test # full test suite +janet test/conformance.janet # Clojure-conformance battery ``` ## License diff --git a/foo.clj b/foo.clj new file mode 100644 index 0000000..1fbbf87 --- /dev/null +++ b/foo.clj @@ -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)