use std::path::PathBuf;
use cljrs_reader::Parser;
use cljrs_runtime::env::env::Env;
use cljrs_value::Value;
fn env_with_sources(files: &[(&str, &str)]) -> (tempfile::TempDir, Env) {
let dir = tempfile::tempdir().expect("tempdir");
for (rel, src) in files {
let path = dir.path().join(rel);
std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
std::fs::write(&path, src).expect("write");
}
let globals = cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::TreeWalk)
.source_paths(vec![PathBuf::from(dir.path())])
.build()
.expect("runtime")
.into_globals();
let env = Env::new(globals, "user");
(dir, env)
}
fn eval_in(env: &mut Env, src: &str) -> Result<Value, String> {
let mut parser = Parser::new(src.to_string(), "<test>".to_string());
let forms = parser.parse_all().map_err(|e| format!("parse: {e:?}"))?;
let mut result = Value::Nil;
for form in forms {
result = cljrs_runtime::interp::eval::eval(&form, env).map_err(|e| format!("{e:?}"))?;
}
Ok(result)
}
const LIB: &str = r#"
(ns some.lib)
(def value 41)
(def ^:dynamic *knob* :default-knob)
(defmacro twice [x] (list 'clojure.core/+ x x))
(defprotocol Shaped (area [s]))
"#;
fn agrees(expr_template: &str) -> Value {
let (_dir, mut env) = env_with_sources(&[("some/lib.cljrs", LIB)]);
eval_in(&mut env, "(require '[some.lib :as l])").expect("require with an alias");
let via_alias = eval_in(&mut env, &expr_template.replace("{ns}", "l"))
.unwrap_or_else(|e| panic!("through the alias: {expr_template}\n{e}"));
let via_full = eval_in(&mut env, &expr_template.replace("{ns}", "some.lib"))
.unwrap_or_else(|e| panic!("through the full name: {expr_template}\n{e}"));
assert_eq!(
via_alias, via_full,
"an alias and the full namespace name disagreed for: {expr_template}"
);
via_alias
}
#[test]
fn a_qualified_symbol_resolves_the_same_either_way() {
assert_eq!(agrees("{ns}/value"), Value::Long(41));
}
#[test]
fn a_var_form_resolves_the_same_either_way() {
assert_eq!(agrees("@(var {ns}/value)"), Value::Long(41));
}
#[test]
fn a_macro_call_resolves_the_same_either_way() {
assert_eq!(agrees("({ns}/twice 21)"), Value::Long(42));
}
#[test]
fn a_syntax_quoted_symbol_resolves_the_same_either_way() {
assert_eq!(
agrees("(str `{ns}/value)"),
Value::string("some.lib/value".to_string())
);
}
#[test]
fn a_binding_target_resolves_the_same_either_way() {
assert_eq!(
agrees("(binding [{ns}/*knob* :bound] {ns}/*knob*)"),
Value::keyword(cljrs_value::Keyword::simple("bound"))
);
}
#[test]
fn a_protocol_name_resolves_the_same_either_way() {
assert_eq!(
agrees("(do (extend-protocol {ns}/Shaped Long (area [s] (* s 2))) (some.lib/area 5))"),
Value::Long(10)
);
}
#[test]
fn an_unknown_namespace_part_is_taken_literally() {
let (_dir, mut env) = env_with_sources(&[("some/lib.cljrs", LIB)]);
eval_in(&mut env, "(require 'some.lib)").expect("require without an alias");
assert_eq!(
eval_in(&mut env, "some.lib/value").expect("full name with no alias in scope"),
Value::Long(41)
);
}
#[test]
fn an_unqualified_name_means_the_current_namespace() {
let (_dir, mut env) = env_with_sources(&[]);
assert_eq!(
eval_in(&mut env, "(do (def here 7) @(var here))").expect("current-ns lookup"),
Value::Long(7)
);
}