use std::sync::Arc;
use cljrs_eval::GlobalEnv;
use cljrs_gc::GcConfig;
mod core_async;
mod edn;
pub mod io;
mod set;
mod string;
const CLOJURE_TEST_SRC: &str = include_str!("clojure/test.cljrs");
const CLOJURE_STRING_SRC: &str = include_str!("clojure/string.cljrs");
const CLOJURE_SET_SRC: &str = include_str!("clojure/set.cljrs");
const CLOJURE_TEMPLATE_SRC: &str = include_str!("clojure/template.cljrs");
const CLOJURE_RUST_IO_SRC: &str = include_str!("clojure/rust/io.cljrs");
const CLOJURE_EDN_SRC: &str = include_str!("clojure/edn.cljrs");
const CLOJURE_WALK_SRC: &str = include_str!("clojure/walk.cljrs");
const CLOJURE_DATA_SRC: &str = include_str!("clojure/data.cljrs");
const COLJURE_ZIP_SRC: &str = include_str!("clojure/zip.cljrs");
macro_rules! register_fns {
($globals:expr, $ns:expr, [ $( ($name:expr, $arity:expr, $func:expr) ),* $(,)? ]) => {{
use cljrs_gc::GcPtr;
use cljrs_value::{NativeFn, Value};
let ns: &str = $ns;
$(
{
let nf = NativeFn::new($name, $arity, $func);
$globals.intern(ns, std::sync::Arc::from($name), Value::NativeFunction(GcPtr::new(nf)));
}
)*
}};
}
pub(crate) use register_fns;
pub fn register(globals: &Arc<GlobalEnv>) {
string::register(globals, "clojure.string");
globals.register_builtin_source("clojure.string", CLOJURE_STRING_SRC);
set::register(globals, "clojure.set");
globals.register_builtin_source("clojure.set", CLOJURE_SET_SRC);
globals.register_builtin_source("clojure.template", CLOJURE_TEMPLATE_SRC);
globals.register_builtin_source("clojure.test", CLOJURE_TEST_SRC);
io::register(globals, "clojure.rust.io");
globals.register_builtin_source("clojure.rust.io", CLOJURE_RUST_IO_SRC);
edn::register(globals, "clojure.edn");
globals.register_builtin_source("clojure.edn", CLOJURE_EDN_SRC);
globals.register_builtin_source("clojure.walk", CLOJURE_WALK_SRC);
globals.register_builtin_source("clojure.data", CLOJURE_DATA_SRC);
globals.register_builtin_source("clojure.zip", COLJURE_ZIP_SRC);
}
pub fn standard_env() -> Arc<GlobalEnv> {
let globals = cljrs_eval::standard_env_minimal();
register(&globals);
globals
}
pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
let globals = standard_env();
globals.set_source_paths(source_paths);
globals
}
pub fn standard_env_with_paths_and_config(
source_paths: Vec<std::path::PathBuf>,
gc_config: Arc<GcConfig>,
) -> Arc<GlobalEnv> {
let globals = standard_env();
globals.set_source_paths(source_paths);
globals.set_gc_config(gc_config.clone());
cljrs_gc::HEAP.set_config(gc_config);
let roots_globals = globals.clone();
cljrs_gc::HEAP.register_root_tracer(move |visitor| {
use cljrs_gc::GcVisitor as _;
let namespaces = roots_globals.namespaces.read().unwrap();
for (_name, ns_ptr) in namespaces.iter() {
visitor.visit(ns_ptr);
}
});
globals
}
#[cfg(test)]
mod tests {
use super::*;
use cljrs_eval::{Env, EvalResult, eval};
use cljrs_reader::Parser;
use cljrs_value::Value;
fn make_env() -> (Arc<GlobalEnv>, Env) {
let globals = standard_env();
let env = Env::new(globals.clone(), "user");
(globals, env)
}
fn run(src: &str, env: &mut Env) -> EvalResult {
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 = eval(&form, env)?;
}
Ok(result)
}
#[test]
fn test_string_upper_lower() {
let (_, mut env) = make_env();
run("(require '[clojure.string :as str])", &mut env).unwrap();
assert_eq!(
run("(str/upper-case \"hello\")", &mut env).unwrap(),
Value::string("HELLO")
);
assert_eq!(
run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
Value::string("world")
);
}
#[test]
fn test_string_trim() {
let (_, mut env) = make_env();
run("(require '[clojure.string :as str])", &mut env).unwrap();
assert_eq!(
run("(str/trim \" hello \")", &mut env).unwrap(),
Value::string("hello")
);
assert_eq!(
run("(str/triml \" hi\")", &mut env).unwrap(),
Value::string("hi")
);
assert_eq!(
run("(str/trimr \"hi \")", &mut env).unwrap(),
Value::string("hi")
);
}
#[test]
fn test_string_predicates() {
let (_, mut env) = make_env();
run("(require '[clojure.string :as str])", &mut env).unwrap();
assert_eq!(
run("(str/blank? \" \")", &mut env).unwrap(),
Value::Bool(true)
);
assert_eq!(
run("(str/blank? \"x\")", &mut env).unwrap(),
Value::Bool(false)
);
assert_eq!(
run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
Value::Bool(true)
);
assert_eq!(
run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
Value::Bool(true)
);
assert_eq!(
run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
Value::Bool(true)
);
}
#[test]
fn test_string_replace() {
let (_, mut env) = make_env();
run("(require '[clojure.string :as str])", &mut env).unwrap();
assert_eq!(
run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
Value::string("aaXXcc")
);
assert_eq!(
run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
Value::string("Xabbcc")
);
}
#[test]
fn test_string_split_join() {
let (_, mut env) = make_env();
run("(require '[clojure.string :as str])", &mut env).unwrap();
let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
assert!(matches!(v, Value::Vector(_)));
assert_eq!(
run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
Value::string("a-b-c")
);
}
#[test]
fn test_string_capitalize() {
let (_, mut env) = make_env();
run("(require '[clojure.string :as str])", &mut env).unwrap();
assert_eq!(
run("(str/capitalize \"hello world\")", &mut env).unwrap(),
Value::string("Hello world")
);
}
#[test]
fn test_string_split_lines() {
let (_, mut env) = make_env();
run("(require '[clojure.string :as str])", &mut env).unwrap();
let v = run("(str/split-lines \"a\\nb\\nc\")", &mut env).unwrap();
assert!(matches!(v, Value::Vector(_)));
}
#[test]
fn test_set_union() {
let (_, mut env) = make_env();
run("(require '[clojure.set :as s])", &mut env).unwrap();
let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
match v {
Value::Set(s) => assert_eq!(s.count(), 3),
other => panic!("expected set, got {other:?}"),
}
}
#[test]
fn test_set_intersection() {
let (_, mut env) = make_env();
run("(require '[clojure.set :as s])", &mut env).unwrap();
let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
match v {
Value::Set(s) => assert_eq!(s.count(), 2),
other => panic!("expected set, got {other:?}"),
}
}
#[test]
fn test_set_difference() {
let (_, mut env) = make_env();
run("(require '[clojure.set :as s])", &mut env).unwrap();
let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
match v {
Value::Set(s) => assert_eq!(s.count(), 1),
other => panic!("expected set, got {other:?}"),
}
}
#[test]
fn test_set_subset_superset() {
let (_, mut env) = make_env();
run("(require '[clojure.set :as s])", &mut env).unwrap();
assert_eq!(
run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
Value::Bool(true)
);
assert_eq!(
run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
Value::Bool(true)
);
}
#[test]
fn test_set_map_invert() {
let (_, mut env) = make_env();
run("(require '[clojure.set :as s])", &mut env).unwrap();
let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
assert!(matches!(v, Value::Map(_)));
}
#[test]
fn test_clojure_test_lazy_load() {
let (_, mut env) = make_env();
run(
"(require '[clojure.test :refer [is deftest run-tests]])",
&mut env,
)
.unwrap();
let v = run("(is (= 1 1))", &mut env).unwrap();
assert_eq!(v, Value::Bool(true));
}
}