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:
Yogthos 2026-06-01 16:48:56 -04:00
commit cdcf569506
16 changed files with 2696 additions and 0 deletions

38
test/api-test.janet Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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!")