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
38
test/api-test.janet
Normal file
38
test/api-test.janet
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
(use ../src/jolt/api)
|
||||
(use ../src/jolt/types)
|
||||
|
||||
(print "1: init creates context...")
|
||||
(let [ctx (init)]
|
||||
(assert (ctx? ctx) "init returns context")
|
||||
(let [ns (ctx-find-ns ctx "clojure.core")]
|
||||
(assert (ns? ns) "clojure.core namespace exists")
|
||||
(assert (ns-find ns "nil?") "nil? is interned")
|
||||
(assert (ns-find ns "+") "+ is interned")))
|
||||
(print " passed")
|
||||
|
||||
(print "2: eval-string basics...")
|
||||
(let [ctx (init)]
|
||||
(assert (= 42 (eval-string ctx "42")) "eval integer")
|
||||
(assert (= true (eval-string ctx "true")) "eval bool")
|
||||
(assert (= 3 (eval-string ctx "(+ 1 2)")) "eval list"))
|
||||
(print " passed")
|
||||
|
||||
(print "3: eval-string with core fns...")
|
||||
(let [ctx (init)]
|
||||
(assert (= true (eval-string ctx "(nil? nil)")) "nil?")
|
||||
(assert (deep= [2 3 4] (eval-string ctx "(map inc [1 2 3])")) "map+inc")
|
||||
(assert (= 6 (eval-string ctx "(reduce + [1 2 3])")) "reduce"))
|
||||
(print " passed")
|
||||
|
||||
(print "4: eval-string with def...")
|
||||
(let [ctx (init)]
|
||||
(eval-string ctx "(def x 42)")
|
||||
(assert (= 42 (eval-string ctx "x")) "def then resolve"))
|
||||
(print " passed")
|
||||
|
||||
(print "5: eval-string* with bindings...")
|
||||
(let [ctx (init)]
|
||||
(assert (= 99 (eval-string* ctx "y" @{"y" 99})) "bound variable"))
|
||||
(print " passed")
|
||||
|
||||
(print "\nAll API tests passed!")
|
||||
110
test/core-test.janet
Normal file
110
test/core-test.janet
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
(use ../src/jolt/types)
|
||||
(use ../src/jolt/reader)
|
||||
(use ../src/jolt/evaluator)
|
||||
(use ../src/jolt/core)
|
||||
|
||||
# Helper: create a fresh bootstrapped context
|
||||
(defn make-boot-ctx []
|
||||
(let [ctx (make-ctx)]
|
||||
(init-core! ctx)
|
||||
ctx))
|
||||
|
||||
# Helper: parse + eval
|
||||
(defn eval-str [ctx s]
|
||||
(let [form (parse-string s)]
|
||||
(eval-form ctx @{} form)))
|
||||
|
||||
(print "1: predicates...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (= true (eval-str ctx "(nil? nil)")) "nil?")
|
||||
(assert (= false (eval-str ctx "(nil? 1)")) "nil? false")
|
||||
(assert (= true (eval-str ctx "(string? \"hello\")")) "string?")
|
||||
(assert (= true (eval-str ctx "(number? 42)")) "number?")
|
||||
(assert (= true (eval-str ctx "(fn? inc)")) "fn?")
|
||||
(assert (= true (eval-str ctx "(keyword? :foo)")) "keyword?")
|
||||
(assert (= false (eval-str ctx "(keyword? 1)")) "keyword? false")
|
||||
(assert (= true (eval-str ctx "(zero? 0)")) "zero?")
|
||||
(assert (= true (eval-str ctx "(pos? 1)")) "pos?")
|
||||
(assert (= true (eval-str ctx "(neg? -1)")) "neg?")
|
||||
(assert (= true (eval-str ctx "(even? 2)")) "even?")
|
||||
(assert (= true (eval-str ctx "(odd? 1)")) "odd?")
|
||||
(assert (= true (eval-str ctx "(empty? [])")) "empty? vector")
|
||||
(assert (= false (eval-str ctx "(empty? [1])")) "empty? non-empty"))
|
||||
(print " passed")
|
||||
|
||||
(print "2: math...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (= 0 (eval-str ctx "(+)")) "+ 0 args")
|
||||
(assert (= 5 (eval-str ctx "(+ 2 3)")) "+ 2 args")
|
||||
(assert (= 10 (eval-str ctx "(+ 1 2 3 4)")) "+ varargs")
|
||||
(assert (= -5 (eval-str ctx "(- 5)")) "- unary")
|
||||
(assert (= 2 (eval-str ctx "(- 5 3)")) "- binary")
|
||||
(assert (= 6 (eval-str ctx "(* 2 3)")) "*")
|
||||
(assert (= 1 (eval-str ctx "(*)")) "* 0 args")
|
||||
(assert (= 42 (eval-str ctx "(inc 41)")) "inc")
|
||||
(assert (= 40 (eval-str ctx "(dec 41)")) "dec")
|
||||
(assert (= 4 (eval-str ctx "(max 1 4 2)")) "max"))
|
||||
(print " passed")
|
||||
|
||||
(print "3: comparison...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (= true (eval-str ctx "(= 1 1)")) "= same")
|
||||
(assert (= false (eval-str ctx "(= 1 2)")) "= diff")
|
||||
(assert (= true (eval-str ctx "(= 1 1 1)")) "= multi same")
|
||||
(assert (= false (eval-str ctx "(= 1 2 1)")) "= multi diff")
|
||||
(assert (= true (eval-str ctx "(not= 1 2)")) "not="))
|
||||
(print " passed")
|
||||
|
||||
(print "4: collections...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (= 3 (eval-str ctx "(count [1 2 3])")) "count vector")
|
||||
(assert (= 1 (eval-str ctx "(first [1 2 3])")) "first")
|
||||
(assert (deep= [2 3] (eval-str ctx "(rest [1 2 3])")) "rest")
|
||||
(assert (= nil (eval-str ctx "(next [1])")) "next singleton")
|
||||
(assert (deep= [1 2 3] (eval-str ctx "(conj [1 2] 3)")) "conj vector")
|
||||
(assert (= 1 (eval-str ctx "(get {:a 1} :a)")) "get map")
|
||||
(assert (= 2 (eval-str ctx "(get [1 2 3] 1)")) "get vector")
|
||||
(assert (= :default (eval-str ctx "(get {:a 1} :b :default)")) "get default")
|
||||
(assert (deep= {:a 1 :c 3} (eval-str ctx "(assoc {:a 1} :c 3)")) "assoc")
|
||||
(assert (deep= {:a 1} (eval-str ctx "(dissoc {:a 1 :b 2} :b)")) "dissoc"))
|
||||
(print " passed")
|
||||
|
||||
(print "5: seq ops...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (deep= [2 3 4] (eval-str ctx "(map inc [1 2 3])")) "map")
|
||||
(assert (deep= [2 4] (eval-str ctx "(filter even? [1 2 3 4])")) "filter")
|
||||
(assert (= 6 (eval-str ctx "(reduce + [1 2 3])")) "reduce")
|
||||
(assert (= 10 (eval-str ctx "(reduce + 4 [1 2 3])")) "reduce with val")
|
||||
(assert (deep= [1 2] (eval-str ctx "(take 2 [1 2 3 4])")) "take")
|
||||
(assert (deep= [3 4] (eval-str ctx "(drop 2 [1 2 3 4])")) "drop")
|
||||
(assert (deep= [1 2] (eval-str ctx "(take-while (fn* [x] (<= x 2)) [1 2 3 4])")) "take-while"))
|
||||
(print " passed")
|
||||
|
||||
(print "6: range...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (deep= [0 1 2 3 4] (eval-str ctx "(range 5)")) "range end")
|
||||
(assert (deep= [2 3 4] (eval-str ctx "(range 2 5)")) "range start end"))
|
||||
(print " passed")
|
||||
|
||||
(print "7: higher-order...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (= 42 (eval-str ctx "(identity 42)")) "identity")
|
||||
(assert (= 42 (eval-str ctx "(let* [f (constantly 42)] (f))")) "constantly")
|
||||
(assert (= 3 (eval-str ctx "(let* [f (comp inc inc)] (f 1))")) "comp")
|
||||
(assert (deep= [2 0] (eval-str ctx "(let* [f (juxt inc dec)] (f 1))")) "juxt"))
|
||||
(print " passed")
|
||||
|
||||
(print "8: str...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (= "hello" (eval-str ctx "(str \"hello\")")) "str")
|
||||
(assert (= "hello42" (eval-str ctx "(str \"hello\" 42)")) "str concat"))
|
||||
(print " passed")
|
||||
|
||||
(print "9: atom...")
|
||||
(let [ctx (make-boot-ctx)]
|
||||
(assert (= 1 (eval-str ctx "(let* [a (atom 1)] (deref a))")) "atom + deref")
|
||||
(assert (= 42 (eval-str ctx "(let* [a (atom 1)] (reset! a 42) (deref a))")) "reset!")
|
||||
(assert (= 2 (eval-str ctx "(let* [a (atom 1)] (swap! a inc) (deref a))")) "swap!"))
|
||||
(print " passed")
|
||||
|
||||
(print "\nAll core tests passed!")
|
||||
73
test/evaluator-test.janet
Normal file
73
test/evaluator-test.janet
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
(use ../src/jolt/evaluator)
|
||||
(use ../src/jolt/types)
|
||||
(use ../src/jolt/reader)
|
||||
|
||||
# Helper: create a Jolt symbol
|
||||
(defn sym [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})))
|
||||
|
||||
# Helper: parse and eval
|
||||
(defn eval-str [s]
|
||||
(let [ctx (make-ctx)
|
||||
form (parse-string s)]
|
||||
(eval-form ctx @{} form)))
|
||||
|
||||
(print "1: literals...")
|
||||
(assert (= 42 (eval-str "42")) "integer")
|
||||
(assert (= "hello" (eval-str "\"hello\"")) "string")
|
||||
(assert (= true (eval-str "true")) "true")
|
||||
(assert (= false (eval-str "false")) "false")
|
||||
(assert (= nil (eval-str "nil")) "nil")
|
||||
(print " passed")
|
||||
|
||||
(print "2: quote...")
|
||||
(assert (deep= (sym "x") (eval-str "'x")) "quote returns symbol")
|
||||
(assert (deep= @[1 2 3] (eval-str "'(1 2 3)")) "quote list")
|
||||
(print " passed")
|
||||
|
||||
(print "3: do...")
|
||||
(assert (= 2 (eval-str "(do 1 2)")) "do returns last")
|
||||
(print " passed")
|
||||
|
||||
(print "4: if...")
|
||||
(assert (= 1 (eval-str "(if true 1 2)")) "if true")
|
||||
(assert (= 2 (eval-str "(if false 1 2)")) "if false")
|
||||
(assert (= :b (eval-str "(if nil :a :b)")) "if nil = false")
|
||||
(assert (= nil (eval-str "(if false 1)")) "if with no else")
|
||||
(print " passed")
|
||||
|
||||
(print "5: def...")
|
||||
(assert (= 42 (eval-str "(do (def x 42) x)")) "def in do")
|
||||
(print " passed")
|
||||
|
||||
(print "6: fn*...")
|
||||
(let [f (eval-str "(fn* [x] (inc x))")]
|
||||
(assert (function? f) "fn* returns function")
|
||||
(assert (= 42 (f 41)) "fn* fn works"))
|
||||
# nested function
|
||||
(let [f (eval-str "(fn* [x] (inc (inc x)))")]
|
||||
(assert (= 43 (f 41)) "nested inc"))
|
||||
(print " passed")
|
||||
|
||||
(print "7: let*...")
|
||||
(assert (= 2 (eval-str "(let* [x 1 y 2] y)")) "let* binds")
|
||||
(assert (= 3 (eval-str "(let* [x 1] (inc (inc x)))")) "let* with expr")
|
||||
(print " passed")
|
||||
|
||||
(print "8: loop*/recur...")
|
||||
(assert (= 5 (eval-str "(loop* [x 0] (if (< x 5) (recur (inc x)) x))"))
|
||||
"loop counts up")
|
||||
(assert (= 10 (eval-str "(loop* [i 0 acc 0] (if (< i 5) (recur (inc i) (+ acc i)) acc))"))
|
||||
"loop with multiple bindings")
|
||||
(print " passed")
|
||||
|
||||
(print "9: recur in fn*...")
|
||||
(let [countdown (eval-str "(fn* [n] (if (< n 1) 0 (recur (dec n))))")]
|
||||
(assert (= 0 (countdown 5)) "recur in fn"))
|
||||
(print " passed")
|
||||
|
||||
(print "\nAll evaluator tests passed!")
|
||||
129
test/macro-test.janet
Normal file
129
test/macro-test.janet
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
(use ../src/jolt/reader)
|
||||
(use ../src/jolt/types)
|
||||
(use ../src/jolt/evaluator)
|
||||
|
||||
# Helper: create a Jolt symbol
|
||||
(defn sym [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})))
|
||||
|
||||
# Helper: parse and eval
|
||||
(defn eval-str [s]
|
||||
(let [ctx (make-ctx)
|
||||
form (parse-string s)]
|
||||
(eval-form ctx @{} form)))
|
||||
|
||||
# ============================================================
|
||||
# 1. syntax-quote — literals pass through
|
||||
# ============================================================
|
||||
(print "1: syntax-quote literals...")
|
||||
(assert (= 42 (eval-str "`42")) "syntax-quote number")
|
||||
(assert (= "hello" (eval-str "`\"hello\"")) "syntax-quote string")
|
||||
(assert (= :foo (eval-str "`:foo")) "syntax-quote keyword")
|
||||
(assert (= nil (eval-str "`nil")) "syntax-quote nil")
|
||||
(assert (= true (eval-str "`true")) "syntax-quote true")
|
||||
(assert (= false (eval-str "`false")) "syntax-quote false")
|
||||
(print " passed")
|
||||
|
||||
# ============================================================
|
||||
# 2. syntax-quote — qualify symbols
|
||||
# ============================================================
|
||||
(print "2: syntax-quote qualifies symbols...")
|
||||
# In the 'user namespace, `x → user/x
|
||||
(let [form (eval-str "`x")]
|
||||
(assert (deep= {:jolt/type :symbol :ns "user" :name "x"} form)
|
||||
"qualifies bare symbol to current ns"))
|
||||
|
||||
# Already qualified symbols stay as-is
|
||||
(let [form (eval-str "`foo/bar")]
|
||||
(assert (deep= {:jolt/type :symbol :ns "foo" :name "bar"} form)
|
||||
"qualified symbol unchanged"))
|
||||
(print " passed")
|
||||
|
||||
# ============================================================
|
||||
# 3. syntax-quote — lists: qualify symbols, literal items as-is
|
||||
# ============================================================
|
||||
(print "3: syntax-quote lists...")
|
||||
# `(+ 1 2) → (user/+ 1 2) — but 1 and 2 are numbers, stay as-is
|
||||
(let [form (eval-str "`(+ 1 2)")]
|
||||
(assert (array? form) "syntax-quote list is array")
|
||||
(assert (= 3 (length form)) "has 3 elements")
|
||||
(assert (deep= {:jolt/type :symbol :ns "user" :name "+"} (in form 0))
|
||||
"operator qualified")
|
||||
(assert (= 1 (in form 1)) "number stays")
|
||||
(assert (= 2 (in form 2)) "number stays"))
|
||||
(print " passed")
|
||||
|
||||
# ============================================================
|
||||
# 4. unquote inside syntax-quote
|
||||
# ============================================================
|
||||
(print "4: unquote...")
|
||||
# `~x inside (let* [x 10] ...) → 10
|
||||
(let [form (eval-str "(let* [x 10] `~x)")]
|
||||
(assert (= 10 form) "unquote evaluates"))
|
||||
# `(~x) produces a list containing x
|
||||
(let [form2 (eval-str "(let* [x 10] `(~x))")]
|
||||
(assert (deep= @[10] form2) "unquote in list"))
|
||||
|
||||
# `(+ ~x ~y) with x=1 y=2 → (+ 1 2)
|
||||
(let [form (eval-str "(let* [x 1 y 2] `(+ ~x ~y))")]
|
||||
(assert (array? form) "result is list")
|
||||
(assert (= 1 (in form 1)) "first unquoted value")
|
||||
(assert (= 2 (in form 2)) "second unquoted value"))
|
||||
(print " passed")
|
||||
|
||||
# ============================================================
|
||||
# 5. unquote-splicing inside syntax-quote
|
||||
# ============================================================
|
||||
(print "5: unquote-splicing...")
|
||||
# `[1 2 ~@xs] with xs = (3 4) → [1 2 3 4]
|
||||
(let [form (eval-str "(let* [xs '(3 4)] `[1 2 ~@xs])")]
|
||||
(assert (tuple? form) "result is vector")
|
||||
(assert (= 4 (length form)) "spliced items merged")
|
||||
(assert (deep= [1 2 3 4] form) "correct items"))
|
||||
|
||||
# `(1 ~@xs) with xs = (2 3) → (1 2 3)
|
||||
(let [form (eval-str "(let* [xs '(2 3)] `(1 ~@xs))")]
|
||||
(assert (array? form) "result is list")
|
||||
(assert (= 3 (length form)) "spliced into list")
|
||||
(assert (deep= @[1 2 3] form) "correct items"))
|
||||
(print " passed")
|
||||
|
||||
# ============================================================
|
||||
# 6. Macro function application
|
||||
# ============================================================
|
||||
(print "6: macro application...")
|
||||
|
||||
# Define a simple macro: (my-when test body) → (if test body nil)
|
||||
(def ctx (make-ctx))
|
||||
(def macro-fn-form (parse-string "(fn* [test body] (if test body nil))"))
|
||||
(def macro-fn (eval-form ctx @{} macro-fn-form))
|
||||
# intern it in user namespace with string key
|
||||
(let [ns (ctx-find-ns ctx "user")]
|
||||
(ns-intern ns "my-when" macro-fn)
|
||||
(put (ns-find ns "my-when") :macro true))
|
||||
|
||||
# (my-when true 42) should expand to (if true 42 nil), evaluating to 42
|
||||
(let [form (parse-string "(my-when true 42)")]
|
||||
(assert (= 42 (eval-form ctx @{} form)) "macro application returns correct value"))
|
||||
|
||||
# (my-when false 99) should expand to (if false 99 nil), evaluating to nil
|
||||
(let [form (parse-string "(my-when false 99)")]
|
||||
(assert (= nil (eval-form ctx @{} form)) "macro returns nil when false"))
|
||||
(print " passed")
|
||||
|
||||
# ============================================================
|
||||
# 7. Nested syntax-quote
|
||||
# ============================================================
|
||||
(print "7: nested syntax-quote...")
|
||||
# ``x → (syntax-quote user/x)
|
||||
(let [form (eval-str "``x")]
|
||||
(assert (array? form) "nested syntax-quote produces list")
|
||||
(assert (deep= {:jolt/type :symbol :ns nil :name "syntax-quote"} (in form 0))
|
||||
"outer is syntax-quote"))
|
||||
(print " passed")
|
||||
|
||||
(print "\nAll macro tests passed!")
|
||||
92
test/namespace-test.janet
Normal file
92
test/namespace-test.janet
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
(use ../src/jolt/reader)
|
||||
(use ../src/jolt/types)
|
||||
(use ../src/jolt/evaluator)
|
||||
|
||||
# Helper: parse and eval in a fresh ctx
|
||||
(defn eval-str [s]
|
||||
(let [ctx (make-ctx)
|
||||
form (parse-string s)]
|
||||
(eval-form ctx @{} form)))
|
||||
|
||||
(print "1: in-ns...")
|
||||
(let [ctx (make-ctx)]
|
||||
(def form (parse-string "(in-ns my.app)"))
|
||||
(eval-form ctx @{} form)
|
||||
(assert (= "my.app" (ctx-current-ns ctx)) "in-ns switches namespace"))
|
||||
(print " passed")
|
||||
|
||||
(print "2: def in different namespace...")
|
||||
(let [ctx (make-ctx)]
|
||||
(eval-form ctx @{} (parse-string "(in-ns my.app)"))
|
||||
(eval-form ctx @{} (parse-string "(def x 42)"))
|
||||
(let [ns (ctx-find-ns ctx "my.app")
|
||||
v (ns-find ns "x")]
|
||||
(assert (= 42 (var-get v)) "def works in new namespace")))
|
||||
(print " passed")
|
||||
|
||||
(print "3: ns form...")
|
||||
(let [ctx (make-ctx)]
|
||||
(eval-form ctx @{} (parse-string "(ns my.lib)"))
|
||||
(assert (= "my.lib" (ctx-current-ns ctx)) "ns sets current namespace"))
|
||||
(print " passed")
|
||||
|
||||
(print "4: ns with require...")
|
||||
(let [ctx (make-ctx)]
|
||||
# Set up a namespace with some vars
|
||||
(let [other-ns (ctx-find-ns ctx "other.lib")]
|
||||
(ns-intern other-ns "f" (fn [x] (inc x))))
|
||||
# Now ns with require
|
||||
(eval-form ctx @{} (parse-string "(ns my.app (:require [other.lib :as o]))"))
|
||||
# current-ns should be my.app
|
||||
(assert (= "my.app" (ctx-current-ns ctx)) "ns with require sets current namespace")
|
||||
# Alias should be registered
|
||||
(let [ns (ctx-find-ns ctx "my.app")
|
||||
aliased (ns-import-lookup ns "o")]
|
||||
(assert (= "other.lib" aliased) "alias o -> other.lib registered")))
|
||||
(print " passed")
|
||||
|
||||
(print "5: require form (standalone)...")
|
||||
(let [ctx (make-ctx)]
|
||||
(eval-form ctx @{} (parse-string "(require '[other.lib :as o])"))
|
||||
(let [ns (ctx-find-ns ctx "user")
|
||||
aliased (ns-import-lookup ns "o")]
|
||||
(assert (= "other.lib" aliased) "standalone require registers alias")))
|
||||
(print " passed")
|
||||
|
||||
(print "6: qualified symbol via alias...")
|
||||
(let [ctx (make-ctx)]
|
||||
# Set up target ns
|
||||
(let [target (ctx-find-ns ctx "other.lib")]
|
||||
(ns-intern target "f" (fn [x] (inc x))))
|
||||
# Register alias
|
||||
(let [ns (ctx-find-ns ctx "user")]
|
||||
(ns-import ns "o" "other.lib"))
|
||||
# Resolve o/f and call it
|
||||
(let [form (parse-string "(o/f 41)")
|
||||
result (eval-form ctx @{} form)]
|
||||
(assert (= 42 result) "qualified call via alias works")))
|
||||
(print " passed")
|
||||
|
||||
(print "7: require then use alias...")
|
||||
(let [ctx (make-ctx)]
|
||||
# Set up target ns
|
||||
(let [target (ctx-find-ns ctx "math.lib")]
|
||||
(ns-intern target "add" (fn [a b] (+ a b))))
|
||||
# require + use
|
||||
(eval-form ctx @{} (parse-string "(require '[math.lib :as m])"))
|
||||
(let [result (eval-form ctx @{} (parse-string "(m/add 1 2)"))]
|
||||
(assert (= 3 result) "require + alias + call chain works")))
|
||||
(print " passed")
|
||||
|
||||
(print "8: ns form requires multiple...")
|
||||
(let [ctx (make-ctx)]
|
||||
(let [ns1 (ctx-find-ns ctx "a.lib")]
|
||||
(ns-intern ns1 "f" (fn [x] (inc x))))
|
||||
(let [ns2 (ctx-find-ns ctx "b.lib")]
|
||||
(ns-intern ns2 "g" (fn [x] (dec x))))
|
||||
(eval-form ctx @{} (parse-string "(ns user (:require [a.lib :as a] [b.lib :as b]))"))
|
||||
(assert (= 43 (eval-form ctx @{} (parse-string "(a/f 42)"))) "alias a works")
|
||||
(assert (= 41 (eval-form ctx @{} (parse-string "(b/g 42)"))) "alias b works"))
|
||||
(print " passed")
|
||||
|
||||
(print "\nAll namespace tests passed!")
|
||||
132
test/reader-test.janet
Normal file
132
test/reader-test.janet
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
(use ../src/jolt/reader)
|
||||
|
||||
# Helper: create a symbol
|
||||
(defn sym [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})))
|
||||
|
||||
# Symbols
|
||||
(assert (deep= (sym "foo") (parse-string "foo"))
|
||||
"bare symbol")
|
||||
(assert (deep= (sym "foo/bar") (parse-string "foo/bar"))
|
||||
"namespaced symbol")
|
||||
(assert (deep= (sym "+") (parse-string "+"))
|
||||
"operator symbol")
|
||||
(assert (deep= (sym "->foo") (parse-string "->foo"))
|
||||
"arrow symbol")
|
||||
|
||||
# Keywords
|
||||
(assert (= :foo (parse-string ":foo"))
|
||||
"bare keyword")
|
||||
(assert (= :foo/bar (parse-string "::foo/bar"))
|
||||
"auto-resolved keyword")
|
||||
(assert (= :foo/bar (parse-string ":foo/bar"))
|
||||
"namespaced keyword")
|
||||
|
||||
# Numbers
|
||||
(assert (= 1 (parse-string "1"))
|
||||
"integer")
|
||||
(assert (= -42 (parse-string "-42"))
|
||||
"negative integer")
|
||||
(assert (= 3.14 (parse-string "3.14"))
|
||||
"float")
|
||||
|
||||
# Strings
|
||||
(assert (= "hello" (parse-string "\"hello\""))
|
||||
"simple string")
|
||||
|
||||
# Nil, booleans
|
||||
(assert (= nil (parse-string "nil"))
|
||||
"nil")
|
||||
(assert (= true (parse-string "true"))
|
||||
"true")
|
||||
(assert (= false (parse-string "false"))
|
||||
"false")
|
||||
|
||||
# Lists → Janet arrays (to distinguish from vectors)
|
||||
(assert (array? (parse-string "(1 2 3)"))
|
||||
"list produces array")
|
||||
(assert (deep= @[1 2 3] (parse-string "(1 2 3)"))
|
||||
"simple list")
|
||||
|
||||
# Vectors → Janet tuples
|
||||
(assert (tuple? (parse-string "[1 2 3]"))
|
||||
"vector produces tuple")
|
||||
(assert (deep= [1 2 3] (parse-string "[1 2 3]"))
|
||||
"simple vector")
|
||||
|
||||
# Maps → Janet structs
|
||||
(let [m (parse-string "{:a 1 :b 2}")]
|
||||
(assert (struct? m) "map is struct")
|
||||
(assert (= 1 (m :a)) "map key lookup"))
|
||||
|
||||
# Sets → tagged with :jolt/set
|
||||
(let [form (parse-string "#{1 2 3}")]
|
||||
(assert (struct? form) "set is struct")
|
||||
(assert (= :jolt/set (form :jolt/type)) "set type tag"))
|
||||
|
||||
# Quote and shorthand
|
||||
(assert (deep= @[(sym "quote") (sym "x")] (parse-string "'x"))
|
||||
"quote shorthand")
|
||||
(assert (deep= @[(sym "syntax-quote") (sym "x")] (parse-string "`x"))
|
||||
"syntax-quote")
|
||||
(assert (deep= @[(sym "unquote") (sym "x")] (parse-string "~x"))
|
||||
"unquote")
|
||||
(assert (deep= @[(sym "unquote-splicing") (sym "x")] (parse-string "~@x"))
|
||||
"unquote-splicing")
|
||||
(assert (deep= @[(sym "deref") (sym "x")] (parse-string "@x"))
|
||||
"deref shorthand")
|
||||
|
||||
# Metadata
|
||||
(let [form (parse-string "^:meta x")]
|
||||
(assert (array? form) "metadata is array")
|
||||
(assert (deep= @[(sym "with-meta") (sym "x") :meta] form)
|
||||
"metadata form"))
|
||||
|
||||
# Comments (skip to end of line)
|
||||
(assert (= 42 (parse-string "; comment\n42"))
|
||||
"comment then form")
|
||||
|
||||
# Discard #_
|
||||
(assert (= 42 (parse-string "#_ (ignored 1 2) 42"))
|
||||
"discard skips next form")
|
||||
|
||||
# Anonymous function #()
|
||||
(let [form (parse-string "#(+ %1 %2)")]
|
||||
(assert (array? form) "fn form is array")
|
||||
(assert (deep= (sym "fn*") (in form 0)) "first element is fn*"))
|
||||
|
||||
# Nested forms
|
||||
(let [form (parse-string "(+ 1 (* 2 3))")]
|
||||
(assert (array? form) "outer list is array")
|
||||
(assert (deep= (sym "+") (in form 0)) "+ is first")
|
||||
(assert (= 1 (in form 1)) "1 is second")
|
||||
(assert (array? (in form 2)) "nested list is array"))
|
||||
|
||||
# Multiple forms: parse-next
|
||||
(let [[form1 rest-str] (parse-next "(1 2) [3 4]")]
|
||||
(assert (deep= @[1 2] form1) "first form is list")
|
||||
(let [[form2 _] (parse-next rest-str)]
|
||||
(assert (deep= [3 4] form2) "second form is vector")))
|
||||
|
||||
# Reader conditional (basic #? support)
|
||||
(let [form (parse-string "#?(:clj 1 :cljs 2)")]
|
||||
(assert (struct? form) "reader conditional")
|
||||
(assert (= :jolt/reader-conditional (form :jolt/type)) "correct type"))
|
||||
|
||||
# Characters
|
||||
(let [form (parse-string "\\newline")]
|
||||
(assert (struct? form) "char is struct")
|
||||
(assert (= :char (form :jolt/type)) "char type")
|
||||
(assert (= "newline" (form :name)) "char name"))
|
||||
|
||||
(let [form (parse-string "\\a")]
|
||||
(assert (= "a" (form :name)) "simple char name"))
|
||||
|
||||
(print "All reader tests passed!")
|
||||
118
test/types-test.janet
Normal file
118
test/types-test.janet
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
(use ../src/jolt/types)
|
||||
|
||||
# ============================================================
|
||||
# Var tests
|
||||
# ============================================================
|
||||
|
||||
# make-var
|
||||
(let [v (make-var 'x 42)]
|
||||
(assert (var? v) "var? returns true")
|
||||
(assert (= 42 (var-get v)) "var-get returns root binding")
|
||||
(assert (deep= {:name 'x} (var-meta v)) "var-meta returns metadata")
|
||||
(assert (deep= 'x (var-name v)) "var-name returns name symbol"))
|
||||
|
||||
# var without init value
|
||||
(let [v (make-var 'y)]
|
||||
(assert (var? v) "unbound var is still a var"))
|
||||
|
||||
# dynamic var
|
||||
(let [v (make-var '*dyn* 1 {:dynamic true})]
|
||||
(assert (var-dynamic? v) "var-dynamic? true")
|
||||
(assert (not (var-macro? v)) "var-macro? false for dynamic var"))
|
||||
|
||||
# macro var
|
||||
(let [v (make-var 'when nil {:macro true})]
|
||||
(assert (var-macro? v) "var-macro? true"))
|
||||
|
||||
# var-set — set root binding
|
||||
(let [v (make-var 'x 1)]
|
||||
(var-set v 99)
|
||||
(assert (= 99 (var-get v)) "var-set changes root binding"))
|
||||
|
||||
# alter-var-root
|
||||
(let [v (make-var 'c 0)]
|
||||
(alter-var-root v inc)
|
||||
(assert (= 1 (var-get v)) "alter-var-root applies fn"))
|
||||
|
||||
# with-meta — returns new var with updated meta
|
||||
(let [v (make-var 'x 42)
|
||||
v2 (with-meta v {:private true})]
|
||||
(assert (deep= {:name 'x :private true} (var-meta v2)) "with-meta merges meta")
|
||||
(assert (= 42 (var-get v2)) "with-meta preserves root binding"))
|
||||
|
||||
# var with namespace
|
||||
(let [ns (make-ns 'my.ns)
|
||||
v (make-var 'my.ns/x 1 {:ns ns})]
|
||||
(assert (= ns (var-ns v)) "var-ns returns namespace"))
|
||||
|
||||
# ============================================================
|
||||
# Namespace tests
|
||||
# ============================================================
|
||||
|
||||
(let [ns (make-ns 'foo.bar)]
|
||||
(assert (ns? ns) "ns? returns true")
|
||||
(assert (deep= 'foo.bar (ns-name ns)) "ns-name returns name symbol")
|
||||
(assert (table? (ns-map ns)) "ns-map returns table")
|
||||
(assert (= 0 (length (ns-map ns))) "empty namespace has no mappings"))
|
||||
|
||||
# ns-intern
|
||||
(let [ns (make-ns 'test.ns)
|
||||
v (ns-intern ns 'x 42)]
|
||||
(assert (var? v) "ns-intern returns a var")
|
||||
(assert (= 42 (var-get v)) "ns-intern sets root binding")
|
||||
# check ns-find returns the same var (by reference, not deep=)
|
||||
(assert (= v (ns-find ns 'x)) "ns-find returns interned var"))
|
||||
|
||||
# ns-intern without value
|
||||
(let [ns (make-ns 'test.ns)
|
||||
v (ns-intern ns 'y)]
|
||||
(assert (var? v) "ns-intern without value creates unbound var"))
|
||||
|
||||
# ns-unmap
|
||||
(let [ns (make-ns 'test.ns)
|
||||
_ (ns-intern ns 'x 1)
|
||||
_ (ns-unmap ns 'x)]
|
||||
(assert (nil? (ns-find ns 'x)) "ns-unmap removes mapping"))
|
||||
|
||||
# ns-resolve — own ns
|
||||
(let [ns (make-ns 'test.ns)
|
||||
v (ns-intern ns 'x 10)]
|
||||
(assert (= v (ns-resolve ns 'x)) "ns-resolve finds var in own ns"))
|
||||
|
||||
# ns-import
|
||||
(let [ns (make-ns 'test.ns)]
|
||||
(ns-import ns 'Date 'java.util.Date)
|
||||
(assert (= 'java.util.Date (ns-import-lookup ns 'Date)) "ns-import-lookup returns import"))
|
||||
|
||||
# ============================================================
|
||||
# Context tests
|
||||
# ============================================================
|
||||
|
||||
(let [ctx (make-ctx)]
|
||||
(assert (ctx? ctx) "ctx? returns true"))
|
||||
|
||||
# ctx with initial namespaces
|
||||
(let [ctx (make-ctx {:namespaces {"user" {"x" 1 "y" 2}}})]
|
||||
(let [ns (ctx-find-ns ctx "user")]
|
||||
(assert (ns? ns) "ctx-find-ns returns namespace for user")
|
||||
(let [v (ns-find ns "x")]
|
||||
(assert (var? v) "user/x is a var")
|
||||
(assert (= 1 (var-get v)) "user/x has correct value"))))
|
||||
|
||||
# ctx-find-ns creates ns if not present
|
||||
(let [ctx (make-ctx)
|
||||
ns (ctx-find-ns ctx "foo")]
|
||||
(assert (ns? ns) "ctx-find-ns creates namespace on demand"))
|
||||
|
||||
# ============================================================
|
||||
# Dynamic binding support (thread-local bindings table)
|
||||
# ============================================================
|
||||
|
||||
# push-thread-bindings / pop-thread-bindings
|
||||
(let [v (make-var '*dyn* 0 {:dynamic true})]
|
||||
(push-thread-bindings @{v 100})
|
||||
(assert (= 100 (var-get v)) "push-thread-bindings sets binding")
|
||||
(pop-thread-bindings)
|
||||
(assert (= 0 (var-get v)) "pop-thread-bindings restores root"))
|
||||
|
||||
(print "All types tests passed!")
|
||||
Loading…
Add table
Add a link
Reference in a new issue