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"))]
115pub fn standard_env_no_ir() -> Arc<GlobalEnv> {
116 let globals = cljrs_eval::standard_env_minimal_no_ir();
117 register(&globals);
118
119 cljrs_gc::HEAP.set_config_from_env();
120 let roots_gc = globals.clone();
121 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
122 use cljrs_gc::GcVisitor as _;
123 let namespaces = roots_gc.namespaces.read().unwrap();
124 for (_name, ns_ptr) in namespaces.iter() {
125 visitor.visit(ns_ptr);
126 }
127 });
128
129 globals
130}
131
132#[cfg(not(target_arch = "wasm32"))]
138pub fn standard_env() -> Arc<GlobalEnv> {
139 let globals = cljrs_eval::standard_env_minimal();
140 register(&globals);
141
142 cljrs_gc::HEAP.set_config_from_env();
146 let roots_gc = globals.clone();
147 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
148 use cljrs_gc::GcVisitor as _;
149 let namespaces = roots_gc.namespaces.read().unwrap();
150 for (_name, ns_ptr) in namespaces.iter() {
151 visitor.visit(ns_ptr);
152 }
153 });
154
155 cljrs_eval::mark_compiler_ready(&globals);
157
158 globals
159}
160
161#[cfg(not(target_arch = "wasm32"))]
163pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
164 let globals = standard_env();
165 globals.set_source_paths(source_paths);
166 globals
167}
168
169#[cfg(not(target_arch = "wasm32"))]
171pub fn standard_env_with_paths_and_config(
172 source_paths: Vec<std::path::PathBuf>,
173 gc_config: Arc<GcConfig>,
174) -> Arc<GlobalEnv> {
175 let globals = standard_env();
176 globals.set_source_paths(source_paths);
177 globals.set_gc_config(gc_config.clone());
178 cljrs_gc::HEAP.set_config(gc_config);
181 globals
182}
183
184#[cfg(test)]
187mod tests {
188 use super::*;
189 use cljrs_eval::{Env, EvalResult, eval};
190 use cljrs_reader::Parser;
191 use cljrs_value::Value;
192
193 fn make_env() -> (Arc<GlobalEnv>, Env) {
194 let globals = standard_env();
195 let env = Env::new(globals.clone(), "user");
196 (globals, env)
197 }
198
199 #[allow(clippy::result_large_err)]
200 fn run(src: &str, env: &mut Env) -> EvalResult {
201 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
202 let forms = parser.parse_all().expect("parse error");
203 let mut result = Value::Nil;
204 for form in forms {
205 result = eval(&form, env)?;
206 }
207 Ok(result)
208 }
209
210 #[test]
213 fn test_string_upper_lower() {
214 let (_, mut env) = make_env();
215 run("(require '[clojure.string :as str])", &mut env).unwrap();
216 assert_eq!(
217 run("(str/upper-case \"hello\")", &mut env).unwrap(),
218 Value::string("HELLO")
219 );
220 assert_eq!(
221 run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
222 Value::string("world")
223 );
224 }
225
226 #[test]
227 fn test_string_trim() {
228 let (_, mut env) = make_env();
229 run("(require '[clojure.string :as str])", &mut env).unwrap();
230 assert_eq!(
231 run("(str/trim \" hello \")", &mut env).unwrap(),
232 Value::string("hello")
233 );
234 assert_eq!(
235 run("(str/triml \" hi\")", &mut env).unwrap(),
236 Value::string("hi")
237 );
238 assert_eq!(
239 run("(str/trimr \"hi \")", &mut env).unwrap(),
240 Value::string("hi")
241 );
242 }
243
244 #[test]
245 fn test_string_predicates() {
246 let (_, mut env) = make_env();
247 run("(require '[clojure.string :as str])", &mut env).unwrap();
248 assert_eq!(
249 run("(str/blank? \" \")", &mut env).unwrap(),
250 Value::Bool(true)
251 );
252 assert_eq!(
253 run("(str/blank? \"x\")", &mut env).unwrap(),
254 Value::Bool(false)
255 );
256 assert_eq!(
257 run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
258 Value::Bool(true)
259 );
260 assert_eq!(
261 run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
262 Value::Bool(true)
263 );
264 assert_eq!(
265 run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
266 Value::Bool(true)
267 );
268 }
269
270 #[test]
271 fn test_string_replace() {
272 let (_, mut env) = make_env();
273 run("(require '[clojure.string :as str])", &mut env).unwrap();
274 assert_eq!(
275 run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
276 Value::string("aaXXcc")
277 );
278 assert_eq!(
279 run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
280 Value::string("Xabbcc")
281 );
282 assert_eq!(
284 run("(str/replace \"--host\" #\"^--\" \"\")", &mut env).unwrap(),
285 Value::string("host")
286 );
287 assert_eq!(
288 run("(str/replace \"aaa\" #\"a\" \"b\")", &mut env).unwrap(),
289 Value::string("bbb")
290 );
291 assert_eq!(
292 run("(str/replace-first \"aaa\" #\"a\" \"b\")", &mut env).unwrap(),
293 Value::string("baa")
294 );
295 assert_eq!(
296 run("(str/replace-first \"--host\" #\"^--\" \"\")", &mut env).unwrap(),
297 Value::string("host")
298 );
299 }
300
301 #[test]
302 fn test_string_split_join() {
303 let (_, mut env) = make_env();
304 run("(require '[clojure.string :as str])", &mut env).unwrap();
305 let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
306 assert!(matches!(v, Value::Vector(_)));
307 assert_eq!(
308 run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
309 Value::string("a-b-c")
310 );
311 }
312
313 #[test]
314 fn test_string_capitalize() {
315 let (_, mut env) = make_env();
316 run("(require '[clojure.string :as str])", &mut env).unwrap();
317 assert_eq!(
318 run("(str/capitalize \"hello world\")", &mut env).unwrap(),
319 Value::string("Hello world")
320 );
321 }
322
323 #[test]
324 fn test_string_split_lines() {
325 let (_, mut env) = make_env();
326 run("(require '[clojure.string :as str])", &mut env).unwrap();
327 let v = run("(str/split-lines \"a\\nb\\nc\")", &mut env).unwrap();
328 assert!(matches!(v, Value::Vector(_)));
329 }
330
331 #[test]
334 fn test_set_union() {
335 let (_, mut env) = make_env();
336 run("(require '[clojure.set :as s])", &mut env).unwrap();
337 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
338 match v {
339 Value::Set(s) => assert_eq!(s.count(), 3),
340 other => panic!("expected set, got {other:?}"),
341 }
342 }
343
344 #[test]
345 fn test_set_intersection() {
346 let (_, mut env) = make_env();
347 run("(require '[clojure.set :as s])", &mut env).unwrap();
348 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
349 match v {
350 Value::Set(s) => assert_eq!(s.count(), 2),
351 other => panic!("expected set, got {other:?}"),
352 }
353 }
354
355 #[test]
356 fn test_set_difference() {
357 let (_, mut env) = make_env();
358 run("(require '[clojure.set :as s])", &mut env).unwrap();
359 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
360 match v {
361 Value::Set(s) => assert_eq!(s.count(), 1),
362 other => panic!("expected set, got {other:?}"),
363 }
364 }
365
366 #[test]
367 fn test_set_subset_superset() {
368 let (_, mut env) = make_env();
369 run("(require '[clojure.set :as s])", &mut env).unwrap();
370 assert_eq!(
371 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
372 Value::Bool(true)
373 );
374 assert_eq!(
375 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
376 Value::Bool(true)
377 );
378 }
379
380 #[test]
381 fn test_set_map_invert() {
382 let (_, mut env) = make_env();
383 run("(require '[clojure.set :as s])", &mut env).unwrap();
384 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
385 assert!(matches!(v, Value::Map(_)));
386 }
387
388 #[test]
391 fn test_clojure_test_lazy_load() {
392 std::thread::Builder::new()
396 .stack_size(16 * 1024 * 1024)
397 .spawn(|| {
398 let (_, mut env) = make_env();
399 run(
402 "(require '[clojure.test :refer [is deftest run-tests]])",
403 &mut env,
404 )
405 .unwrap();
406 let v = run("(is (= 1 1))", &mut env).unwrap();
407 assert_eq!(v, Value::Bool(true));
408 })
409 .unwrap()
410 .join()
411 .unwrap();
412 }
413}