1use std::sync::Arc;
14
15use cljrs_eval::GlobalEnv;
16#[cfg(not(target_arch = "wasm32"))]
17use cljrs_gc::GcConfig;
18
19mod core_async;
20#[cfg(not(target_arch = "wasm32"))]
22mod edn;
23#[cfg(not(target_arch = "wasm32"))]
24pub mod io;
25mod set;
26mod string;
27const CLOJURE_TEST_SRC: &str = include_str!("clojure/test.cljrs");
30const CLOJURE_STRING_SRC: &str = include_str!("clojure/string.cljrs");
31const CLOJURE_SET_SRC: &str = include_str!("clojure/set.cljrs");
32const CLOJURE_TEMPLATE_SRC: &str = include_str!("clojure/template.cljrs");
33#[cfg(not(target_arch = "wasm32"))]
34const CLOJURE_RUST_IO_SRC: &str = include_str!("clojure/rust/io.cljrs");
35#[cfg(not(target_arch = "wasm32"))]
36const CLOJURE_EDN_SRC: &str = include_str!("clojure/edn.cljrs");
37const CLOJURE_WALK_SRC: &str = include_str!("clojure/walk.cljrs");
38const CLOJURE_DATA_SRC: &str = include_str!("clojure/data.cljrs");
39const COLJURE_ZIP_SRC: &str = include_str!("clojure/zip.cljrs");
40
41macro_rules! register_fns {
46 ($globals:expr, $ns:expr, [ $( ($name:expr, $arity:expr, $func:expr) ),* $(,)? ]) => {{
47 use cljrs_gc::GcPtr;
48 use cljrs_value::{NativeFn, Value};
49 let ns: &str = $ns;
50 $(
51 {
52 let nf = NativeFn::new($name, $arity, $func);
53 $globals.intern(ns, std::sync::Arc::from($name), Value::NativeFunction(GcPtr::new(nf)));
54 }
55 )*
56 }};
57}
58
59pub(crate) use register_fns;
60
61pub fn register(globals: &Arc<GlobalEnv>) {
70 string::register(globals, "clojure.string");
73 globals.register_builtin_source("clojure.string", CLOJURE_STRING_SRC);
74
75 set::register(globals, "clojure.set");
77 globals.register_builtin_source("clojure.set", CLOJURE_SET_SRC);
78
79 globals.register_builtin_source("clojure.template", CLOJURE_TEMPLATE_SRC);
81
82 globals.register_builtin_source("clojure.test", CLOJURE_TEST_SRC);
84
85 #[cfg(not(target_arch = "wasm32"))]
87 {
88 io::register(globals, "clojure.rust.io");
89 globals.register_builtin_source("clojure.rust.io", CLOJURE_RUST_IO_SRC);
90
91 edn::register(globals, "clojure.edn");
92 globals.register_builtin_source("clojure.edn", CLOJURE_EDN_SRC);
93 }
94
95 globals.register_builtin_source("clojure.walk", CLOJURE_WALK_SRC);
97
98 globals.register_builtin_source("clojure.data", CLOJURE_DATA_SRC);
100
101 globals.register_builtin_source("clojure.zip", COLJURE_ZIP_SRC);
103}
104
105#[cfg(not(target_arch = "wasm32"))]
117pub fn standard_env_no_ir() -> Arc<GlobalEnv> {
118 let globals = cljrs_eval::standard_env_minimal_no_ir();
119 register(&globals);
120
121 cljrs_gc::HEAP.set_config_from_env();
122 let roots_gc = globals.clone();
123 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
124 use cljrs_gc::GcVisitor as _;
125 let namespaces = roots_gc.namespaces.read().unwrap();
126 for (_name, ns_ptr) in namespaces.iter() {
127 visitor.visit(ns_ptr);
128 }
129 });
130
131 globals
132}
133
134#[cfg(not(target_arch = "wasm32"))]
140pub fn standard_env() -> Arc<GlobalEnv> {
141 let globals = cljrs_eval::standard_env_minimal();
142 register(&globals);
143
144 cljrs_gc::HEAP.set_config_from_env();
148 let roots_gc = globals.clone();
149 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
150 use cljrs_gc::GcVisitor as _;
151 let namespaces = roots_gc.namespaces.read().unwrap();
152 for (_name, ns_ptr) in namespaces.iter() {
153 visitor.visit(ns_ptr);
154 }
155 });
156
157 cljrs_eval::register_compiler_sources(&globals);
161 {
162 let mut env = cljrs_eval::Env::new(globals.clone(), "user");
163 cljrs_eval::ensure_compiler_loaded(&globals, &mut env);
164 }
165
166 globals
167}
168
169#[cfg(not(target_arch = "wasm32"))]
171pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
172 let globals = standard_env();
173 globals.set_source_paths(source_paths);
174 globals
175}
176
177#[cfg(not(target_arch = "wasm32"))]
179pub fn standard_env_with_paths_and_config(
180 source_paths: Vec<std::path::PathBuf>,
181 gc_config: Arc<GcConfig>,
182) -> Arc<GlobalEnv> {
183 let globals = standard_env();
184 globals.set_source_paths(source_paths);
185 globals.set_gc_config(gc_config.clone());
186 cljrs_gc::HEAP.set_config(gc_config);
189 globals
190}
191
192#[cfg(test)]
195mod tests {
196 use super::*;
197 use cljrs_eval::{Env, EvalResult, eval};
198 use cljrs_reader::Parser;
199 use cljrs_value::Value;
200
201 fn make_env() -> (Arc<GlobalEnv>, Env) {
202 let globals = standard_env();
203 let env = Env::new(globals.clone(), "user");
204 (globals, env)
205 }
206
207 #[allow(clippy::result_large_err)]
208 fn run(src: &str, env: &mut Env) -> EvalResult {
209 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
210 let forms = parser.parse_all().expect("parse error");
211 let mut result = Value::Nil;
212 for form in forms {
213 result = eval(&form, env)?;
214 }
215 Ok(result)
216 }
217
218 #[test]
221 fn test_string_upper_lower() {
222 let (_, mut env) = make_env();
223 run("(require '[clojure.string :as str])", &mut env).unwrap();
224 assert_eq!(
225 run("(str/upper-case \"hello\")", &mut env).unwrap(),
226 Value::string("HELLO")
227 );
228 assert_eq!(
229 run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
230 Value::string("world")
231 );
232 }
233
234 #[test]
235 fn test_string_trim() {
236 let (_, mut env) = make_env();
237 run("(require '[clojure.string :as str])", &mut env).unwrap();
238 assert_eq!(
239 run("(str/trim \" hello \")", &mut env).unwrap(),
240 Value::string("hello")
241 );
242 assert_eq!(
243 run("(str/triml \" hi\")", &mut env).unwrap(),
244 Value::string("hi")
245 );
246 assert_eq!(
247 run("(str/trimr \"hi \")", &mut env).unwrap(),
248 Value::string("hi")
249 );
250 }
251
252 #[test]
253 fn test_string_predicates() {
254 let (_, mut env) = make_env();
255 run("(require '[clojure.string :as str])", &mut env).unwrap();
256 assert_eq!(
257 run("(str/blank? \" \")", &mut env).unwrap(),
258 Value::Bool(true)
259 );
260 assert_eq!(
261 run("(str/blank? \"x\")", &mut env).unwrap(),
262 Value::Bool(false)
263 );
264 assert_eq!(
265 run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
266 Value::Bool(true)
267 );
268 assert_eq!(
269 run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
270 Value::Bool(true)
271 );
272 assert_eq!(
273 run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
274 Value::Bool(true)
275 );
276 }
277
278 #[test]
279 fn test_string_replace() {
280 let (_, mut env) = make_env();
281 run("(require '[clojure.string :as str])", &mut env).unwrap();
282 assert_eq!(
283 run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
284 Value::string("aaXXcc")
285 );
286 assert_eq!(
287 run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
288 Value::string("Xabbcc")
289 );
290 }
291
292 #[test]
293 fn test_string_split_join() {
294 let (_, mut env) = make_env();
295 run("(require '[clojure.string :as str])", &mut env).unwrap();
296 let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
297 assert!(matches!(v, Value::Vector(_)));
298 assert_eq!(
299 run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
300 Value::string("a-b-c")
301 );
302 }
303
304 #[test]
305 fn test_string_capitalize() {
306 let (_, mut env) = make_env();
307 run("(require '[clojure.string :as str])", &mut env).unwrap();
308 assert_eq!(
309 run("(str/capitalize \"hello world\")", &mut env).unwrap(),
310 Value::string("Hello world")
311 );
312 }
313
314 #[test]
315 fn test_string_split_lines() {
316 let (_, mut env) = make_env();
317 run("(require '[clojure.string :as str])", &mut env).unwrap();
318 let v = run("(str/split-lines \"a\\nb\\nc\")", &mut env).unwrap();
319 assert!(matches!(v, Value::Vector(_)));
320 }
321
322 #[test]
325 fn test_set_union() {
326 let (_, mut env) = make_env();
327 run("(require '[clojure.set :as s])", &mut env).unwrap();
328 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
329 match v {
330 Value::Set(s) => assert_eq!(s.count(), 3),
331 other => panic!("expected set, got {other:?}"),
332 }
333 }
334
335 #[test]
336 fn test_set_intersection() {
337 let (_, mut env) = make_env();
338 run("(require '[clojure.set :as s])", &mut env).unwrap();
339 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
340 match v {
341 Value::Set(s) => assert_eq!(s.count(), 2),
342 other => panic!("expected set, got {other:?}"),
343 }
344 }
345
346 #[test]
347 fn test_set_difference() {
348 let (_, mut env) = make_env();
349 run("(require '[clojure.set :as s])", &mut env).unwrap();
350 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
351 match v {
352 Value::Set(s) => assert_eq!(s.count(), 1),
353 other => panic!("expected set, got {other:?}"),
354 }
355 }
356
357 #[test]
358 fn test_set_subset_superset() {
359 let (_, mut env) = make_env();
360 run("(require '[clojure.set :as s])", &mut env).unwrap();
361 assert_eq!(
362 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
363 Value::Bool(true)
364 );
365 assert_eq!(
366 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
367 Value::Bool(true)
368 );
369 }
370
371 #[test]
372 fn test_set_map_invert() {
373 let (_, mut env) = make_env();
374 run("(require '[clojure.set :as s])", &mut env).unwrap();
375 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
376 assert!(matches!(v, Value::Map(_)));
377 }
378
379 #[test]
382 fn test_clojure_test_lazy_load() {
383 std::thread::Builder::new()
387 .stack_size(16 * 1024 * 1024)
388 .spawn(|| {
389 let (_, mut env) = make_env();
390 run(
393 "(require '[clojure.test :refer [is deftest run-tests]])",
394 &mut env,
395 )
396 .unwrap();
397 let v = run("(is (= 1 1))", &mut env).unwrap();
398 assert_eq!(v, Value::Bool(true));
399 })
400 .unwrap()
401 .join()
402 .unwrap();
403 }
404}