Initial commit: Jolt — Clojure interpreter on Janet
- PEG-based Clojure reader (symbols, keywords, numbers, strings, lists, vectors, maps, sets, quote forms, reader macros, metadata) - Tree-walking evaluator (quote, do, if, def, fn*, let*, loop*/recur, syntax-quote/unquote/unquote-splicing, macro system, ns/require/in-ns) - 95+ clojure.core functions (predicates, math, collections, seq ops, higher-order, atoms, I/O) - Public API (init, eval-string, eval-string*) - REPL (jolt/main.janet) - 7 test suites, all green - MIT license
This commit is contained in:
commit
cdcf569506
16 changed files with 2696 additions and 0 deletions
37
src/jolt/api.janet
Normal file
37
src/jolt/api.janet
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Jolt Public API
|
||||
# High-level interface for the Clojure-on-Janet interpreter.
|
||||
|
||||
(use ./types)
|
||||
(use ./reader)
|
||||
(use ./evaluator)
|
||||
(use ./core)
|
||||
|
||||
(defn init
|
||||
"Create a new Jolt evaluation context, optionally with opts.
|
||||
(init) — empty context with clojure.core loaded
|
||||
(init opts) — context with opts and clojure.core loaded
|
||||
|
||||
opts may contain:
|
||||
:namespaces — map of {ns-name → {sym → value, ...}, ...}"
|
||||
[&opt opts]
|
||||
(default opts nil)
|
||||
(let [ctx (make-ctx opts)]
|
||||
(init-core! ctx)
|
||||
ctx))
|
||||
|
||||
(defn eval-string
|
||||
"Evaluate a Clojure source string in a Jolt context.
|
||||
(eval-string ctx s) → value
|
||||
|
||||
Returns the result of evaluating the first form in s."
|
||||
[ctx s]
|
||||
(let [form (parse-string s)]
|
||||
(eval-form ctx @{} form)))
|
||||
|
||||
(defn eval-string*
|
||||
"Evaluate a Clojure source string in a Jolt context.
|
||||
Like eval-string but with explicit bindings.
|
||||
(eval-string* ctx s bindings) → value"
|
||||
[ctx s bindings]
|
||||
(let [form (parse-string s)]
|
||||
(eval-form ctx bindings form)))
|
||||
709
src/jolt/core.janet
Normal file
709
src/jolt/core.janet
Normal file
|
|
@ -0,0 +1,709 @@
|
|||
# Jolt Core Library
|
||||
# Clojure-compatible core functions for the Jolt interpreter.
|
||||
|
||||
(use ./types)
|
||||
|
||||
# ============================================================
|
||||
# Predicates
|
||||
# ============================================================
|
||||
|
||||
(defn core-nil? [x] (nil? x))
|
||||
(defn core-some? [x] (not (nil? x)))
|
||||
(defn core-string? [x] (string? x))
|
||||
(defn core-number? [x] (number? x))
|
||||
(defn core-fn? [x] (or (function? x) (cfunction? x)))
|
||||
(defn core-keyword? [x] (keyword? x))
|
||||
(defn core-symbol? [x] (and (struct? x) (= :symbol (x :jolt/type))))
|
||||
(defn core-vector? [x] (tuple? x))
|
||||
(defn core-map? [x] (struct? x))
|
||||
(defn core-seq? [x] (or (array? x) (tuple? x)))
|
||||
(defn core-coll? [x] (or (array? x) (tuple? x) (struct? x)))
|
||||
|
||||
(defn core-true? [x] (= true x))
|
||||
(defn core-false? [x] (= false x))
|
||||
(defn core-identical? [a b] (= a b))
|
||||
|
||||
(defn core-zero? [x] (and (number? x) (= x 0)))
|
||||
(defn core-pos? [x] (and (number? x) (> x 0)))
|
||||
(defn core-neg? [x] (and (number? x) (< x 0)))
|
||||
(defn core-even? [n] (= 0 (% n 2)))
|
||||
(defn core-odd? [n] (not= 0 (% n 2)))
|
||||
|
||||
(defn core-empty? [coll]
|
||||
(if (nil? coll) true
|
||||
(if (struct? coll) (= 0 (length (keys coll)))
|
||||
(= 0 (length coll)))))
|
||||
|
||||
(defn core-every? [pred coll]
|
||||
(var result true)
|
||||
(each x coll (if (not (pred x)) (do (set result false) (break))))
|
||||
result)
|
||||
|
||||
# ============================================================
|
||||
# Math — Clojure semantics (variadic, / with one arg = reciprocal)
|
||||
# ============================================================
|
||||
|
||||
(def core-+ (fn [& args] (if (= 0 (length args)) 0 (+ ;args))))
|
||||
|
||||
(def core-sub
|
||||
(fn [& args]
|
||||
(if (= 0 (length args))
|
||||
(error "Wrong number of args (0) passed to: -")
|
||||
(apply - args))))
|
||||
|
||||
(def core-* (fn [& args] (if (= 0 (length args)) 1 (* ;args))))
|
||||
|
||||
(def core-/
|
||||
(fn [& args]
|
||||
(case (length args)
|
||||
0 (error "Wrong number of args (0) passed to: /")
|
||||
1 (/ 1 (args 0))
|
||||
(apply / args))))
|
||||
|
||||
(def core-inc inc)
|
||||
(def core-dec dec)
|
||||
(def core-mod %)
|
||||
(def core-rem %)
|
||||
(def core-quot (fn [n d] (math/floor (/ n d))))
|
||||
|
||||
(defn core-max [& args] (apply max args))
|
||||
(defn core-min [& args] (apply min args))
|
||||
|
||||
# ============================================================
|
||||
# Comparison
|
||||
# ============================================================
|
||||
|
||||
(defn core-= [& args]
|
||||
(if (< (length args) 2) true
|
||||
(do
|
||||
(var ok true)
|
||||
(var i 0)
|
||||
(while (and ok (< i (dec (length args))))
|
||||
(if (not (deep= (args i) (args (+ i 1))))
|
||||
(set ok false))
|
||||
(++ i))
|
||||
ok)))
|
||||
|
||||
(defn core-not= [& args] (not (apply core-= args)))
|
||||
|
||||
# ============================================================
|
||||
# Collections
|
||||
# ============================================================
|
||||
|
||||
(defn core-conj [coll & xs]
|
||||
(if (tuple? coll)
|
||||
# vector: add to end
|
||||
(tuple/slice (tuple ;(array/concat (array/slice coll) xs)))
|
||||
(if (array? coll)
|
||||
# list: add to front (reverse xs, push each)
|
||||
(do
|
||||
(var result coll)
|
||||
(var i 0)
|
||||
(while (< i (length xs))
|
||||
(set result (array/insert result 0 (xs i)))
|
||||
(++ i))
|
||||
result)
|
||||
# struct/map: add [k v] pairs
|
||||
(do
|
||||
(var result coll)
|
||||
(var i 0)
|
||||
(while (< i (length xs))
|
||||
(let [pair (xs i)]
|
||||
(set result (merge result {(pair 0) (pair 1)})))
|
||||
(++ i))
|
||||
result))))
|
||||
|
||||
(defn core-assoc [m & kvs]
|
||||
(var result @{})
|
||||
(when m
|
||||
(each k (if (struct? m) (keys m) (keys (table ;(pairs m))))
|
||||
(put result k (get m k))))
|
||||
(var i 0)
|
||||
(while (< i (length kvs))
|
||||
(let [k (kvs i) v (kvs (+ i 1))]
|
||||
(put result k v)
|
||||
(+= i 2)))
|
||||
(if (struct? m) (table/to-struct result) result))
|
||||
|
||||
(defn core-dissoc [m & ks]
|
||||
(var result @{})
|
||||
(each k (keys m)
|
||||
(var in-ks false)
|
||||
(each k2 ks
|
||||
(if (deep= k k2) (do (set in-ks true) (break))))
|
||||
(if (not in-ks) (put result k (m k))))
|
||||
(if (struct? m) (table/to-struct result) result))
|
||||
|
||||
(defn core-get [m k &opt default]
|
||||
(default default nil)
|
||||
(if (nil? m) default
|
||||
(if (or (struct? m) (table? m))
|
||||
(let [v (m k)]
|
||||
(if (nil? v) default v))
|
||||
(if (and (or (tuple? m) (array? m)) (number? k) (>= k 0) (< k (length m)))
|
||||
(in m k)
|
||||
default))))
|
||||
|
||||
(defn core-get-in [m ks &opt default]
|
||||
(default default nil)
|
||||
(var current m)
|
||||
(var i 0)
|
||||
(while (< i (length ks))
|
||||
(if (nil? current) (break))
|
||||
(set current (core-get current (ks i)))
|
||||
(++ i))
|
||||
(if (nil? current) default current))
|
||||
|
||||
(defn core-contains? [coll key]
|
||||
(if (struct? coll) (not (nil? (coll key)))
|
||||
(if (table? coll) (not (nil? (coll key)))
|
||||
(if (or (tuple? coll) (array? coll))
|
||||
(and (number? key) (>= key 0) (< key (length coll)))
|
||||
false))))
|
||||
|
||||
(def core-count length)
|
||||
|
||||
(defn core-first [coll]
|
||||
(if (or (nil? coll) (= 0 (length coll))) nil
|
||||
(in coll 0)))
|
||||
|
||||
(defn core-rest [coll]
|
||||
(if (or (nil? coll) (= 0 (length coll)))
|
||||
@[]
|
||||
(if (tuple? coll)
|
||||
(tuple/slice coll 1)
|
||||
(array/slice coll 1))))
|
||||
|
||||
(defn core-next [coll]
|
||||
(let [r (core-rest coll)]
|
||||
(if (= 0 (length r)) nil r)))
|
||||
|
||||
(defn core-cons [x coll]
|
||||
(if (nil? coll)
|
||||
@[x]
|
||||
(if (tuple? coll)
|
||||
(tuple/slice (tuple ;(array/insert (array/slice coll) 0 x)))
|
||||
(array/insert coll 0 x))))
|
||||
|
||||
(defn core-seq [coll]
|
||||
(if (or (nil? coll) (and (or (tuple? coll) (array? coll)) (= 0 (length coll))))
|
||||
nil
|
||||
(if (tuple? coll) (tuple/slice coll)
|
||||
(if (string? coll) (map |(string/from-bytes $) (string/bytes coll))
|
||||
(if (struct? coll) (tuple ;(keys coll))
|
||||
coll)))))
|
||||
|
||||
(defn core-vec [coll]
|
||||
(if (tuple? coll) coll
|
||||
(if (array? coll) (tuple ;coll)
|
||||
(if (struct? coll) (tuple ;(map |(in (kvs coll) (+ (* $ 2) 1)) (range (/ (length (kvs coll)) 2))))
|
||||
(tuple)))))
|
||||
|
||||
(defn core-into [to from]
|
||||
(if (tuple? to)
|
||||
(tuple/slice (tuple ;(array/concat (array/slice to) (if (tuple? from) from (array/slice from)))))
|
||||
(if (array? to)
|
||||
(array/concat to from)
|
||||
(if (struct? to)
|
||||
(do
|
||||
(var result to)
|
||||
(each [k v] (pairs from)
|
||||
(set result (merge result {k v})))
|
||||
result)
|
||||
to))))
|
||||
|
||||
(defn core-merge [& maps]
|
||||
(var result (struct))
|
||||
(each m maps
|
||||
(set result (merge result m)))
|
||||
result)
|
||||
|
||||
(defn core-merge-with [f & maps]
|
||||
(var result @{})
|
||||
(each m maps
|
||||
(each k (keys m)
|
||||
(let [existing (result k)]
|
||||
(put result k (if (nil? existing) (m k) (f existing (m k)))))))
|
||||
(table/to-struct result))
|
||||
|
||||
(defn core-keys [m]
|
||||
(tuple ;(keys m)))
|
||||
|
||||
(defn core-vals [m]
|
||||
(tuple ;(map |(m $) (keys m))))
|
||||
|
||||
(defn core-select-keys [m ks]
|
||||
(var result @{})
|
||||
(each k ks
|
||||
(let [v (core-get m k)]
|
||||
(if (not (nil? v)) (put result k v))))
|
||||
(if (struct? m) (table/to-struct result) result))
|
||||
|
||||
(defn core-zipmap [ks vs]
|
||||
(var result @{})
|
||||
(var i 0)
|
||||
(while (and (< i (length ks)) (< i (length vs)))
|
||||
(put result (ks i) (vs i))
|
||||
(++ i))
|
||||
(table/to-struct result))
|
||||
|
||||
# ============================================================
|
||||
# Sequence operations
|
||||
# ============================================================
|
||||
|
||||
(defn core-map [f & colls]
|
||||
(let [first-coll (colls 0)
|
||||
result (if (= 1 (length colls))
|
||||
(array ;(map f first-coll))
|
||||
(do
|
||||
(var res @[])
|
||||
(var idxs @{})
|
||||
(each _ first-coll (array/push idxs 0))
|
||||
(var done false)
|
||||
(while (not done)
|
||||
(var args @[])
|
||||
(var i 0)
|
||||
(while (< i (length colls))
|
||||
(let [c (colls i) j (idxs i)]
|
||||
(if (>= j (length c))
|
||||
(do (set done true) (break))
|
||||
(array/push args (c j))))
|
||||
(++ i))
|
||||
(if (not done) (array/push res (apply f args)))
|
||||
(var k 0)
|
||||
(while (< k (length colls))
|
||||
(set (idxs k) (+ (idxs k) 1))
|
||||
(++ k)))
|
||||
res))]
|
||||
(if (tuple? first-coll) (tuple/slice (tuple ;result)) result)))
|
||||
|
||||
(defn core-filter [pred coll]
|
||||
(var result @[])
|
||||
(each x coll
|
||||
(if (pred x) (array/push result x)))
|
||||
(if (tuple? coll) (tuple/slice (tuple ;result)) result))
|
||||
|
||||
(defn core-remove [pred coll]
|
||||
(core-filter (fn [x] (not (pred x))) coll))
|
||||
|
||||
(def core-reduce
|
||||
(fn [& args]
|
||||
(case (length args)
|
||||
2 (let [f (args 0) coll (args 1)]
|
||||
(if (= 0 (length coll))
|
||||
(f)
|
||||
(do
|
||||
(var acc (coll 0))
|
||||
(var i 1)
|
||||
(while (< i (length coll))
|
||||
(set acc (f acc (coll i)))
|
||||
(++ i))
|
||||
acc)))
|
||||
3 (let [f (args 0) val (args 1) coll (args 2)]
|
||||
(var acc val)
|
||||
(each x coll (set acc (f acc x)))
|
||||
acc)
|
||||
(error "Wrong number of args passed to: reduce"))))
|
||||
|
||||
(defn core-take [n coll]
|
||||
(var result @[])
|
||||
(var i 0)
|
||||
(while (and (< i n) (< i (length coll)))
|
||||
(array/push result (coll i))
|
||||
(++ i))
|
||||
(if (tuple? coll) (tuple/slice (tuple ;result)) result))
|
||||
|
||||
(defn core-drop [n coll]
|
||||
(if (tuple? coll)
|
||||
(tuple/slice coll (min n (length coll)))
|
||||
(array/slice coll (min n (length coll)))))
|
||||
|
||||
(defn core-take-while [pred coll]
|
||||
(var result @[])
|
||||
(each x coll
|
||||
(if (pred x) (array/push result x) (break)))
|
||||
(if (tuple? coll) (tuple/slice (tuple ;result)) result))
|
||||
|
||||
(defn core-drop-while [pred coll]
|
||||
(var start 0)
|
||||
(while (and (< start (length coll)) (pred (coll start)))
|
||||
(++ start))
|
||||
(if (tuple? coll)
|
||||
(tuple/slice coll start)
|
||||
(array/slice coll start)))
|
||||
|
||||
(defn core-concat [& colls]
|
||||
(var result @[])
|
||||
(each c colls
|
||||
(each x c (array/push result x)))
|
||||
result)
|
||||
|
||||
(defn core-reverse [coll]
|
||||
(var result @[])
|
||||
(var i (dec (length coll)))
|
||||
(while (>= i 0)
|
||||
(array/push result (coll i))
|
||||
(-- i))
|
||||
(if (tuple? coll) (tuple/slice (tuple ;result)) result))
|
||||
|
||||
(defn core-sort [coll]
|
||||
(let [arr (if (tuple? coll) (array/slice coll) coll)
|
||||
sorted (sort arr)]
|
||||
(if (tuple? coll) (tuple/slice (tuple ;sorted)) sorted)))
|
||||
|
||||
(defn core-sort-by [keyfn coll]
|
||||
(let [arr (if (tuple? coll) (array/slice coll) coll)
|
||||
sorted (sort-by keyfn arr)]
|
||||
(if (tuple? coll) (tuple/slice (tuple ;sorted)) sorted)))
|
||||
|
||||
(defn core-distinct [coll]
|
||||
(var seen @{})
|
||||
(var result @[])
|
||||
(each x coll
|
||||
(if (nil? (seen x))
|
||||
(do
|
||||
(put seen x true)
|
||||
(array/push result x))))
|
||||
(if (tuple? coll) (tuple/slice (tuple ;result)) result))
|
||||
|
||||
(defn core-group-by [f coll]
|
||||
(var result @{})
|
||||
(each x coll
|
||||
(let [k (f x)]
|
||||
(put result k (array/push (core-get result k @[]) x))))
|
||||
result)
|
||||
|
||||
(defn core-frequencies [coll]
|
||||
(core-group-by identity coll))
|
||||
|
||||
(defn core-partition [n coll]
|
||||
(var result @[])
|
||||
(var i 0)
|
||||
(while (< i (length coll))
|
||||
(var part @[])
|
||||
(var j 0)
|
||||
(while (and (< j n) (< (+ i j) (length coll)))
|
||||
(array/push part (coll (+ i j)))
|
||||
(++ j))
|
||||
(if (= (length part) n) (array/push result (tuple/slice (tuple ;part))))
|
||||
(+= i n))
|
||||
result)
|
||||
|
||||
(defn core-partition-by [f coll]
|
||||
(var result @[])
|
||||
(var part @[])
|
||||
(var last-k nil)
|
||||
(each x coll
|
||||
(let [k (f x)]
|
||||
(if (and last-k (deep= k last-k))
|
||||
(array/push part x)
|
||||
(do
|
||||
(if (> (length part) 0) (array/push result (tuple/slice (tuple ;part))))
|
||||
(set part @[x])
|
||||
(set last-k k)))))
|
||||
(if (> (length part) 0) (array/push result (tuple/slice (tuple ;part))))
|
||||
result)
|
||||
|
||||
# ============================================================
|
||||
# Sequence generators
|
||||
# ============================================================
|
||||
|
||||
(def core-range
|
||||
(fn [& args]
|
||||
(let [start (if (> (length args) 1) (args 0) 0)
|
||||
end (if (> (length args) 1) (args 1) (args 0))
|
||||
step (if (> (length args) 2) (args 2) 1)]
|
||||
(var result @[])
|
||||
(var i start)
|
||||
(while (if (pos? step) (< i end) (> i end))
|
||||
(array/push result i)
|
||||
(+= i step))
|
||||
(tuple/slice (tuple ;result)))))
|
||||
|
||||
(def core-repeat (fn [n x]
|
||||
(var result @[])
|
||||
(var i 0)
|
||||
(while (< i n)
|
||||
(array/push result x)
|
||||
(++ i))
|
||||
result))
|
||||
|
||||
(defn core-repeatedly [n f]
|
||||
(var result @[])
|
||||
(var i 0)
|
||||
(while (< i n)
|
||||
(array/push result (f))
|
||||
(++ i))
|
||||
result)
|
||||
|
||||
# ============================================================
|
||||
# Higher-order functions
|
||||
# ============================================================
|
||||
|
||||
(def core-identity (fn [x] x))
|
||||
|
||||
(def core-constantly (fn [x] (fn [& _] x)))
|
||||
|
||||
(defn core-complement [f]
|
||||
(fn [& args] (not (apply f args))))
|
||||
|
||||
(def core-comp
|
||||
(fn [& fs]
|
||||
(case (length fs)
|
||||
0 identity
|
||||
1 (fs 0)
|
||||
2 (let [f (fs 0) g (fs 1)] (fn [& args] (f (apply g args))))
|
||||
(let [f (last fs)
|
||||
gs (array/slice fs 0 (dec (length fs)))]
|
||||
(fn [& args]
|
||||
(var result (apply (last gs) args))
|
||||
(var i (- (length gs) 2))
|
||||
(while (>= i 0)
|
||||
(set result ((gs i) result))
|
||||
(-- i))
|
||||
(f result))))))
|
||||
|
||||
(defn core-partial [f & args]
|
||||
(fn [& more] (apply f (array/concat (array/slice args) more))))
|
||||
|
||||
(defn core-juxt [& fs]
|
||||
(fn [& args]
|
||||
(tuple ;(map |(apply $ args) fs))))
|
||||
|
||||
(defn core-memoize [f]
|
||||
(var cache @{})
|
||||
(fn [& args]
|
||||
(let [key (tuple ;args)]
|
||||
(if-let [v (get cache key)]
|
||||
v
|
||||
(let [result (apply f args)]
|
||||
(put cache key result)
|
||||
result)))))
|
||||
|
||||
# ============================================================
|
||||
# Collection constructors
|
||||
# ============================================================
|
||||
|
||||
(defn core-vector [& xs] (tuple ;xs))
|
||||
(defn core-hash-map [& kvs]
|
||||
(var result @{})
|
||||
(var i 0)
|
||||
(while (< i (length kvs))
|
||||
(put result (kvs i) (kvs (+ i 1)))
|
||||
(+= i 2))
|
||||
(table/to-struct result))
|
||||
|
||||
(defn core-array-map [& kvs]
|
||||
(var result @{})
|
||||
(var i 0)
|
||||
(while (< i (length kvs))
|
||||
(put result (kvs i) (kvs (+ i 1)))
|
||||
(+= i 2))
|
||||
(table/to-struct result))
|
||||
|
||||
(defn core-hash-set [& xs]
|
||||
(var result @{})
|
||||
(each x xs (put result x true))
|
||||
{:jolt/type :jolt/set :value (tuple ;(keys result))})
|
||||
|
||||
(defn core-set [coll]
|
||||
(apply core-hash-set (if (tuple? coll) (array/slice coll) coll)))
|
||||
|
||||
(defn core-list [& xs]
|
||||
(array ;xs))
|
||||
|
||||
# ============================================================
|
||||
# String functions
|
||||
# ============================================================
|
||||
|
||||
(defn core-str [& xs]
|
||||
(if (= 0 (length xs)) ""
|
||||
(do
|
||||
(var result @[])
|
||||
(each x xs
|
||||
(if (nil? x) nil # skip nil
|
||||
(array/push result (if (string? x) x (string x)))))
|
||||
(string/join result ""))))
|
||||
|
||||
(def core-subs
|
||||
(fn [& args]
|
||||
(case (length args)
|
||||
2 (string/slice (args 0) (args 1))
|
||||
3 (string/slice (args 0) (args 1) (args 2))
|
||||
(error "Wrong number of args passed to: subs"))))
|
||||
|
||||
# ============================================================
|
||||
# I/O — minimal wrappers
|
||||
# ============================================================
|
||||
|
||||
(def core-print print)
|
||||
(def core-println (fn [& xs] (apply print xs) (print "\n") nil))
|
||||
|
||||
(defn core-pr [& xs]
|
||||
(var i 0)
|
||||
(while (< i (length xs))
|
||||
(if (> i 0) (prin " "))
|
||||
(prin (xs i))
|
||||
(++ i))
|
||||
nil)
|
||||
|
||||
(defn core-prn [& xs]
|
||||
(apply core-pr xs)
|
||||
(print "\n")
|
||||
nil)
|
||||
|
||||
# ============================================================
|
||||
# Atom
|
||||
# ============================================================
|
||||
|
||||
(defn core-atom [val]
|
||||
@{:jolt/type :jolt/atom :value val :watches @{}})
|
||||
|
||||
(defn core-atom? [x]
|
||||
(and (table? x) (= :jolt/atom (x :jolt/type))))
|
||||
|
||||
(defn core-deref [ref]
|
||||
(cond
|
||||
# Jolt atom
|
||||
(and (table? ref) (= :jolt/atom (ref :jolt/type)))
|
||||
(ref :value)
|
||||
# Jolt var (from types.janet)
|
||||
(and (table? ref) (= :jolt/var (ref :jolt/type)))
|
||||
(ref :root)
|
||||
# default: return as-is
|
||||
ref))
|
||||
|
||||
(defn core-reset! [atm val]
|
||||
(put atm :value val)
|
||||
val)
|
||||
|
||||
(defn core-swap! [atm f & args]
|
||||
(let [new-val (apply f (atm :value) args)]
|
||||
(put atm :value new-val)
|
||||
new-val))
|
||||
|
||||
# ============================================================
|
||||
# Threading macros (as regular functions? No, as macros in Clojure)
|
||||
# These need to be defined as macros in the Jolt namespace system.
|
||||
# For now, skip — they need proper macro definition via the evaluator.
|
||||
# ============================================================
|
||||
|
||||
# ============================================================
|
||||
# Initialization — intern everything into a context's namespace
|
||||
# ============================================================
|
||||
|
||||
(def- core-bindings
|
||||
"Map of symbol name → function for all core functions."
|
||||
@{"nil?" core-nil?
|
||||
"some?" core-some?
|
||||
"string?" core-string?
|
||||
"number?" core-number?
|
||||
"fn?" core-fn?
|
||||
"keyword?" core-keyword?
|
||||
"symbol?" core-symbol?
|
||||
"vector?" core-vector?
|
||||
"map?" core-map?
|
||||
"seq?" core-seq?
|
||||
"coll?" core-coll?
|
||||
"true?" core-true?
|
||||
"false?" core-false?
|
||||
"identical?" core-identical?
|
||||
"zero?" core-zero?
|
||||
"pos?" core-pos?
|
||||
"neg?" core-neg?
|
||||
"even?" core-even?
|
||||
"odd?" core-odd?
|
||||
"empty?" core-empty?
|
||||
"every?" core-every?
|
||||
"+" core-+
|
||||
"-" core-sub
|
||||
"*" core-*
|
||||
"/" core-/
|
||||
"inc" core-inc
|
||||
"dec" core-dec
|
||||
"mod" core-mod
|
||||
"rem" core-rem
|
||||
"quot" core-quot
|
||||
"max" core-max
|
||||
"min" core-min
|
||||
"=" core-=
|
||||
"not=" core-not=
|
||||
"conj" core-conj
|
||||
"assoc" core-assoc
|
||||
"dissoc" core-dissoc
|
||||
"get" core-get
|
||||
"get-in" core-get-in
|
||||
"contains?" core-contains?
|
||||
"count" core-count
|
||||
"first" core-first
|
||||
"rest" core-rest
|
||||
"next" core-next
|
||||
"cons" core-cons
|
||||
"seq" core-seq
|
||||
"vec" core-vec
|
||||
"into" core-into
|
||||
"merge" core-merge
|
||||
"merge-with" core-merge-with
|
||||
"keys" core-keys
|
||||
"vals" core-vals
|
||||
"select-keys" core-select-keys
|
||||
"zipmap" core-zipmap
|
||||
"map" core-map
|
||||
"filter" core-filter
|
||||
"remove" core-remove
|
||||
"reduce" core-reduce
|
||||
"take" core-take
|
||||
"drop" core-drop
|
||||
"take-while" core-take-while
|
||||
"drop-while" core-drop-while
|
||||
"concat" core-concat
|
||||
"reverse" core-reverse
|
||||
"sort" core-sort
|
||||
"sort-by" core-sort-by
|
||||
"distinct" core-distinct
|
||||
"group-by" core-group-by
|
||||
"frequencies" core-frequencies
|
||||
"partition" core-partition
|
||||
"partition-by" core-partition-by
|
||||
"range" core-range
|
||||
"repeat" core-repeat
|
||||
"repeatedly" core-repeatedly
|
||||
"identity" core-identity
|
||||
"constantly" core-constantly
|
||||
"complement" core-complement
|
||||
"comp" core-comp
|
||||
"partial" core-partial
|
||||
"juxt" core-juxt
|
||||
"memoize" core-memoize
|
||||
"vector" core-vector
|
||||
"hash-map" core-hash-map
|
||||
"array-map" core-array-map
|
||||
"hash-set" core-hash-set
|
||||
"set" core-set
|
||||
"list" core-list
|
||||
"str" core-str
|
||||
"subs" core-subs
|
||||
"print" core-print
|
||||
"println" core-println
|
||||
"pr" core-pr
|
||||
"prn" core-prn
|
||||
"atom" core-atom
|
||||
"atom?" core-atom?
|
||||
"deref" core-deref
|
||||
"reset!" core-reset!
|
||||
"swap!" core-swap!})
|
||||
|
||||
(def init-core!
|
||||
(fn [& args]
|
||||
(case (length args)
|
||||
1 (let [ctx (args 0)
|
||||
ns (ctx-find-ns ctx "clojure.core")]
|
||||
(loop [[name fn] :pairs core-bindings]
|
||||
(ns-intern ns name fn))
|
||||
ns)
|
||||
2 (let [ctx (args 0) ns-name (args 1)
|
||||
ns (ctx-find-ns ctx ns-name)]
|
||||
(loop [[name fn] :pairs core-bindings]
|
||||
(ns-intern ns name fn))
|
||||
ns)
|
||||
(error "Wrong number of args passed to: init-core!"))))
|
||||
264
src/jolt/evaluator.janet
Normal file
264
src/jolt/evaluator.janet
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# Jolt Evaluator
|
||||
# Direct interpreter for Clojure forms on Janet.
|
||||
|
||||
(use ./types)
|
||||
|
||||
(defn- sym-name?
|
||||
[sym-s name-str]
|
||||
(and (struct? sym-s) (= :symbol (sym-s :jolt/type)) (= name-str (sym-s :name))))
|
||||
|
||||
(defn- special-symbol?
|
||||
[name]
|
||||
(or (= name "quote") (= name "syntax-quote") (= name "unquote")
|
||||
(= name "unquote-splicing") (= name "do") (= name "if")
|
||||
(= name "def") (= name "fn*") (= name "let*") (= name "loop*")
|
||||
(= name "recur")))
|
||||
|
||||
(var eval-form nil)
|
||||
|
||||
(defn- syntax-quote*
|
||||
[ctx bindings form]
|
||||
(cond
|
||||
(and (array? form) (> (length form) 0) (sym-name? (first form) "unquote"))
|
||||
(eval-form ctx bindings (in form 1))
|
||||
(and (array? form) (> (length form) 0) (sym-name? (first form) "unquote-splicing"))
|
||||
(error "~@ used outside of a list or vector in syntax-quote")
|
||||
(or (number? form) (string? form) (keyword? form) (nil? form) (= true form) (= false form))
|
||||
form
|
||||
(and (struct? form) (= :symbol (form :jolt/type)))
|
||||
(if (nil? (form :ns))
|
||||
(if (special-symbol? (form :name)) form
|
||||
{:jolt/type :symbol :ns (ctx-current-ns ctx) :name (form :name)})
|
||||
form)
|
||||
(tuple? form)
|
||||
(do (var result @[]) (var i 0) (while (< i (length form))
|
||||
(let [item (in form i)]
|
||||
(if (and (array? item) (> (length item) 0) (sym-name? (first item) "unquote-splicing"))
|
||||
(each v (eval-form ctx bindings (in item 1)) (array/push result v))
|
||||
(array/push result (syntax-quote* ctx bindings item))))
|
||||
(++ i)) (tuple ;result))
|
||||
(array? form)
|
||||
(do (var result @[]) (var i 0) (while (< i (length form))
|
||||
(let [item (in form i)]
|
||||
(if (and (array? item) (> (length item) 0) (sym-name? (first item) "unquote-splicing"))
|
||||
(each v (eval-form ctx bindings (in item 1)) (array/push result v))
|
||||
(array/push result (syntax-quote* ctx bindings item))))
|
||||
(++ i)) result)
|
||||
(and (struct? form) (get form :jolt/type)) form
|
||||
(struct? form)
|
||||
(do (var kvs @[]) (each k (keys form)
|
||||
(array/push kvs (syntax-quote* ctx bindings k))
|
||||
(array/push kvs (syntax-quote* ctx bindings (get form k)))) (struct ;kvs))
|
||||
form))
|
||||
|
||||
(defn- resolve-var
|
||||
[ctx bindings sym-s]
|
||||
(let [name (sym-s :name) ns (sym-s :ns)]
|
||||
(if (not (nil? ns))
|
||||
(let [target-ns (ctx-find-ns ctx ns)] (ns-find target-ns name))
|
||||
(if (get bindings name) nil
|
||||
(let [current-ns (ctx-current-ns ctx) ns (ctx-find-ns ctx current-ns)] (ns-find ns name))))))
|
||||
|
||||
(defn- sym-name-str
|
||||
[sym-s]
|
||||
(if (sym-s :ns) (string (sym-s :ns) "/" (sym-s :name)) (sym-s :name)))
|
||||
|
||||
(defn- eval-require
|
||||
[ctx spec]
|
||||
(let [ns-sym (in spec 0)
|
||||
ns-name (sym-name-str ns-sym)]
|
||||
(var alias nil)
|
||||
(var i 1)
|
||||
(let [slen (length spec)]
|
||||
(while (< i slen)
|
||||
(let [item (in spec i)]
|
||||
(if (or (= item :as) (and (struct? item) (= :symbol (item :jolt/type)) (= "as" (item :name))))
|
||||
(do
|
||||
(def alias-sym (in spec (+ i 1)))
|
||||
(set alias (alias-sym :name))
|
||||
(set i slen))
|
||||
(++ i)))))
|
||||
(ctx-find-ns ctx ns-name)
|
||||
(when alias
|
||||
(let [current-ns (ctx-find-ns ctx (ctx-current-ns ctx))]
|
||||
(ns-import current-ns alias ns-name)))
|
||||
nil))
|
||||
|
||||
(defn- resolve-sym
|
||||
[ctx bindings sym-s]
|
||||
(let [name (sym-s :name) ns (sym-s :ns)]
|
||||
(if (not (nil? ns))
|
||||
(let [current-ns (ctx-find-ns ctx (ctx-current-ns ctx)) aliased-ns (ns-import-lookup current-ns ns)]
|
||||
(if aliased-ns
|
||||
(let [target-ns (ctx-find-ns ctx aliased-ns) v (ns-find target-ns name)]
|
||||
(if v (var-get v) (error (string "Unable to resolve symbol: " ns "/" name))))
|
||||
(let [target-ns (ctx-find-ns ctx ns) v (ns-find target-ns name)]
|
||||
(if v (var-get v) (error (string "Unable to resolve symbol: " ns "/" name))))))
|
||||
(let [local (get bindings name)]
|
||||
(if (not (nil? local)) local
|
||||
(let [current-ns (ctx-current-ns ctx) ns (ctx-find-ns ctx current-ns) v (ns-find ns name)]
|
||||
(if v (var-get v)
|
||||
# Check clojure.core as auto-referred fallback
|
||||
(let [core-ns (ctx-find-ns ctx "clojure.core")
|
||||
core-v (ns-find core-ns name)]
|
||||
(if core-v
|
||||
(var-get core-v)
|
||||
# Fall back to Janet's global environment
|
||||
(let [root-env (fiber/getenv (fiber/current))
|
||||
entry (in root-env (symbol name))]
|
||||
(if (not (nil? entry))
|
||||
(if (table? entry) (entry :value) entry)
|
||||
(error (string "Unable to resolve symbol: " name)))))))))))))
|
||||
|
||||
# Dispatch a special form by its string name. Each branch is a standalone
|
||||
# expression that returns the value directly — no cond, no nested if chains.
|
||||
# We use a local function per form and call the matching one.
|
||||
(defn- eval-list
|
||||
[ctx bindings form]
|
||||
(def first-form (first form))
|
||||
(def name (first-form :name))
|
||||
(match name
|
||||
"quote" (in form 1)
|
||||
"syntax-quote" (syntax-quote* ctx bindings (in form 1))
|
||||
"unquote" (error "Unquote not valid outside of syntax-quote")
|
||||
"unquote-splicing" (error "Unquote-splicing not valid outside of syntax-quote")
|
||||
"do" (do
|
||||
(var result nil)
|
||||
(var i 1)
|
||||
(let [len (length form)]
|
||||
(while (< i len)
|
||||
(set result (eval-form ctx bindings (in form i)))
|
||||
(++ i)))
|
||||
result)
|
||||
"if" (let [test-val (eval-form ctx bindings (in form 1))]
|
||||
(if (and (not (nil? test-val)) (not (= false test-val)))
|
||||
(eval-form ctx bindings (in form 2))
|
||||
(if (> (length form) 3) (eval-form ctx bindings (in form 3)) nil)))
|
||||
"def" (let [name-sym (in form 1)
|
||||
val (eval-form ctx bindings (in form 2))
|
||||
ns-name (ctx-current-ns ctx)
|
||||
ns (ctx-find-ns ctx ns-name)]
|
||||
(ns-intern ns (name-sym :name) val)
|
||||
(var-get (ns-intern ns (name-sym :name))))
|
||||
"ns" (let [ns-name (sym-name-str (in form 1))
|
||||
clauses (tuple/slice form 2)]
|
||||
(ctx-set-current-ns ctx ns-name)
|
||||
(ctx-find-ns ctx ns-name)
|
||||
(var result nil)
|
||||
(var i 0)
|
||||
(let [clen (length clauses)]
|
||||
(while (< i clen)
|
||||
(let [clause (in clauses i)]
|
||||
(if (and (array? clause) (> (length clause) 0) (= :require (first clause)))
|
||||
(let [specs (tuple/slice clause 1)
|
||||
slen (length specs)]
|
||||
(var j 0)
|
||||
(while (< j slen)
|
||||
(let [s (in specs j)]
|
||||
(eval-require ctx s))
|
||||
(++ j))
|
||||
(set i (+ i 1)))
|
||||
(do (set result clause) (++ i))))))
|
||||
result)
|
||||
"require" (let [spec (eval-form ctx bindings (in form 1))]
|
||||
(if (and (tuple? spec) (> (length spec) 0))
|
||||
(eval-require ctx spec)
|
||||
(error "require expects a vector spec")))
|
||||
"in-ns" (let [ns-name (sym-name-str (in form 1))]
|
||||
(ctx-set-current-ns ctx ns-name)
|
||||
(ctx-find-ns ctx ns-name)
|
||||
nil)
|
||||
"fn*" (let [args-form (in form 1)
|
||||
body (tuple/slice form 2)
|
||||
arg-names (map |($ :name) args-form)]
|
||||
(var self nil)
|
||||
(set self (fn [& fn-args]
|
||||
(var fn-bindings @{})
|
||||
(table/setproto fn-bindings bindings)
|
||||
(var i 0)
|
||||
(each arg-name arg-names
|
||||
(put fn-bindings arg-name (fn-args i))
|
||||
(++ i))
|
||||
(put fn-bindings :jolt/loop-fn self)
|
||||
(var result nil)
|
||||
(each body-form body
|
||||
(set result (eval-form ctx fn-bindings body-form)))
|
||||
result))
|
||||
self)
|
||||
"let*" (let [bind-vec (in form 1)
|
||||
body (tuple/slice form 2)]
|
||||
(var new-bindings @{})
|
||||
(table/setproto new-bindings bindings)
|
||||
(var i 0)
|
||||
(let [len (length bind-vec)]
|
||||
(while (< i len)
|
||||
(let [sym (bind-vec i)
|
||||
val (eval-form ctx new-bindings (bind-vec (+ i 1)))]
|
||||
(put new-bindings (sym :name) val)
|
||||
(+= i 2))))
|
||||
(var result nil)
|
||||
(each body-form body
|
||||
(set result (eval-form ctx new-bindings body-form)))
|
||||
result)
|
||||
"loop*" (let [bind-vec (in form 1)
|
||||
body (tuple/slice form 2)
|
||||
init-vals @[]
|
||||
sym-names @[]]
|
||||
(var i 0)
|
||||
(while (< i (length bind-vec))
|
||||
(array/push init-vals (eval-form ctx bindings (bind-vec (+ i 1))))
|
||||
(array/push sym-names ((bind-vec i) :name))
|
||||
(+= i 2))
|
||||
(var loop-fn nil)
|
||||
(set loop-fn (fn [& args]
|
||||
(var loop-bindings @{})
|
||||
(table/setproto loop-bindings bindings)
|
||||
(var j 0)
|
||||
(each sn sym-names
|
||||
(put loop-bindings sn (args j))
|
||||
(++ j))
|
||||
(put loop-bindings :jolt/loop-fn loop-fn)
|
||||
(var result nil)
|
||||
(each body-form body
|
||||
(set result (eval-form ctx loop-bindings body-form)))
|
||||
result))
|
||||
(apply loop-fn init-vals))
|
||||
"recur" (let [loop-fn (get bindings :jolt/loop-fn)]
|
||||
(if (nil? loop-fn)
|
||||
(error "recur used outside of loop* or fn*")
|
||||
(let [args (map |(eval-form ctx bindings $) (tuple/slice form 1))]
|
||||
(apply loop-fn args))))
|
||||
# default: function application — check for macros
|
||||
(if (and (struct? first-form) (= :symbol (first-form :jolt/type)))
|
||||
(let [v (resolve-var ctx bindings first-form)]
|
||||
(if (and v (var-macro? v))
|
||||
(let [macro-fn (var-get v)
|
||||
args (tuple/slice form 1)]
|
||||
(eval-form ctx bindings (apply macro-fn args)))
|
||||
(let [f (eval-form ctx bindings first-form)
|
||||
args (map |(eval-form ctx bindings $) (tuple/slice form 1))]
|
||||
(apply f args))))
|
||||
(let [f (eval-form ctx bindings first-form)
|
||||
args (map |(eval-form ctx bindings $) (tuple/slice form 1))]
|
||||
(apply f args)))))
|
||||
|
||||
(set eval-form (fn [ctx bindings form]
|
||||
(cond
|
||||
(nil? form) nil
|
||||
(number? form) form
|
||||
(string? form) form
|
||||
(keyword? form) form
|
||||
(bytes? form) form
|
||||
(buffer? form) form
|
||||
(tuple? form) (tuple/slice (map |(eval-form ctx bindings $) form))
|
||||
(struct? form)
|
||||
(if (= :symbol (form :jolt/type))
|
||||
(resolve-sym ctx bindings form)
|
||||
(if (get form :jolt/type)
|
||||
(error (string "Unexpected tagged form: " (form :jolt/type)))
|
||||
form))
|
||||
(array? form)
|
||||
(if (= 0 (length form))
|
||||
@[]
|
||||
(eval-list ctx bindings form))
|
||||
form)))
|
||||
34
src/jolt/main.janet
Normal file
34
src/jolt/main.janet
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Jolt REPL
|
||||
# Read-eval-print loop for Clojure expressions.
|
||||
|
||||
(use ./api)
|
||||
(use ./types)
|
||||
|
||||
(def ctx (init))
|
||||
|
||||
(defn read-line [prompt]
|
||||
(prin prompt)
|
||||
(flush)
|
||||
(let [line (file/read stdin :line)]
|
||||
(if line (string/trim line) nil)))
|
||||
|
||||
(defn print-value [v]
|
||||
(if (nil? v)
|
||||
(print "nil")
|
||||
(if (and (table? v) (= :jolt/var (v :jolt/type)))
|
||||
(printf "#'%s/%s" (ctx-current-ns ctx) ((var-name v) :name))
|
||||
(print v))))
|
||||
|
||||
(print "Jolt — Clojure on Janet")
|
||||
(print "Type (exit) to quit.\n")
|
||||
|
||||
(var running true)
|
||||
(while running
|
||||
(let [line (read-line (string (ctx-current-ns ctx) "=> "))]
|
||||
(if (nil? line) (set running false)
|
||||
(if (= line "(exit)") (set running false)
|
||||
(if (not (= "" line))
|
||||
(try
|
||||
(print-value (eval-string ctx line))
|
||||
([err]
|
||||
(eprint "Error: " err))))))))
|
||||
383
src/jolt/reader.janet
Normal file
383
src/jolt/reader.janet
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
# Jolt Clojure Reader
|
||||
# Recursive descent parser for Clojure source text.
|
||||
# Output convention:
|
||||
# Symbols foo, foo/bar → {:jolt/type :symbol :ns "foo" :name "bar"}
|
||||
# Keywords :foo, :foo/bar → Janet keyword :foo, :foo/bar
|
||||
# Lists (a b c) → Janet array @[a b c]
|
||||
# Vectors [a b c] → Janet tuple [a b c]
|
||||
# Maps {:a 1} → Janet struct {:a 1}
|
||||
# Sets #{1 2} → tagged struct {:jolt/type :jolt/set :value [1 2]}
|
||||
|
||||
# Forward declaration for mutual recursion
|
||||
(var read-form nil)
|
||||
|
||||
(def whitespace-chars " \t\n\r,")
|
||||
|
||||
(defn whitespace? [c]
|
||||
(or (= c 32) # space
|
||||
(= c 10) # \n
|
||||
(= c 9) # \t
|
||||
(= c 13) # \r
|
||||
(= c 44))) # comma
|
||||
|
||||
(defn skip-whitespace [s pos]
|
||||
(if (and (< pos (length s))
|
||||
(whitespace? (s pos)))
|
||||
(skip-whitespace s (+ pos 1))
|
||||
pos))
|
||||
|
||||
(defn digit? [c]
|
||||
(and (>= c 48) (<= c 57)))
|
||||
|
||||
(defn hex-digit? [c]
|
||||
(or (and (>= c 48) (<= c 57))
|
||||
(and (>= c 65) (<= c 70))
|
||||
(and (>= c 97) (<= c 102))))
|
||||
|
||||
(defn symbol-start? [c]
|
||||
(or (and (>= c 65) (<= c 90)) # A-Z
|
||||
(and (>= c 97) (<= c 122)) # a-z
|
||||
(= c 42) # *
|
||||
(= c 43) # +
|
||||
(= c 33) # !
|
||||
(= c 95) # _
|
||||
(= c 45) # -
|
||||
(= c 63) # ?
|
||||
(= c 46) # .
|
||||
(= c 60) # <
|
||||
(= c 62) # >
|
||||
(= c 61) # =
|
||||
(= c 38) # &
|
||||
(= c 124) # |
|
||||
(= c 36) # $
|
||||
(= c 37) # %
|
||||
(= c 47))) # /
|
||||
|
||||
(defn symbol-char? [c]
|
||||
(or (symbol-start? c)
|
||||
(digit? c)
|
||||
(= c 35) # #
|
||||
(= c 39) # '
|
||||
(= c 58))) # :
|
||||
|
||||
(defn read-symbol-name [s pos end]
|
||||
(if (and (< end (length s))
|
||||
(symbol-char? (s end)))
|
||||
(read-symbol-name s pos (+ end 1))
|
||||
end))
|
||||
|
||||
(defn make-symbol
|
||||
"Create a Jolt symbol struct."
|
||||
[name]
|
||||
(let [slash (string/find "/" name)]
|
||||
(if slash
|
||||
{:jolt/type :symbol
|
||||
:ns (string/slice name 0 slash)
|
||||
:name (string/slice name (+ slash 1))}
|
||||
{:jolt/type :symbol
|
||||
:ns nil
|
||||
:name name})))
|
||||
|
||||
(defn sym
|
||||
"Convenience to create a symbol during testing."
|
||||
[name]
|
||||
(make-symbol name))
|
||||
|
||||
(defn read-symbol [s pos]
|
||||
(let [end (read-symbol-name s pos pos)]
|
||||
(if (= end pos)
|
||||
(error (string "Unrecognized character at " pos ": " (string/from-bytes (s pos))))
|
||||
(let [name (string/slice s pos end)]
|
||||
(if (= name "nil") [nil end]
|
||||
(if (= name "true") [true end]
|
||||
(if (= name "false") [false end]
|
||||
[(make-symbol name) end])))))))
|
||||
|
||||
(defn read-keyword-name [s pos end]
|
||||
(if (and (< end (length s))
|
||||
(symbol-char? (s end)))
|
||||
(read-keyword-name s pos (+ end 1))
|
||||
end))
|
||||
|
||||
(defn read-keyword [s pos]
|
||||
# pos is at the first colon
|
||||
(if (and (< (+ pos 1) (length s)) (= (s (+ pos 1)) 58))
|
||||
# ::foo/bar — auto-resolved keyword
|
||||
(let [start (+ pos 2)
|
||||
end (read-keyword-name s start start)
|
||||
name (string/slice s start end)]
|
||||
[(keyword name) end])
|
||||
# :foo or :foo/bar
|
||||
(let [start (+ pos 1)
|
||||
end (read-keyword-name s start start)
|
||||
name (string/slice s start end)]
|
||||
[(keyword name) end])))
|
||||
|
||||
(defn escape-char [c]
|
||||
(if (= c 110) "\n"
|
||||
(if (= c 116) "\t"
|
||||
(if (= c 114) "\r"
|
||||
(if (= c 92) "\\"
|
||||
(if (= c 34) "\""
|
||||
(string/from-bytes c)))))))
|
||||
|
||||
(defn read-string-chars [s pos end chars]
|
||||
(if (>= pos end)
|
||||
(error "Unterminated string")
|
||||
(let [c (s pos)]
|
||||
(if (= c 92) # backslash
|
||||
(let [next-pos (+ pos 1)]
|
||||
(if (>= next-pos end)
|
||||
(error "Unterminated escape")
|
||||
(read-string-chars s (+ pos 2) end
|
||||
(array/push chars (escape-char (s next-pos))))))
|
||||
(if (= c 34) # closing quote
|
||||
[pos chars]
|
||||
(read-string-chars s (+ pos 1) end
|
||||
(array/push chars (string/from-bytes c))))))))
|
||||
|
||||
(defn read-string [s pos]
|
||||
# pos is at opening double-quote
|
||||
(let [end (length s)
|
||||
[new-pos chars] (read-string-chars s (+ pos 1) end @[])]
|
||||
[(string/join chars "") (+ new-pos 1)]))
|
||||
|
||||
(defn read-hex-digits [s pos end]
|
||||
(if (and (< end (length s)) (hex-digit? (s end)))
|
||||
(read-hex-digits s pos (+ end 1))
|
||||
end))
|
||||
|
||||
(defn read-digits [s pos end]
|
||||
(if (and (< end (length s)) (digit? (s end)))
|
||||
(read-digits s pos (+ end 1))
|
||||
end))
|
||||
|
||||
(defn read-fractional [s pos end]
|
||||
(if (and (< end (length s)) (digit? (s end)))
|
||||
(read-fractional s pos (+ end 1))
|
||||
end))
|
||||
|
||||
(defn read-number [s pos]
|
||||
(var start pos) # start is mutable for sign handling
|
||||
(var neg false)
|
||||
|
||||
# optional sign
|
||||
(if (and (< pos (length s)) (= (s pos) 45))
|
||||
(do (set start (+ pos 1)) (set neg true)))
|
||||
|
||||
(let [pos start
|
||||
hex? (and (< (+ pos 1) (length s))
|
||||
(= (s pos) 48) (= (s (+ pos 1)) 120))
|
||||
start (if hex? (+ pos 2) pos)
|
||||
end (if hex?
|
||||
(read-hex-digits s start start)
|
||||
(read-digits s start start))]
|
||||
(if (= end start) (error (string "Expected number at " pos)))
|
||||
|
||||
# check for fractional part
|
||||
(if (and (not hex?)
|
||||
(< end (length s))
|
||||
(= (s end) 46))
|
||||
(let [frac-start (+ end 1)
|
||||
frac-end (read-fractional s frac-start frac-start)]
|
||||
(if (= frac-end frac-start) (error "Expected digit after ."))
|
||||
(let [num-str (string/slice s start frac-end)
|
||||
val (scan-number num-str)]
|
||||
[(if neg (- val) val) frac-end]))
|
||||
|
||||
# integer or hex
|
||||
(let [num-str (string/slice s start end)
|
||||
val (if hex?
|
||||
(string/format "0x%s" num-str)
|
||||
num-str)
|
||||
val (scan-number val)]
|
||||
[(if neg (- val) val) end]))))
|
||||
|
||||
(defn read-list [s pos]
|
||||
# pos is at opening paren
|
||||
(defn read-list-items [s pos items]
|
||||
(let [pos (skip-whitespace s pos)]
|
||||
(if (>= pos (length s))
|
||||
(error "Unterminated list"))
|
||||
(if (= (s pos) 41) # )
|
||||
[items (+ pos 1)]
|
||||
(let [[form new-pos] (read-form s pos)]
|
||||
(read-list-items s new-pos (array/push items form))))))
|
||||
(read-list-items s (+ pos 1) @[]))
|
||||
|
||||
(defn read-vector [s pos]
|
||||
# pos is at opening bracket
|
||||
(defn read-vec-items [s pos items]
|
||||
(let [pos (skip-whitespace s pos)]
|
||||
(if (>= pos (length s))
|
||||
(error "Unterminated vector"))
|
||||
(if (= (s pos) 93) # ]
|
||||
[(tuple/slice (tuple ;items)) (+ pos 1)]
|
||||
(let [[form new-pos] (read-form s pos)]
|
||||
(read-vec-items s new-pos (array/push items form))))))
|
||||
(read-vec-items s (+ pos 1) @[]))
|
||||
|
||||
(defn read-map [s pos]
|
||||
# pos is at opening brace
|
||||
(defn read-kvs [s pos kvs]
|
||||
(let [pos (skip-whitespace s pos)]
|
||||
(if (>= pos (length s))
|
||||
(error "Unterminated map"))
|
||||
(if (= (s pos) 125) # }
|
||||
[(struct ;kvs) (+ pos 1)]
|
||||
(let [[key new-pos] (read-form s pos)
|
||||
pos (skip-whitespace s new-pos)
|
||||
[val new-pos2] (read-form s pos)]
|
||||
(read-kvs s new-pos2 (-> kvs (array/push key) (array/push val)))))))
|
||||
(read-kvs s (+ pos 1) @[]))
|
||||
|
||||
(defn read-set [s pos]
|
||||
# pos is at #, next char is {
|
||||
(defn read-set-items [s pos items]
|
||||
(let [pos (skip-whitespace s pos)]
|
||||
(if (>= pos (length s))
|
||||
(error "Unterminated set"))
|
||||
(if (= (s pos) 125) # }
|
||||
[{:jolt/type :jolt/set :value (tuple/slice (tuple ;items))} (+ pos 1)]
|
||||
(let [[form new-pos] (read-form s pos)]
|
||||
(read-set-items s new-pos (array/push items form))))))
|
||||
(read-set-items s (+ pos 2) @[]))
|
||||
|
||||
(defn read-char-name-end [s pos]
|
||||
(if (and (< pos (length s)) (symbol-char? (s pos)))
|
||||
(read-char-name-end s (+ pos 1))
|
||||
pos))
|
||||
|
||||
(defn read-char [s pos]
|
||||
# pos is at backslash
|
||||
(let [end (read-char-name-end s (+ pos 1))
|
||||
char-name (string/slice s (+ pos 1) end)]
|
||||
[{:jolt/type :char :name char-name} end]))
|
||||
|
||||
(defn read-anon-fn [s pos]
|
||||
# pos is at #, next char is (
|
||||
(let [[form new-pos] (read-form s (+ pos 1))]
|
||||
[(array/insert form 0 (sym "fn*")) new-pos]))
|
||||
|
||||
(defn read-reader-conditional [s pos]
|
||||
# pos is at #, next char is ?
|
||||
(let [[form new-pos] (read-form s (+ pos 2))]
|
||||
[{:jolt/type :jolt/reader-conditional :clauses form} new-pos]))
|
||||
|
||||
(defn read-var-quote [s pos]
|
||||
# pos is at #, next char is '
|
||||
(let [[form new-pos] (read-form s (+ pos 2))]
|
||||
[(array (sym "var") form) new-pos]))
|
||||
|
||||
(defn read-dispatch [s pos]
|
||||
# pos is at #
|
||||
(if (>= (+ pos 1) (length s))
|
||||
(error "Unexpected end after #"))
|
||||
(let [c (s (+ pos 1))]
|
||||
(cond
|
||||
(= c 123) (read-set s pos) # #{
|
||||
(= c 40) (read-anon-fn s pos) # #(
|
||||
(= c 63) (read-reader-conditional s pos) # #?
|
||||
(= c 95) (let [[_ new-pos] (read-form s (+ pos 2))] # #_
|
||||
(read-form s new-pos))
|
||||
(= c 39) (read-var-quote s pos) # #'
|
||||
# unknown dispatch — tagged literal
|
||||
(let [end (read-symbol-name s pos pos)
|
||||
tag (string/slice s pos end)
|
||||
[form new-pos] (read-form s end)]
|
||||
[{:jolt/type :jolt/tagged :tag (keyword tag) :form form} new-pos]))))
|
||||
|
||||
(defn read-quote [s pos new-pos token-sym]
|
||||
(let [[form final-pos] (read-form s new-pos)]
|
||||
[(array token-sym form) final-pos]))
|
||||
|
||||
(defn read-meta [s pos]
|
||||
# pos is at ^
|
||||
(let [[meta-form new-pos] (read-form s (+ pos 1))
|
||||
[form new-pos2] (read-form s new-pos)]
|
||||
[(array (sym "with-meta") form meta-form) new-pos2]))
|
||||
|
||||
(defn read-until-newline [s pos]
|
||||
(if (or (>= pos (length s)) (= (s pos) 10))
|
||||
pos
|
||||
(read-until-newline s (+ pos 1))))
|
||||
|
||||
(set read-form (fn [s pos]
|
||||
(let [pos (skip-whitespace s pos)]
|
||||
(if (>= pos (length s))
|
||||
[nil pos]
|
||||
(let [c (s pos)]
|
||||
(cond
|
||||
# comment
|
||||
(= c 59)
|
||||
(let [line-end (read-until-newline s pos)]
|
||||
(read-form s line-end))
|
||||
|
||||
# dispatch
|
||||
(= c 35)
|
||||
(read-dispatch s pos)
|
||||
|
||||
# string
|
||||
(= c 34)
|
||||
(read-string s pos)
|
||||
|
||||
# list
|
||||
(= c 40)
|
||||
(read-list s pos)
|
||||
|
||||
# vector
|
||||
(= c 91)
|
||||
(read-vector s pos)
|
||||
|
||||
# map
|
||||
(= c 123)
|
||||
(read-map s pos)
|
||||
|
||||
# keyword
|
||||
(= c 58)
|
||||
(read-keyword s pos)
|
||||
|
||||
# quote
|
||||
(= c 39)
|
||||
(read-quote s pos (+ pos 1) (sym "quote"))
|
||||
|
||||
# syntax-quote / backtick
|
||||
(= c 96)
|
||||
(read-quote s pos (+ pos 1) (sym "syntax-quote"))
|
||||
|
||||
# unquote ~
|
||||
(= c 126)
|
||||
(if (and (< (+ pos 1) (length s)) (= (s (+ pos 1)) 64))
|
||||
(read-quote s pos (+ pos 2) (sym "unquote-splicing"))
|
||||
(read-quote s pos (+ pos 1) (sym "unquote")))
|
||||
|
||||
# deref
|
||||
(= c 64)
|
||||
(read-quote s pos (+ pos 1) (sym "deref"))
|
||||
|
||||
# metadata
|
||||
(= c 94)
|
||||
(read-meta s pos)
|
||||
|
||||
# character
|
||||
(= c 92)
|
||||
(read-char s pos)
|
||||
|
||||
# number or symbol
|
||||
(if (or (digit? c)
|
||||
(and (= c 45) (< (+ pos 1) (length s)) (digit? (s (+ pos 1))))
|
||||
(and (= c 43) (< (+ pos 1) (length s)) (digit? (s (+ pos 1)))))
|
||||
(read-number s pos)
|
||||
(read-symbol s pos))))))))
|
||||
|
||||
(defn parse-string
|
||||
"Parse a Clojure source string and return the first form."
|
||||
[s]
|
||||
(let [[form _] (read-form s 0)]
|
||||
form))
|
||||
|
||||
(defn parse-next
|
||||
"Parse the first form from a string. Returns [form remaining-string]."
|
||||
[s]
|
||||
(let [[form pos] (read-form s 0)]
|
||||
[form (string/slice s pos)]))
|
||||
276
src/jolt/types.janet
Normal file
276
src/jolt/types.janet
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
# Jolt Types
|
||||
# Core types for the Clojure-on-Janet interpreter.
|
||||
#
|
||||
# Types:
|
||||
# JoltVar — mutable container with metadata (like Clojure Var)
|
||||
# JoltNamespace — namespace with symbol→var mappings and imports
|
||||
# JoltContext — evaluation context (env atom, namespaces)
|
||||
#
|
||||
# Symbols are represented as {:jolt/type :symbol :ns <string-or-nil> :name <string>}
|
||||
# as produced by the reader.
|
||||
|
||||
# ============================================================
|
||||
# Symbol helpers
|
||||
# ============================================================
|
||||
|
||||
(defn sym?
|
||||
"Check if x is a Jolt symbol struct."
|
||||
[x]
|
||||
(and (struct? x) (= :symbol (x :jolt/type))))
|
||||
|
||||
# ============================================================
|
||||
# Var
|
||||
# ============================================================
|
||||
|
||||
(def- binding-stack @[]) # stack of {var → value} tables for thread-local bindings
|
||||
|
||||
(defn push-thread-bindings
|
||||
"Push a frame of dynamic var bindings. Takes a struct of var→value."
|
||||
[bindings]
|
||||
(array/push binding-stack bindings))
|
||||
|
||||
(defn pop-thread-bindings
|
||||
"Pop the most recent frame of dynamic var bindings."
|
||||
[]
|
||||
(array/pop binding-stack))
|
||||
|
||||
(defn make-var
|
||||
"Create a new Jolt Var.
|
||||
(make-var name) — unbound var
|
||||
(make-var name init-val) — var with root binding
|
||||
(make-var name init-val meta) — var with root and metadata
|
||||
|
||||
name is a symbol struct {:jolt/type :symbol ...}"
|
||||
[name &opt init-val meta]
|
||||
(default init-val nil)
|
||||
(default meta nil)
|
||||
(let [m (if meta meta {:name name})
|
||||
result @{:jolt/type :jolt/var
|
||||
:name name
|
||||
:root init-val
|
||||
:meta m
|
||||
:dynamic (if meta (get meta :dynamic) false)
|
||||
:macro (if meta (get meta :macro) false)
|
||||
:ns (if meta (get meta :ns) nil)}]
|
||||
result))
|
||||
|
||||
(defn var?
|
||||
"Check if x is a Jolt Var."
|
||||
[x]
|
||||
(and (table? x) (= :jolt/var (x :jolt/type))))
|
||||
|
||||
(defn var-dynamic?
|
||||
"Check if var is marked :dynamic."
|
||||
[v]
|
||||
(v :dynamic))
|
||||
|
||||
(defn var-macro?
|
||||
"Check if var is marked :macro."
|
||||
[v]
|
||||
(v :macro))
|
||||
|
||||
(defn var-name
|
||||
"Return the symbol name of the var."
|
||||
[v]
|
||||
(v :name))
|
||||
|
||||
(defn var-meta
|
||||
"Return the metadata of the var."
|
||||
[v]
|
||||
(v :meta))
|
||||
|
||||
(defn var-ns
|
||||
"Return the namespace of the var."
|
||||
[v]
|
||||
(v :ns))
|
||||
|
||||
(defn var-get
|
||||
"Deref the var. If the var is dynamic and has a thread-local binding, return that.
|
||||
Otherwise return the root binding."
|
||||
[v]
|
||||
# walk binding stack top-down for this var
|
||||
(var result nil)
|
||||
(var i (dec (length binding-stack)))
|
||||
(while (>= i 0)
|
||||
(let [frame (in binding-stack i)
|
||||
val (get frame v)]
|
||||
(if (not (nil? val))
|
||||
(do
|
||||
(set result (if (var? val) (var-get val) val))
|
||||
(set i -1))
|
||||
(-- i))))
|
||||
(if (not (nil? result)) result (v :root)))
|
||||
|
||||
(defn var-set
|
||||
"Set the root binding of a var."
|
||||
[v val]
|
||||
(put v :root val))
|
||||
|
||||
(defn alter-var-root
|
||||
"Atomically alter the root binding of v by applying f to current value plus args."
|
||||
[v f & args]
|
||||
(let [new-val (f (v :root) ;args)]
|
||||
(put v :root new-val)))
|
||||
|
||||
(defn with-meta
|
||||
"Return a new var with updated metadata. The original var is unchanged."
|
||||
[v meta]
|
||||
# build new meta as a table first (to allow adding keys), then convert
|
||||
(let [new-meta-table (merge @{} (v :meta) meta)
|
||||
# convert to struct by extracting all keys
|
||||
new-meta (table/to-struct new-meta-table)]
|
||||
@{:jolt/type :jolt/var
|
||||
:name (v :name)
|
||||
:root (v :root)
|
||||
:meta new-meta
|
||||
:dynamic (v :dynamic)
|
||||
:macro (v :macro)
|
||||
:ns (v :ns)}))
|
||||
|
||||
(defn bind-root
|
||||
"Set the root binding (internal, same as var-set)."
|
||||
[v val]
|
||||
(put v :root val))
|
||||
|
||||
# ============================================================
|
||||
# Namespace
|
||||
# ============================================================
|
||||
|
||||
(defn make-ns
|
||||
"Create a new namespace.
|
||||
(make-ns name) — empty namespace
|
||||
name is a symbol struct {:jolt/type :symbol ...}"
|
||||
[name]
|
||||
(struct
|
||||
:jolt/type :jolt/namespace
|
||||
:name name
|
||||
:mappings @{}
|
||||
:imports @{}
|
||||
:aliases @{}))
|
||||
|
||||
(defn ns?
|
||||
"Check if x is a Jolt Namespace."
|
||||
[x]
|
||||
(and (struct? x) (= :jolt/namespace (x :jolt/type))))
|
||||
|
||||
(defn ns-name
|
||||
"Return the name symbol of a namespace."
|
||||
[ns]
|
||||
(ns :name))
|
||||
|
||||
(defn ns-map
|
||||
"Return the mappings table (symbol → var) for a namespace."
|
||||
[ns]
|
||||
(ns :mappings))
|
||||
|
||||
(defn ns-intern
|
||||
"Find or create a var named by sym in namespace ns, setting root binding to val if given.
|
||||
(ns-intern ns sym) — find or create unbound var
|
||||
(ns-intern ns sym val) — find or create with root binding"
|
||||
[ns sym &opt val]
|
||||
(default val nil)
|
||||
(let [mappings (ns :mappings)
|
||||
existing (get mappings sym)]
|
||||
(if existing
|
||||
(do
|
||||
(when (not (nil? val))
|
||||
(bind-root existing val))
|
||||
existing)
|
||||
(let [v (make-var sym val {:ns ns :name sym})]
|
||||
(put mappings sym v)
|
||||
v))))
|
||||
|
||||
(defn ns-find
|
||||
"Find a var by symbol in the namespace. Returns nil if not found."
|
||||
[ns sym]
|
||||
(get (ns :mappings) sym))
|
||||
|
||||
(defn ns-unmap
|
||||
"Remove a mapping by symbol from the namespace."
|
||||
[ns sym]
|
||||
(put (ns :mappings) sym nil))
|
||||
|
||||
(defn ns-resolve
|
||||
"Resolve a symbol in a namespace. Looks in own mappings first,
|
||||
then aliases. Returns the var or nil."
|
||||
[ns sym]
|
||||
(or (ns-find ns sym)
|
||||
(let [qualified? (sym? sym)]
|
||||
(when qualified?
|
||||
# qualified symbol: look up via alias
|
||||
(let [alias-ns (get (ns :aliases) (sym :ns))]
|
||||
(when alias-ns
|
||||
(ns-find alias-ns (sym :name))))))))
|
||||
|
||||
(defn ns-import
|
||||
"Add an import to the namespace. class-name is local symbol, fq-class-name is the full qualified name."
|
||||
[ns class-name fq-class-name]
|
||||
(put (ns :imports) class-name fq-class-name))
|
||||
|
||||
(defn ns-import-lookup
|
||||
"Look up an import in the namespace. Returns the full qualified name or nil."
|
||||
[ns class-name]
|
||||
(get (ns :imports) class-name))
|
||||
|
||||
(defn ns-add-alias
|
||||
"Add an alias from alias-sym to target-ns."
|
||||
[ns alias-sym target-ns]
|
||||
(put (ns :aliases) alias-sym target-ns))
|
||||
|
||||
# ============================================================
|
||||
# Context
|
||||
# ============================================================
|
||||
|
||||
(defn ctx-find-ns
|
||||
"Find or create a namespace in the context by name symbol."
|
||||
[ctx ns-sym]
|
||||
(let [env (ctx :env)
|
||||
namespaces (env :namespaces)]
|
||||
(or (get namespaces ns-sym)
|
||||
(let [ns (make-ns ns-sym)]
|
||||
(put namespaces ns-sym ns)
|
||||
ns))))
|
||||
|
||||
(defn make-ctx
|
||||
"Create a new evaluation context.
|
||||
(make-ctx) — empty context with 'user namespace
|
||||
(make-ctx opts) — context with initial namespaces from opts
|
||||
|
||||
opts may contain:
|
||||
:namespaces — struct of {ns-symbol → {sym → value, ...}, ...}"
|
||||
[&opt opts]
|
||||
(default opts nil)
|
||||
(let [env @{:namespaces @{}
|
||||
:class->opts @{}
|
||||
:current-ns "user"}
|
||||
# create the user namespace via a partial context
|
||||
_ (ctx-find-ns {:env env} "user")]
|
||||
# initialize from opts
|
||||
(when opts
|
||||
(when-let [ns-opts (get opts :namespaces)]
|
||||
(loop [[ns-sym mappings] :pairs ns-opts]
|
||||
(let [ns (ctx-find-ns {:env env} ns-sym)]
|
||||
(loop [[sym val] :pairs mappings]
|
||||
(ns-intern ns sym val))))))
|
||||
{:jolt/type :jolt/context
|
||||
:env env}))
|
||||
|
||||
(defn ctx?
|
||||
"Check if x is a Jolt Context."
|
||||
[x]
|
||||
(and (struct? x) (= :jolt/context (x :jolt/type))))
|
||||
|
||||
(defn ctx-env
|
||||
"Return the env atom from the context."
|
||||
[ctx]
|
||||
(ctx :env))
|
||||
|
||||
(defn ctx-current-ns
|
||||
"Get the current namespace symbol."
|
||||
[ctx]
|
||||
(get (ctx :env) :current-ns))
|
||||
|
||||
(defn ctx-set-current-ns
|
||||
"Set the current namespace symbol."
|
||||
[ctx ns-sym]
|
||||
(put (ctx :env) :current-ns ns-sym))
|
||||
Loading…
Add table
Add a link
Reference in a new issue