test: restructure into unit / integration / spec layers + shared harness

Reorganize the flat 49-file test/ into three layers (jpm test recurses, so all
are still discovered):
- test/unit/        white-box component tests (reader, evaluator, types,
                    persistent-map, lazy-seq, macro, interop, compiler)
- test/integration/ cross-cutting + regression batteries (conformance, jank,
                    sci-bootstrap/runtime, features, systematic-coverage, api,
                    core, namespaces, ported clojure suites) and
   .../ports/       ported clojure/cljs test batches pending consolidation
- test/spec/        the behavioral contract (built out in following commits)
- test/support/harness.janet  shared defspec table runner (cases compared via
                    Jolt's own =, with a :throws sentinel) + expect= helpers

Files moved with git mv (history preserved) and import paths fixed for depth.
jpm test green. README Test section updated.

Next: build out test/spec/ to cover the public API area-by-area, mining the
integration batteries and filling gaps.
This commit is contained in:
Yogthos 2026-06-05 00:00:16 -04:00
parent 1898308926
commit 16428179fa
52 changed files with 185 additions and 86 deletions

View file

@ -0,0 +1,335 @@
# Jolt Compiler Tests — Phase 2
# Tests for source-to-source Clojure→Janet compilation.
# Core ops: const, do, if, def, fn, let, invoke
# Phase 2 adds: symbol classification with binding awareness
(use ../../src/jolt/compiler)
(use ../../src/jolt/reader)
(defn compile-str [s]
(let [form (parse-string s)]
(compile-form form)))
# ============================================================
# 1. Literals (const)
# ============================================================
(print "1: literal constants...")
(assert (= "42" (compile-str "42")) "integer")
(assert (= "nil" (compile-str "nil")) "nil")
(assert (= "true" (compile-str "true")) "true")
(assert (= "false" (compile-str "false")) "false")
(assert (= "\"hello\"" (compile-str "\"hello\"")) "string")
(assert (= ":foo" (compile-str ":foo")) "keyword")
(print " passed")
# ============================================================
# 2. do
# ============================================================
(print "2: do...")
(assert (= "(do 1 2)" (compile-str "(do 1 2)")) "do two exprs")
(assert (= "(do 42)" (compile-str "(do 42)")) "do single expr")
(assert (= "(do (core-inc 1) (core-inc 2))" (compile-str "(do (inc 1) (inc 2))")) "do with fn calls")
(print " passed")
# ============================================================
# 3. if
# ============================================================
(print "3: if...")
(assert (= "(if true 1 2)" (compile-str "(if true 1 2)")) "if three-arg")
(assert (= "(if false 1 nil)" (compile-str "(if false 1)")) "if two-arg")
(print " passed")
# ============================================================
# 4. def
# ============================================================
(print "4: def...")
(assert (= "(def x 42)" (compile-str "(def x 42)")) "def constant")
(assert (= "(def f (fn [x] (core-inc x)))" (compile-str "(def f (fn* [x] (inc x)))")) "def with fn")
(print " passed")
# ============================================================
# 5. fn
# ============================================================
(print "5: fn...")
(assert (= "(fn [x] (core-inc x))" (compile-str "(fn* [x] (inc x))")) "fn single arity")
(assert (= "(fn [] 42)" (compile-str "(fn* [] 42)")) "fn no args")
(assert (= "(fn [x] (do (core-print x) (core-inc x)))"
(compile-str "(fn* [x] (print x) (inc x))")) "fn multi-expr body")
(print " passed")
# ============================================================
# 6. let
# ============================================================
(print "6: let...")
(assert (= "(let [x 1] (core-inc x))" (compile-str "(let* [x 1] (inc x))")) "let single binding")
(assert (= "(let [x 1 y 2] (core-+ x y))" (compile-str "(let* [x 1 y 2] (+ x y))")) "let two bindings")
(assert (= "(let [x (core-inc 1)] (core-inc x))" (compile-str "(let* [x (inc 1)] (inc x))")) "let with fn in binding")
(print " passed")
# ============================================================
# 7. invoke (function calls)
# ============================================================
(print "7: invoke...")
(assert (= "(core-inc 1)" (compile-str "(inc 1)")) "inc call")
(assert (= "(core-+ 1 2)" (compile-str "(+ 1 2)")) "+ call")
(assert (= "(core-+ (core-inc 1) 2)" (compile-str "(+ (inc 1) 2)")) "nested calls")
(assert (= "(core-map core-inc (core-vec 1 2 3))"
(compile-str "(map inc (vec 1 2 3))")) "multi-arg call")
(print " passed")
# ============================================================
# 8. Local symbol classification (Phase 2)
# ============================================================
(print "8: local classification...")
# Shadowing: local inc should NOT be rewritten to core-inc
(assert (= "(let [inc 5] (inc inc))"
(compile-str "(let* [inc 5] (inc inc))")) "local shadows core fn")
# fn params are locals, not core symbols
(assert (= "(fn [map] (core-vec map))"
(compile-str "(fn* [map] (vec map))")) "fn param shadows core map")
# nested let with shadowing
(assert (= "(let [x 1] (let [inc x] (inc x)))"
(compile-str "(let* [x 1] (let* [inc x] (inc x)))")) "nested let local")
(print " passed")
(print "\nAll compiler Phase 2 tests passed!")
# ============================================================
# 9. Compile-and-eval round-trip (Phase 3)
# ============================================================
(print "9: compile-and-eval...")
(use ../../src/jolt/core) # need core fns in scope for eval
(defn compile-eval-str [s]
(let [form (parse-string s)]
(compile-and-eval form nil)))
(assert (= 42 (compile-eval-str "42")) "eval literal")
(assert (= 2 (compile-eval-str "(inc 1)")) "eval inc")
(assert (= 3 (compile-eval-str "(+ 1 2)")) "eval +")
(assert (= 6 (compile-eval-str "(+ (inc 1) (inc 3))")) "eval nested")
(assert (= 2 (compile-eval-str "(do 1 2)")) "eval do")
(assert (= 1 (compile-eval-str "(if true 1 2)")) "eval if true")
(assert (= 2 (compile-eval-str "(if false 1 2)")) "eval if false")
(assert (= 2 (compile-eval-str "(let* [x 1] (inc x))")) "eval let")
(let [f (compile-eval-str "(fn* [x] (inc x))")]
(assert (function? f) "eval fn returns fn")
(assert (= 6 (f 5)) "eval fn works"))
(print " passed")
# ============================================================
# 10. Compile flag in context (Phase 3)
# ============================================================
(print "10: compile flag...")
(use ../../src/jolt/api)
# Without compile flag
(let [ctx (init)]
(assert (= 2 (eval-string ctx "(inc 1)")) "no-compile flag: inc works"))
# With compile flag: pure expressions use compile-and-eval
(let [ctx (init {:compile? true})]
(assert (= 2 (eval-string ctx "(inc 1)")) "compile flag: inc works")
(assert (= 3 (eval-string ctx "(+ 1 2)")) "compile flag: + works")
(assert (= 6 (eval-string ctx "(+ (inc 1) (inc 3))")) "compile flag: nested works"))
# With compile flag: stateful forms fall back to interpreter
(let [ctx (init {:compile? true})]
(eval-string ctx "(def foo 99)")
(assert (= 99 (eval-string ctx "foo")) "compile flag: def works"))
(print " passed")
(print "\nAll compiler Phase 3 tests passed!")
# ============================================================
# 11. Macro expansion (Phase 4)
# ============================================================
(print "11: macro expansion...")
(use ../../src/jolt/api)
(let [ctx (init {:compile? true})]
# defn expands via compiler, produces Janet def
(eval-string ctx "(defn square [n] (* n n))")
(assert (= 25 (eval-string ctx "(square 5)")) "defn via compiler")
# when macro
(assert (= 42 (eval-string ctx "(when true 42)")) "when true")
(assert (= nil (eval-string ctx "(when false 42)")) "when false")
# let macro
(assert (= 30 (eval-string ctx "(let [x 10 y 20] (+ x y))")) "let macro")
# fn macro
(assert (= 49 (eval-string ctx "((fn [x] (* x x)) 7)")) "fn macro")
# and/or
(assert (= 3 (eval-string ctx "(and 1 2 3)")) "and")
(assert (= 99 (eval-string ctx "(or nil false 99)")) "or"))
(print " passed")
(print "\nAll compiler Phase 4 tests passed!")
# ============================================================
# 12. throw, try, loop*/recur (Phase 5)
# ============================================================
(print "12: throw/try/loop...")
(use ../../src/jolt/api)
(let [ctx (init {:compile? true})]
# throw/catch via compiler
(assert (= "caught"
(eval-string ctx "(try (throw 42) (catch Exception e \"caught\"))"))
"try/catch")
# try without catch returns body
(assert (= 1 (eval-string ctx "(try 1 (catch Exception e 2))")) "try no throw")
# throw in nested context
(assert (= "ok"
(eval-string ctx "(try (do (throw 99) 1) (catch Exception e \"ok\"))"))
"throw in do")
# loop*/recur
(assert (= 3 (eval-string ctx "(loop* [x 0] (if (< x 3) (recur (inc x)) x))"))
"loop count up")
(assert (= 3
(eval-string ctx "(loop* [i 0 acc 0] (if (< i 3) (recur (inc i) (+ acc i)) acc))"))
"loop with acc"))
(print " passed")
(print "\nAll compiler Phase 5 tests passed!")
# ============================================================
# 13. defn/def integration (Phase 0 fix)
# ============================================================
(print "13: defn/def integration...")
(use ../../src/jolt/api)
(let [ctx (init {:compile? true})]
# defn produces a resolvable var
(eval-string ctx "(defn identity-fn [x] x)")
(assert (= 1 (eval-string ctx "(identity-fn 1)")) "defn works")
(let [f (eval-string ctx "identity-fn")]
(assert (function? f) "bare defn symbol returns fn"))
# def produces a resolvable var
(eval-string ctx "(def answer 42)")
(assert (= 42 (eval-string ctx "answer")) "def bare symbol")
(assert (= 43 (eval-string ctx "(inc answer)")) "def in call"))
(print " passed")
(print "\nAll compiler tests passed!")
# ============================================================
# 14. Phase 1: ns accessors + ns form extensions
# ============================================================
(print "14: ns accessors...")
(use ../../src/jolt/api)
(let [ctx (init)]
(eval-string ctx "(ns mytest.core)")
(def ns-list (eval-string ctx "(all-ns)"))
(assert (> (length ns-list) 0) "all-ns returns namespaces")
# create-ns
(eval-string ctx "(create-ns mytest.extra)")
(def all (eval-string ctx "(all-ns)"))
(assert (> (length all) 1) "create-ns adds namespace"))
(print " passed")
(print "15: ns form extensions...")
(let [ctx (init)]
# ns with :require + :refer
(eval-string ctx "(ns test.ns-ext (:require [clojure.core :refer [inc +]]))")
(assert (= 2 (eval-string ctx "(inc 1)")) "refer inc works"))
(print " passed")
(print "\nAll Phase 1 tests passed!")
# ============================================================
# 17. Phase 3: Var system completion
# ============================================================
(print "17: var system...")
(use ../../src/jolt/api)
(let [ctx (init)]
(eval-string ctx "(def x-var-test 42)")
(assert (= true (eval-string ctx "(var? (var x-var-test))")) "var?")
(eval-string ctx "(def y-var-test 99)")
(assert (= 99 (eval-string ctx "(var-get (var y-var-test))")) "var-get")
(eval-string ctx "(def z-var-test 10)")
(assert (= 20 (eval-string ctx "(do (var-set (var z-var-test) 20) (var-get (var z-var-test)))")) "var-set")
(eval-string ctx "(def a-var-test 1)")
(assert (= 2 (eval-string ctx "(do (alter-var-root (var a-var-test) inc) (var-get (var a-var-test)))")) "alter-var-root")
(eval-string ctx "(def fv-var-test :found)")
(assert (= :found (eval-string ctx "(var-get (find-var 'fv-var-test))")) "find-var")
(eval-string ctx "(intern (the-ns) 'iv-var-test 77)")
(assert (= 77 (eval-string ctx "iv-var-test")) "intern")
(eval-string ctx "(def ^:dynamic *dv* 1)")
(assert (= 99 (eval-string ctx "(binding [*dv* 99] *dv*)")) "dynamic binding"))
(print " passed")
# ============================================================
# 18. Phase 3: Var metadata
# ============================================================
(print "18: var metadata...")
(let [ctx (init)]
(eval-string ctx "(def mvar 42)")
(eval-string ctx "(alter-meta! (var mvar) assoc :doc \"the answer\")")
(assert (= "the answer" (eval-string ctx "(:doc (meta (var mvar)))")) "alter-meta!")
(eval-string ctx "(reset-meta! (var mvar) {:a 1})")
(assert (= 1 (eval-string ctx "(:a (meta (var mvar)))")) "reset-meta!"))
(print " passed")
(print "\nAll Phase 3 tests passed!")
# ============================================================
# 19. Phase 4: deftype
# ============================================================
(print "19: deftype...")
(use ../../src/jolt/api)
(let [ctx (init)]
(eval-string ctx "(deftype Point [x y])")
(assert (not (nil? (eval-string ctx "(Point. 3 4)"))) "deftype constructs")
(assert (= 3 (eval-string ctx "(. (Point. 3 4) x)")) ".x access")
(assert (= 4 (eval-string ctx "(. (Point. 3 4) y)")) ".y access")
(assert (= 10 (eval-string ctx "(let [p (Point. 3 4)] (set! (.-x p) 10) (. p x))")) "set! field")
(assert (= true (eval-string ctx "(instance? Point (Point. 1 2))")) "instance?")
(assert (= false (eval-string ctx "(instance? Point {:x 1})")) "instance? false")
(assert (= 5 (eval-string ctx "(. (->Point 5 6) x)")) "arrow factory"))
(print " passed")
# ============================================================
# 20. Phase 4: defrecord
# ============================================================
(print "20: defrecord...")
(let [ctx (init)]
(eval-string ctx "(defrecord Person [name age])")
(assert (not (nil? (eval-string ctx "(Person. \"Alice\" 30)"))) "record constructs")
(assert (= true (eval-string ctx "(map? (Person. \"Alice\" 30))")) "record is map?")
(assert (= "Alice" (eval-string ctx "(:name (Person. \"Alice\" 30))")) "keyword access")
(assert (= 30 (eval-string ctx "(get (Person. \"Alice\" 30) :age)")) "get access")
(assert (= "Alice" (eval-string ctx "(. (Person. \"Alice\" 30) name)")) ".name access")
(assert (= 30 (eval-string ctx "(. (Person. \"Alice\" 30) age)")) ".age access")
(assert (= 2 (eval-string ctx "(count (Person. \"Alice\" 30))")) "count")
(assert (= "Bob" (eval-string ctx "(:name (->Person \"Bob\" 25))")) "arrow factory"))
(print " passed")
# ============================================================
# 21. Phase 4: record equality
# ============================================================
(print "21: record equality...")
(let [ctx (init)]
(eval-string ctx "(defrecord Point3D [x y z])")
(assert (= true (eval-string ctx "(= (Point3D. 1 2 3) (Point3D. 1 2 3))")) "same values")
(assert (= false (eval-string ctx "(= (Point3D. 1 2 3) (Point3D. 4 5 6))")) "different values"))
(print " passed")
(print "\nAll Phase 4 tests passed!")

14
test/unit/eval-test.janet Normal file
View file

@ -0,0 +1,14 @@
(use ../../src/jolt/api)
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Eval Tests")
(print "1: eval literal...")
(let [ctx (init)]
(assert (= 42 (ct-eval ctx "(eval 42)")) "eval literal")
(assert (= 3 (ct-eval ctx "(eval '(+ 1 2))")) "eval quoted form")
(assert (= 3 (ct-eval ctx "(eval (eval '(+ 1 2)))")) "eval nested")
(ct-eval ctx "(eval '(def ex 99))")
(assert (= 99 (ct-eval ctx "ex")) "eval defines var"))
(print " ok")
(print "\nAll Eval tests passed!")

View file

@ -0,0 +1,151 @@
(use ../../src/jolt/evaluator)
(use ../../src/jolt/types)
(use ../../src/jolt/reader)
(use ../../src/jolt/api)
# 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 "10: throw/try/catch/finally...")
# throw + catch
(let [result (eval-str "(try (throw \"boom\") (catch Exception e \"caught\"))")]
(assert (= "caught" result) "catch catches throw"))
# try with finally — body returns, finally runs
(let [result (eval-str "(try 1 (finally 2))")]
(assert (= 1 result) "try returns body even with finally"))
# try/catch/finally — catch returns, finally runs
(let [result (eval-str "(try (throw \"err\") (catch Exception e \"handled\") (finally :cleanup))")]
(assert (= "handled" result) "catch + finally returns catch value"))
(print " passed")
(print "11: set!...")
# set! on a var
(assert (= 99 (eval-str "(do (def x 1) (set! x 99) x)")) "set! on var")
# set! re-evaluates
(assert (= 3 (eval-str "(do (def a 1) (def b 2) (set! a (+ a b)) a)")) "set! with expression")
(print " passed")
(print "12: var...")
# (var x) returns the var itself, not its value
(let [v (eval-str "(do (def x 42) (var x))")]
(assert (var? v) "(var x) returns a var")
(assert (= 42 (var-get v)) "var holds value"))
(print " passed")
(print "13: locking...")
# locking is a no-op in single-threaded Janet — just executes body
(assert (= 42 (eval-str "(locking :lock 42)")) "locking returns body result")
(print " passed")
(print "14: instance?...")
# instance? checks type
(assert (= true (eval-str "(instance? Number 42)")) "instance? Number matches number")
(assert (= false (eval-str "(instance? Number \"hello\")")) "instance? Number doesn't match string")
(print " passed")
(print "15: defmulti/defmethod...")
(let [ctx (make-ctx)]
(eval-form ctx @{} (parse-string "(defmulti my-dispatch (fn* [x] (x :type)))"))
(eval-form ctx @{} (parse-string "(defmethod my-dispatch :foo [_] :got-foo)"))
(eval-form ctx @{} (parse-string "(defmethod my-dispatch :bar [_] :got-bar)"))
(assert (= :got-foo (eval-form ctx @{} (parse-string "(my-dispatch {:type :foo})"))) "defmethod :foo dispatches")
(assert (= :got-bar (eval-form ctx @{} (parse-string "(my-dispatch {:type :bar})"))) "defmethod :bar dispatches"))
(print " passed")
(print "16: deftype...")
(let [ctx (make-ctx)
_ (eval-form ctx @{} (parse-string "(deftype Point [x y])"))
_ (eval-form ctx @{} (parse-string "(def p (Point. 10 20))"))
p-val (eval-form ctx @{} (parse-string "p"))
x-val (eval-form ctx @{} (parse-string "(p :x)"))
y-val (eval-form ctx @{} (parse-string "(p :y)"))
result [x-val y-val]]
(printf " p-val: %q" p-val)
(printf " x-val: %q, y-val: %q" x-val y-val)
(printf " result: %q" result)
(assert (deep= [10 20] result) "deftype creates tagged instances with fields"))
(print " passed")
(print "17: defmacro...")
# define a macro using defmacro special form
# init loads clojure.core so `list` is available
(let [ctx (init)
_ (eval-form ctx @{} (parse-string "(defmacro my-when [test body] (list 'if test body nil))"))
result (eval-form ctx @{} (parse-string "(my-when true 2)"))]
(assert (= 2 result) "defmacro defines callable macro"))
# verify the var is marked :macro
(let [ctx (make-ctx)
_ (eval-form ctx @{} (parse-string "(defmacro m [x] (list 'quote x))"))
v (resolve-var ctx @{} (parse-string "m"))]
(assert v "macro var exists")
(assert (v :macro) "macro var has :macro true"))
(print " passed")
(print "\nAll evaluator tests passed!")

View file

@ -0,0 +1,46 @@
(use ../../src/jolt/api)
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Janet Interop Tests")
(print "1: field access on tables...")
(let [ctx (init)]
(ct-eval ctx "(def t {:a 1 :b 2})")
(assert (= 1 (ct-eval ctx "(. t :a)")) ". field access table")
(assert (= 2 (ct-eval ctx "(. t :b)")) ". field access table 2")
(assert (= nil (ct-eval ctx "(. t :z)")) ". field access missing key"))
(print " ok")
(print "2: field access on structs...")
(let [ctx (init)]
(ct-eval ctx "(def s {:x 10 :y 20})")
(assert (= 10 (ct-eval ctx "(. s :x)")) ". field access struct")
(assert (= 20 (ct-eval ctx "(. s :y)")) ". field access struct 2"))
(print " ok")
(print "3: method calls on tables...")
(let [ctx (init)]
(ct-eval ctx "(def obj {:greet (fn [self name] (str \"Hello \" name))})")
(assert (= "Hello Alice" (ct-eval ctx "(. obj greet \"Alice\")")) ". method call on table"))
(print " ok")
(print "4: field access via .- reader sugar...")
(let [ctx (init)]
(ct-eval ctx "(def t {:x 42 :len 5})")
(assert (= 42 (ct-eval ctx "(.-x t)")) ".-x reader sugar")
(assert (= 5 (ct-eval ctx "(.-len t)")) ".-len reader sugar"))
(print " ok")
(print "5: . field access still works on deftypes...")
(let [ctx (init)]
(ct-eval ctx "(deftype Point [x y])")
(assert (= 3 (ct-eval ctx "(. (Point. 3 4) x)")) ". field access deftype"))
(print " ok")
(print "6: method call with multiple args...")
(let [ctx (init)]
(ct-eval ctx "(def calc {:add (fn [_ a b] (+ a b)) :mul (fn [_ a b] (* a b))})")
(assert (= 7 (ct-eval ctx "(. calc add 3 4)")) ". method add")
(assert (= 12 (ct-eval ctx "(. calc mul 3 4)")) ". method mul"))
(print " ok")
(print "\nAll Janet Interop tests passed!")

View file

@ -0,0 +1,56 @@
(use ../../src/jolt/api)
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "LazySeq Tests")
(print "1: lazy-seq from list...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= [1 2 3] (take 10 (lazy-seq [1 2 3])))")) "lazy-seq list")
(assert (= true (ct-eval ctx "(= 3 (count (lazy-seq [1 2 3])))")) "count lazy-seq"))
(print " ok")
(print "2: lazy-cat concatenation...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= [1 2 3 4] (take 10 (lazy-cat [1 2] [3 4])))")) "lazy-cat concat")
(assert (= true (ct-eval ctx "(= 4 (count (lazy-cat [1 2] [3 4])))")) "lazy-cat count"))
(print " ok")
(print "3: first/rest on lazy-seqs...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= 1 (first (lazy-seq [1 2 3])))")) "first lazy")
(assert (= true (ct-eval ctx "(= 2 (first (rest (lazy-seq [1 2 3]))))")) "first rest lazy"))
(print " ok")
(print "4: drop/nth on lazy-seqs...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= [3 4 5] (take 10 (drop 2 (lazy-seq [1 2 3 4 5]))))")) "drop 2 take 10")
(assert (= true (ct-eval ctx "(= 3 (nth (lazy-seq [1 2 3 4 5]) 2))")) "nth lazy"))
(print " ok")
(print "5: concat on lazy-seqs...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= 5 (count (concat (lazy-seq [1 2]) (lazy-seq [3 4 5]))))")) "concat lazy"))
(print " ok")
(print "6: reverse/sort on lazy-seqs...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= [3 2 1] (reverse (lazy-seq [1 2 3])))")) "reverse lazy")
(assert (= true (ct-eval ctx "(= [1 2 3] (sort (lazy-seq [3 1 2])))")) "sort lazy"))
(print " ok")
(print "7: distinct on lazy-seqs...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= [1 2 3] (distinct (lazy-seq [1 2 1 3 2])))")) "distinct lazy"))
(print " ok")
(print "8: fib-seq (deferred — eager core-map needs lazy upgrade)...")
(let [ctx (init)]
(print " The cell-by-cell lazy-seq architecture is correct. concat, take,")
(print " drop, nth, first, rest all work with self-referencing patterns.")
(print " core-map still eagerly realizes all lazy-seq arguments, which")
(print " loops on infinite sequences. Making core-map return a lazy")
(print " result would complete the self-referencing lazy-seq story.")
(print " When fixed: (def fib-seq (lazy-cat [0 1] (map + (rest fib-seq) fib-seq)))")
(print " → (take 10 fib-seq) = [0 1 1 2 3 5 8 13 21 34]"))
(print " ok (deferred — core-map needs lazy upgrade)")
(print "\nAll LazySeq tests passed!")

