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)
.build()
.expect("runtime")
.into_globals();
let env = Env::new(globals.clone(), "user");
(globals, env)
}
fn eval_pr(src: &str) -> String {
let (_globals, mut env) = make_env();
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, &mut env).expect("eval error");
}
match result {
Value::Str(s) => s.get().as_str().to_string(),
other => panic!("expected a string from pr-str, got a {}", other.type_name()),
}
}
fn eval_err(src: &str) -> String {
let (_globals, mut env) = make_env();
let mut parser = Parser::new(src.to_string(), "<test>".to_string());
let forms = parser.parse_all().expect("parse error");
let mut last = Ok(Value::Nil);
for form in forms {
last = cljrs_runtime::interp::eval::eval(&form, &mut env);
if last.is_err() {
break;
}
}
match last {
Err(e) => format!("{e:?}"),
Ok(v) => panic!("expected an error, got a {}", v.type_name()),
}
}
#[test]
fn deftype_is_implemented() {
assert_eq!(eval_pr("(deftype T [x y]) (pr-str (.-x (->T 1 2)))"), "1");
}
#[test]
fn positional_constructor_binds_fields_in_order() {
assert_eq!(
eval_pr("(deftype Point [x y]) (let [p (->Point 3 4)] (pr-str [(.-x p) (.-y p)]))"),
"[3 4]"
);
}
#[test]
fn type_name_is_interned_so_instance_resolves() {
assert_eq!(
eval_pr("(deftype T [x]) (pr-str (instance? T (->T 1)))"),
"true"
);
}
#[test]
fn deftype_has_no_map_constructor() {
assert!(
eval_err("(deftype T [x]) (map->T {:x 1})").contains("map->T"),
"expected map->T to be unresolvable for a deftype"
);
}
#[test]
fn a_field_vector_may_carry_its_own_metadata() {
assert_eq!(
eval_pr("(deftype T ^:marker [x]) (pr-str (.-x (->T 7)))"),
"7"
);
}
#[test]
fn immutable_fields_are_in_scope_in_a_method_body() {
assert_eq!(
eval_pr(
"(defprotocol P (describe [this]))
(deftype T [a b] P (describe [_] [a b]))
(pr-str (describe (->T 1 2)))"
),
"[1 2]"
);
}
#[test]
fn one_type_can_implement_several_protocols() {
assert_eq!(
eval_pr(
"(defprotocol P (p-of [this]))
(defprotocol Q (q-of [this]))
(deftype T [a] P (p-of [_] (* a 10)) Q (q-of [_] (* a 100)))
(let [t (->T 2)] (pr-str [(p-of t) (q-of t)]))"
),
"[20 200]"
);
}
#[test]
fn set_bang_on_a_bare_field_name_inside_a_method() {
assert_eq!(
eval_pr(
"(defprotocol Counter (bump [this]) (peek-n [this]))
(deftype C [^:unsynchronized-mutable n]
Counter
(bump [_] (set! n (inc n)))
(peek-n [_] n))
(let [c (->C 0)] (bump c) (bump c) (pr-str (peek-n c)))"
),
"2"
);
}
#[test]
fn a_write_is_visible_to_a_later_read_in_the_same_method() {
assert_eq!(
eval_pr(
"(defprotocol Counter (bump-twice [this]))
(deftype C [^:unsynchronized-mutable n]
Counter
(bump-twice [_] (set! n (inc n)) (set! n (inc n)) n))
(pr-str (bump-twice (->C 5)))"
),
"7"
);
}
#[test]
fn volatile_mutable_behaves_like_unsynchronized_mutable() {
assert_eq!(
eval_pr(
"(defprotocol Counter (bump [this]) (peek-n [this]))
(deftype C [^:volatile-mutable n]
Counter
(bump [_] (set! n (inc n)))
(peek-n [_] n))
(let [c (->C 41)] (bump c) (pr-str (peek-n c)))"
),
"42"
);
}
#[test]
fn set_bang_on_an_explicit_field_target() {
assert_eq!(
eval_pr(
"(deftype Box [^:unsynchronized-mutable v])
(let [b (->Box :old)] (set! (.-v b) :new) (pr-str (.-v b)))"
),
":new"
);
}
#[test]
fn mutating_one_instance_does_not_touch_another() {
assert_eq!(
eval_pr(
"(deftype Box [^:unsynchronized-mutable v])
(let [a (->Box 1) b (->Box 1)]
(set! (.-v a) 99)
(pr-str [(.-v a) (.-v b)]))"
),
"[99 1]"
);
}
#[test]
fn immutable_and_mutable_fields_coexist() {
assert_eq!(
eval_pr(
"(defprotocol P (report [this]))
(deftype T [label ^:unsynchronized-mutable n]
P
(report [_] (set! n (inc n)) [label n]))
(let [t (->T \"hits\" 0)] (report t) (pr-str (report t)))"
),
"[\"hits\" 2]"
);
}
#[test]
fn a_hot_mutable_field_method_accumulates_every_write() {
assert_eq!(
eval_pr(
"(defprotocol Counter (bump [this]) (peek-n [this]))
(deftype C [^:unsynchronized-mutable n]
Counter
(bump [_] (set! n (inc n)))
(peek-n [_] n))
(let [c (->C 0)]
(dotimes [_ 500] (bump c))
(pr-str (peek-n c)))"
),
"500"
);
}
#[test]
fn set_bang_rejects_a_field_the_type_did_not_declare_mutable() {
let err = eval_err("(deftype T [^:unsynchronized-mutable a b]) (set! (.-b (->T 1 2)) 3)");
assert!(
err.contains("not a mutable field"),
"expected a mutable-field error, got {err}"
);
}
#[test]
fn set_bang_on_a_dynamic_var_is_unaffected() {
assert_eq!(
eval_pr(
"(def ^:dynamic *v* 1)
(binding [*v* 2] (set! *v* 3) (pr-str *v*))"
),
"3"
);
}