#![cfg(feature = "no-gc")]
use std::sync::Arc;
use cljrs_reader::Parser;
use cljrs_runtime::env::env::{Env, GlobalEnv};
use cljrs_value::Value;
fn make_env() -> (Arc<GlobalEnv>, Env) {
let globals = cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::TreeWalk)
.eager_clojure_test(true)
.build()
.expect("runtime")
.into_globals();
let env = Env::new(globals.clone(), "user");
(globals, env)
}
fn eval_src(src: &str, env: &mut Env) -> Value {
let mut parser = Parser::new(src.to_string(), "<test>".to_string());
let forms = parser.parse_all().expect("parse error");
let mut result = Value::Nil;
for form in forms {
result = cljrs_runtime::interp::eval::eval(&form, env).expect("eval error");
}
result
}
fn eval_fresh(src: &str) -> Value {
let (_, mut env) = make_env();
eval_src(src, &mut env)
}
#[test]
fn arithmetic_is_correct() {
assert_eq!(eval_fresh("(+ 1 2)"), Value::Long(3));
assert_eq!(eval_fresh("(* 6 7)"), Value::Long(42));
assert_eq!(eval_fresh("(- 10 3)"), Value::Long(7));
}
#[test]
fn if_form_is_correct() {
assert_eq!(eval_fresh("(if true 1 2)"), Value::Long(1));
assert_eq!(eval_fresh("(if false 1 2)"), Value::Long(2));
assert!(
matches!(eval_fresh("(if nil :no :yes)"), Value::Keyword(p) if p.get().name.as_ref() == "yes")
);
}
#[test]
fn let_binding_is_correct() {
assert_eq!(eval_fresh("(let [x 10 y (+ x 5)] y)"), Value::Long(15));
}
#[test]
fn def_scalar_does_not_panic() {
eval_fresh("(def x 42)");
}
#[test]
fn def_vector_does_not_panic() {
eval_fresh("(def v [1 2 3])");
}
#[test]
fn def_map_does_not_panic() {
eval_fresh("(def m {:a 1 :b 2})");
}
#[test]
fn def_value_is_readable() {
let (globals, mut env) = make_env();
eval_src("(def pi 3)", &mut env);
let val = globals
.lookup_in_ns("user", "pi")
.expect("def should intern var in user ns");
assert_eq!(val, Value::Long(3));
}
#[test]
fn def_vector_var_is_static() {
let (globals, mut env) = make_env();
eval_src("(def v [10 20 30])", &mut env);
let val = globals
.lookup_in_ns("user", "v")
.expect("var should exist after def");
#[cfg(debug_assertions)]
{
if let Value::Vector(p) = &val {
assert!(
p.is_static_alloc(),
"def'd vector must be allocated in the StaticArena"
);
} else {
panic!("expected Value::Vector, got {val:?}");
}
}
if let Value::Vector(p) = val {
assert_eq!(p.get().count(), 3);
} else {
panic!("expected Value::Vector");
}
}
#[test]
fn defn_and_call_is_correct() {
assert_eq!(
eval_fresh("(defn double [x] (* x 2)) (double 21)"),
Value::Long(42)
);
}
#[test]
fn nested_fn_calls_do_not_corrupt_region_stack() {
assert_eq!(
eval_fresh(
"(defn add1 [x] (+ x 1)) \
(defn add2 [x] (add1 (add1 x))) \
(defn add4 [x] (add2 (add2 x))) \
(add4 38)"
),
Value::Long(42)
);
}
#[test]
fn fn_return_value_is_correct_in_caller_context() {
assert_eq!(
eval_fresh(
"(defn make-pair [a b] [a b]) \
(let [p (make-pair 10 20)] (count p))"
),
Value::Long(2)
);
}
#[test]
fn closure_capture_is_correct() {
assert_eq!(
eval_fresh(
"(defn make-adder [n] (fn [x] (+ x n))) \
(let [add5 (make-adder 5)] (add5 37))"
),
Value::Long(42)
);
}
#[test]
fn loop_sum_accumulates_correctly() {
assert_eq!(
eval_fresh("(loop [acc 0 i 1] (if (> i 10) acc (recur (+ acc i) (+ i 1))))"),
Value::Long(55)
);
}
#[test]
fn loop_with_vector_accumulator_is_correct() {
let result = eval_fresh(
"(loop [acc [] i 0] \
(if (= i 5) \
acc \
(recur (conj acc i) (+ i 1))))",
);
if let Value::Vector(p) = result {
let v = p.get();
assert_eq!(v.count(), 5);
assert_eq!(v.nth(0), Some(&Value::Long(0)));
assert_eq!(v.nth(4), Some(&Value::Long(4)));
} else {
panic!("expected Vector");
}
}
#[test]
fn loop_in_defn_is_correct() {
assert_eq!(
eval_fresh(
"(defn factorial [n] \
(loop [i n acc 1] \
(if (<= i 1) acc (recur (- i 1) (* acc i))))) \
(factorial 10)"
),
Value::Long(3_628_800)
);
}
#[test]
fn atom_init_does_not_panic() {
eval_fresh("(def a (atom 0))");
}
#[test]
fn atom_swap_is_correct() {
let (_, mut env) = make_env();
eval_src("(def a (atom 0))", &mut env);
eval_src("(swap! a inc)", &mut env);
eval_src("(swap! a inc)", &mut env);
let val = eval_src("@a", &mut env);
assert_eq!(val, Value::Long(2));
}
#[test]
fn reset_bang_is_correct_and_does_not_panic() {
let (_, mut env) = make_env();
eval_src("(def a (atom 0))", &mut env);
eval_src("(reset! a 42)", &mut env);
let val = eval_src("@a", &mut env);
assert_eq!(val, Value::Long(42));
}
#[test]
fn loop_with_atom_accumulation_does_not_panic() {
let (_, mut env) = make_env();
eval_src("(def log (atom []))", &mut env);
eval_src(
"(loop [i 0] \
(when (< i 5) \
(swap! log conj i) \
(recur (+ i 1))))",
&mut env,
);
let val = eval_src("@log", &mut env);
if let Value::Vector(p) = val {
assert_eq!(p.get().count(), 5);
} else {
panic!("expected Vector from atom deref");
}
}
#[test]
fn str_concat_is_correct() {
let result = eval_fresh(r#"(str "hello" " " "world")"#);
if let Value::Str(p) = result {
assert_eq!(p.get().as_str(), "hello world");
} else {
panic!("expected Str");
}
}
#[test]
fn assoc_returns_fresh_map() {
let (globals, mut env) = make_env();
eval_src("(def m {:count 0})", &mut env);
eval_src(
"(defn update-count [m] (assoc m :count (inc (:count m))))",
&mut env,
);
let result = eval_src("(update-count m)", &mut env);
#[cfg(debug_assertions)]
{
if let Value::Map(m) = &result {
use cljrs_value::MapValue;
let is_static = match m {
MapValue::Array(p) => p.is_static_alloc(),
MapValue::Hash(p) => p.is_static_alloc(),
MapValue::Sorted(p) => p.is_static_alloc(),
};
let _ = is_static; }
}
let count = eval_src("(:count (update-count m))", &mut env);
assert_eq!(count, Value::Long(1));
let orig = globals
.lookup_in_ns("user", "m")
.expect("m should be defined");
if let Value::Map(_) = orig {
let orig_count = eval_src("(:count m)", &mut env);
assert_eq!(orig_count, Value::Long(0));
}
}
#[test]
fn recursive_fibonacci_is_correct() {
assert_eq!(
eval_fresh(
"(defn fib [n] \
(if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2))))) \
(fib 10)"
),
Value::Long(55)
);
}
#[test]
fn multiple_defs_and_calls_work() {
let result = eval_fresh(
"(def a 10) \
(def b 20) \
(defn sum [x y] (+ x y)) \
(sum a b)",
);
assert_eq!(result, Value::Long(30));
}