129
test/unit/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!")

View file

@ -0,0 +1,75 @@
# Phase 2: PersistentHashMap Tests
# Uses Clojure = (core-=) for PHM-aware comparison
(use ../../src/jolt/api)
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
# Helper: compare via Clojure = which handles PHM
(defn clj= [ctx a b]
(eval-string ctx (string "(= " a " " b ")")))
# ============================================================
# 1. Basic hash-map construction and access
# ============================================================
(print "1: hash-map construction...")
(let [ctx (init)]
(def m1 (ct-eval ctx "(hash-map :a 1)"))
(assert (not (nil? m1)) "hash-map returns non-nil")
(assert (= true (ct-eval ctx "(map? (hash-map :a 1))")) "map? returns true for PHM")
(assert (= true (ct-eval ctx "(= (hash-map :a 1) {:a 1})")) "PHM = struct via Clojure =")
(assert (= 0 (ct-eval ctx "(count (hash-map))")) "count empty")
(assert (= 2 (ct-eval ctx "(count (hash-map :a 1 :b 2))")) "count two")
(assert (= 1 (ct-eval ctx "(get (hash-map :a 1 :b 2) :a)")) "get present")
(assert (= nil (ct-eval ctx "(get (hash-map :a 1) :z)")) "get missing"))
(print " passed")
# ============================================================
# 2. assoc and dissoc
# ============================================================
(print "2: assoc/dissoc...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(= (assoc (hash-map :a 1) :b 2) (hash-map :a 1 :b 2))")) "assoc add")
(assert (= true (ct-eval ctx "(= (assoc (hash-map :a 1) :a 99) (hash-map :a 99))")) "assoc replace")
(assert (= true (ct-eval ctx "(= (dissoc (hash-map :a 1 :b 2) :a) (hash-map :b 2))")) "dissoc")
(assert (= true (ct-eval ctx "(contains? (hash-map :a 1) :a)")) "contains? true")
(assert (= false (ct-eval ctx "(contains? (hash-map :a 1) :z)")) "contains? false"))
(print " passed")
# ============================================================
# 3. keys, vals, merge
# ============================================================
(print "3: keys/vals/merge...")
(let [ctx (init)]
(assert (= 2 (ct-eval ctx "(count (keys (hash-map :a 1 :b 2)))")) "keys count")
(assert (= 2 (ct-eval ctx "(count (vals (hash-map :a 1 :b 2)))")) "vals count")
(assert (= true (ct-eval ctx "(= (merge (hash-map :a 1) (hash-map :b 2)) (hash-map :a 1 :b 2))")) "merge"))
(print " passed")
# ============================================================
# 4. Empty and seq
# ============================================================
(print "4: empty? and seq...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(empty? (hash-map))")) "empty? true")
(assert (= false (ct-eval ctx "(empty? (hash-map :a 1))")) "empty? false")
(assert (= 1 (ct-eval ctx "(count (seq (hash-map :a 1)))")) "seq count"))
(print " passed")
# ============================================================
# 5. Larger maps
# ============================================================
(print "5: larger maps...")
(let [ctx (init)]
(eval-string ctx "
(def big-map
(reduce (fn [m i] (assoc m (keyword (str \"k\" i)) i))
(hash-map)
(range 100)))")
(assert (= 100 (ct-eval ctx "(count big-map)")) "count 100")
(assert (= 42 (ct-eval ctx "(get big-map :k42)")) "get k42"))
(print " passed")
(print "\nAll PersistentHashMap tests passed!")

140
test/unit/reader-test.janet Normal file
View file

@ -0,0 +1,140 @@
(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 — resolves :clj branch at read time
(assert (= 1 (parse-string "#?(:clj 1 :cljs 2)"))
"#?(:clj) picks :clj branch")
(assert (= nil (parse-string "#?(:cljs 999)"))
"#?(:cljs) returns nil on CLJ")
(assert (= 42 (parse-string "#?(:clj 42)"))
"#?(:clj) with single branch")
(assert (deep= (sym "clj-only") (parse-string "#?(:clj clj-only :cljs cljs-only)"))
"#?(:clj) picks :clj symbol")
# Nested inside a list — :clj branch is evaluated at read time
(assert (deep= @[(sym "+") 1 3] (parse-string "(+ 1 #?(:clj 3 :cljs 4))"))
"#? inside list picks :clj")
# Characters — the reader now produces char values {:jolt/type :jolt/char :ch N}
(let [form (parse-string "\\newline")]
(assert (struct? form) "char is struct")
(assert (= :jolt/char (form :jolt/type)) "char type")
(assert (= 10 (form :ch)) "newline codepoint"))
(let [form (parse-string "\\a")]
(assert (= 97 (form :ch)) "simple char codepoint"))
(print "All reader tests passed!")

118
test/unit/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!")