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 g = globals.clone();
163 let _ = std::thread::Builder::new()
164 .stack_size(16 * 1024 * 1024)
165 .spawn(move || {
166 let mut env = cljrs_eval::Env::new(g.clone(), "user");
167 cljrs_eval::ensure_compiler_loaded(&g, &mut env);
168 })
169 .and_then(|h| h.join().map_err(|_| std::io::Error::other("join failed")));
170 }
171
172 globals
173}
174
175#[cfg(not(target_arch = "wasm32"))]
177pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
178 let globals = standard_env();
179 globals.set_source_paths(source_paths);
180 globals
181}
182
183#[cfg(not(target_arch = "wasm32"))]
185pub fn standard_env_with_paths_and_config(
186 source_paths: Vec<std::path::PathBuf>,
187 gc_config: Arc<GcConfig>,
188) -> Arc<GlobalEnv> {
189 let globals = standard_env();
190 globals.set_source_paths(source_paths);
191 globals.set_gc_config(gc_config.clone());
192 cljrs_gc::HEAP.set_config(gc_config);
195 globals
196}
197
198#[cfg(test)]
201mod tests {
202 use super::*;
203 use cljrs_eval::{Env, EvalResult, eval};
204 use cljrs_reader::Parser;
205 use cljrs_value::Value;
206
207 fn make_env() -> (Arc<GlobalEnv>, Env) {
208 let globals = standard_env();
209 let env = Env::new(globals.clone(), "user");
210 (globals, env)
211 }
212
213 #[allow(clippy::result_large_err)]
214 fn run(src: &str, env: &mut Env) -> EvalResult {
215 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
216 let forms = parser.parse_all().expect("parse error");
217 let mut result = Value::Nil;
218 for form in forms {
219 result = eval(&form, env)?;
220 }
221 Ok(result)
222 }
223
224 #[test]
227 fn test_string_upper_lower() {
228 let (_, mut env) = make_env();
229 run("(require '[clojure.string :as str])", &mut env).unwrap();
230 assert_eq!(
231 run("(str/upper-case \"hello\")", &mut env).unwrap(),
232 Value::string("HELLO")
233 );
234 assert_eq!(
235 run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
236 Value::string("world")
237 );
238 }
239
240 #[test]
241 fn test_string_trim() {
242 let (_, mut env) = make_env();
243 run("(require '[clojure.string :as str])", &mut env).unwrap();
244 assert_eq!(
245 run("(str/trim \" hello \")", &mut env).unwrap(),
246 Value::string("hello")
247 );
248 assert_eq!(
249 run("(str/triml \" hi\")", &mut env).unwrap(),
250 Value::string("hi")
251 );
252 assert_eq!(
253 run("(str/trimr \"hi \")", &mut env).unwrap(),
254 Value::string("hi")
255 );
256 }
257
258 #[test]
259 fn test_string_predicates() {
260 let (_, mut env) = make_env();
261 run("(require '[clojure.string :as str])", &mut env).unwrap();
262 assert_eq!(
263 run("(str/blank? \" \")", &mut env).unwrap(),
264 Value::Bool(true)
265 );
266 assert_eq!(
267 run("(str/blank? \"x\")", &mut env).unwrap(),
268 Value::Bool(false)
269 );
270 assert_eq!(
271 run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
272 Value::Bool(true)
273 );
274 assert_eq!(
275 run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
276 Value::Bool(true)
277 );
278 assert_eq!(
279 run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
280 Value::Bool(true)
281 );
282 }
283
284 #[test]
285 fn test_string_replace() {
286 let (_, mut env) = make_env();
287 run("(require '[clojure.string :as str])", &mut env).unwrap();
288 assert_eq!(
289 run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
290 Value::string("aaXXcc")
291 );
292 assert_eq!(
293 run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
294 Value::string("Xabbcc")
295 );
296 }
297
298 #[test]
299 fn test_string_split_join() {
300 let (_, mut env) = make_env();
301 run("(require '[clojure.string :as str])", &mut env).unwrap();
302 let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
303 assert!(matches!(v, Value::Vector(_)));
304 assert_eq!(
305 run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
306 Value::string("a-b-c")
307 );
308 }
309
310 #[test]
311 fn test_string_capitalize() {
312 let (_, mut env) = make_env();
313 run("(require '[clojure.string :as str])", &mut env).unwrap();
314 assert_eq!(
315 run("(str/capitalize \"hello world\")", &mut env).unwrap(),
316 Value::string("Hello world")
317 );
318 }
319
320 #[test]
321 fn test_string_split_lines() {
322 let (_, mut env) = make_env();
323 run("(require '[clojure.string :as str])", &mut env).unwrap();
324 let v = run("(str/split-lines \"a\\nb\\nc\")", &mut env).unwrap();
325 assert!(matches!(v, Value::Vector(_)));
326 }
327
328 #[test]
331 fn test_set_union() {
332 let (_, mut env) = make_env();
333 run("(require '[clojure.set :as s])", &mut env).unwrap();
334 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
335 match v {
336 Value::Set(s) => assert_eq!(s.count(), 3),
337 other => panic!("expected set, got {other:?}"),
338 }
339 }
340
341 #[test]
342 fn test_set_intersection() {
343 let (_, mut env) = make_env();
344 run("(require '[clojure.set :as s])", &mut env).unwrap();
345 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
346 match v {
347 Value::Set(s) => assert_eq!(s.count(), 2),
348 other => panic!("expected set, got {other:?}"),
349 }
350 }
351
352 #[test]
353 fn test_set_difference() {
354 let (_, mut env) = make_env();
355 run("(require '[clojure.set :as s])", &mut env).unwrap();
356 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
357 match v {
358 Value::Set(s) => assert_eq!(s.count(), 1),
359 other => panic!("expected set, got {other:?}"),
360 }
361 }
362
363 #[test]
364 fn test_set_subset_superset() {
365 let (_, mut env) = make_env();
366 run("(require '[clojure.set :as s])", &mut env).unwrap();
367 assert_eq!(
368 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
369 Value::Bool(true)
370 );
371 assert_eq!(
372 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
373 Value::Bool(true)
374 );
375 }
376
377 #[test]
378 fn test_set_map_invert() {
379 let (_, mut env) = make_env();
380 run("(require '[clojure.set :as s])", &mut env).unwrap();
381 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
382 assert!(matches!(v, Value::Map(_)));
383 }
384
385 #[test]
388 fn test_clojure_test_lazy_load() {
389 std::thread::Builder::new()
393 .stack_size(16 * 1024 * 1024)
394 .spawn(|| {
395 let (_, mut env) = make_env();
396 run(
399 "(require '[clojure.test :refer [is deftest run-tests]])",
400 &mut env,
401 )
402 .unwrap();
403 let v = run("(is (= 1 1))", &mut env).unwrap();
404 assert_eq!(v, Value::Bool(true));
405 })
406 .unwrap()
407 .join()
408 .unwrap();
409 }
410}