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()),
}
}
#[test]
fn dispatch_and_default() {
let src = "(defmulti area :shape)
(defmethod area :square [s] (* (:side s) (:side s)))
(defmethod area :default [_] :unknown)
(pr-str [(area {:shape :square :side 3}) (area {:shape :blob})])";
assert_eq!(eval_pr(src), "[9 :unknown]");
}
#[test]
fn a_docstring_is_not_read_as_the_dispatch_fn() {
let src = "(defmulti area \"the area of a shape\" :shape)
(defmethod area :square [s] (* (:side s) (:side s)))
(pr-str (area {:shape :square :side 4}))";
assert_eq!(eval_pr(src), "16");
}
#[test]
fn the_default_dispatch_value_can_be_chosen() {
let src = "(defmulti f identity :default :other)
(defmethod f 1 [x] [:one x])
(defmethod f :other [x] [:fallback x])
(pr-str [(f 1) (f 99)])";
assert_eq!(eval_pr(src), "[[:one 1] [:fallback 99]]");
}
#[test]
fn nil_is_a_usable_default_dispatch_value() {
let src = "(defmulti f identity :default nil)
(defmethod f nil [x] [:fallback x])
(pr-str (f 99))";
assert_eq!(eval_pr(src), "[:fallback 99]");
}
#[test]
fn defmethod_finds_a_multimethod_in_another_namespace() {
let src = "(ns other.ns)
(defmulti describe :kind)
(ns user)
(alias 'o 'other.ns)
(defmethod o/describe :thing [x] [:described (:kind x)])
(pr-str (o/describe {:kind :thing}))";
assert_eq!(eval_pr(src), "[:described :thing]");
}
#[test]
fn defmethod_on_a_name_that_is_not_a_multimethod_errors() {
let (_globals, mut env) = make_env();
let src = "(def notamulti 42)
(defmethod notamulti :x [_] 1)";
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);
}
let err = last.expect_err("registering a method on a non-multimethod must fail");
assert!(
format!("{err:?}").contains("multimethod"),
"expected a multimethod type error, got {err:?}"
);
}
#[test]
fn defmulti_returns_the_var() {
assert_eq!(eval_pr("(pr-str (defmulti m identity))"), "#'user/m");
}
#[test]
fn the_name_keeps_its_metadata() {
let src = "(defmulti ^{:marker true} m identity)
(pr-str (:marker (meta (var m))))";
assert_eq!(eval_pr(src), "true");
}
#[test]
fn multi_fn_and_add_method_are_callable_on_their_own() {
let src = "(def m (multi-fn 'm :shape))
(add-method m :square (fn [s] (:side s)))
(pr-str (m {:shape :square :side 7}))";
assert_eq!(eval_pr(src), "7");
}
#[test]
fn add_method_and_remove_method_agree_on_the_key() {
let src = "(defmulti area :shape)
(defmethod area :square [_] :square-area)
(pr-str [(count (methods area))
(do (remove-method area :square) (count (methods area)))])";
assert_eq!(eval_pr(src), "[1 0]");
}
#[test]
fn multi_fn_defaults_its_default_dispatch_value() {
let src = "(def m (multi-fn 'm identity))
(add-method m :default (fn [_] :fell-through))
(pr-str (m :anything))";
assert_eq!(eval_pr(src), ":fell-through");
}