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(feature = "prebuild-ir")]
109static PREBUILT_IR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/core_ir.bin"));
110
111#[cfg(feature = "prebuild-ir")]
115static PREBUILT_COMPILER_IR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/compiler_ir.bin"));
116
117#[cfg(feature = "prebuild-ir")]
125fn load_prebuilt_compiler_ir(globals: &Arc<GlobalEnv>) {
126 match cljrs_ir::deserialize_bundle(PREBUILT_COMPILER_IR) {
127 Ok(bundle) if !bundle.is_empty() => {
128 let n = cljrs_eval::load_prebuilt_ir(globals, &bundle);
129 cljrs_logging::feat_debug!(
130 "ir",
131 "loaded {n} prebuilt compiler IR arities from bundle ({} entries)",
132 bundle.len()
133 );
134 }
135 Ok(_) => {}
136 Err(e) => {
137 cljrs_logging::feat_debug!("ir", "compiler IR bundle deserialize failed: {e}");
138 }
139 }
140}
141
142#[cfg(not(feature = "prebuild-ir"))]
143fn load_prebuilt_compiler_ir(_globals: &Arc<GlobalEnv>) {}
144
145#[cfg(not(target_arch = "wasm32"))]
157pub fn standard_env_no_ir() -> Arc<GlobalEnv> {
158 let globals = cljrs_eval::standard_env_minimal_no_ir();
159 register(&globals);
160
161 cljrs_gc::HEAP.set_config_from_env();
162 let roots_gc = globals.clone();
163 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
164 use cljrs_gc::GcVisitor as _;
165 let namespaces = roots_gc.namespaces.read().unwrap();
166 for (_name, ns_ptr) in namespaces.iter() {
167 visitor.visit(ns_ptr);
168 }
169 });
170
171 globals
172}
173
174#[cfg(not(target_arch = "wasm32"))]
180pub fn standard_env() -> Arc<GlobalEnv> {
181 let globals = cljrs_eval::standard_env_minimal();
182 register(&globals);
183
184 cljrs_gc::HEAP.set_config_from_env();
188 let roots_gc = globals.clone();
189 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
190 use cljrs_gc::GcVisitor as _;
191 let namespaces = roots_gc.namespaces.read().unwrap();
192 for (_name, ns_ptr) in namespaces.iter() {
193 visitor.visit(ns_ptr);
194 }
195 });
196
197 #[cfg(feature = "prebuild-ir")]
199 {
200 match cljrs_ir::deserialize_bundle(PREBUILT_IR) {
201 Ok(bundle) if !bundle.is_empty() => {
202 cljrs_eval::register_compiler_sources(&globals);
205
206 let loaded = cljrs_eval::load_prebuilt_ir(&globals, &bundle);
207 cljrs_logging::feat_debug!(
208 "ir",
209 "loaded {loaded} prebuilt IR arities from bundle ({} entries)",
210 bundle.len()
211 );
212
213 let g = globals.clone();
218 let _ = std::thread::Builder::new()
219 .stack_size(16 * 1024 * 1024)
220 .spawn(move || {
221 let mut env = cljrs_eval::Env::new(g.clone(), "user");
222 if cljrs_eval::ensure_compiler_loaded(&g, &mut env) {
223 load_prebuilt_compiler_ir(&g);
224 }
225 });
226
227 return globals;
228 }
229 _ => {
230 }
232 }
233 }
234
235 cljrs_eval::register_compiler_sources(&globals);
239 {
240 let g = globals.clone();
241 let _ = std::thread::Builder::new()
242 .stack_size(16 * 1024 * 1024)
243 .spawn(move || {
244 let mut env = cljrs_eval::Env::new(g.clone(), "user");
245 if cljrs_eval::ensure_compiler_loaded(&g, &mut env) {
246 load_prebuilt_compiler_ir(&g);
247 }
248 })
249 .and_then(|h| h.join().map_err(|_| std::io::Error::other("join failed")));
250 }
251
252 globals
253}
254
255#[cfg(not(target_arch = "wasm32"))]
257pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
258 let globals = standard_env();
259 globals.set_source_paths(source_paths);
260 globals
261}
262
263#[cfg(not(target_arch = "wasm32"))]
265pub fn standard_env_with_paths_and_config(
266 source_paths: Vec<std::path::PathBuf>,
267 gc_config: Arc<GcConfig>,
268) -> Arc<GlobalEnv> {
269 let globals = standard_env();
270 globals.set_source_paths(source_paths);
271 globals.set_gc_config(gc_config.clone());
272 cljrs_gc::HEAP.set_config(gc_config);
275 globals
276}
277
278#[cfg(test)]
281mod tests {
282 use super::*;
283 use cljrs_eval::{Env, EvalResult, eval};
284 use cljrs_reader::Parser;
285 use cljrs_value::Value;
286
287 fn make_env() -> (Arc<GlobalEnv>, Env) {
288 let globals = standard_env();
289 let env = Env::new(globals.clone(), "user");
290 (globals, env)
291 }
292
293 #[allow(clippy::result_large_err)]
294 fn run(src: &str, env: &mut Env) -> EvalResult {
295 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
296 let forms = parser.parse_all().expect("parse error");
297 let mut result = Value::Nil;
298 for form in forms {
299 result = eval(&form, env)?;
300 }
301 Ok(result)
302 }
303
304 #[test]
307 fn test_string_upper_lower() {
308 let (_, mut env) = make_env();
309 run("(require '[clojure.string :as str])", &mut env).unwrap();
310 assert_eq!(
311 run("(str/upper-case \"hello\")", &mut env).unwrap(),
312 Value::string("HELLO")
313 );
314 assert_eq!(
315 run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
316 Value::string("world")
317 );
318 }
319
320 #[test]
321 fn test_string_trim() {
322 let (_, mut env) = make_env();
323 run("(require '[clojure.string :as str])", &mut env).unwrap();
324 assert_eq!(
325 run("(str/trim \" hello \")", &mut env).unwrap(),
326 Value::string("hello")
327 );
328 assert_eq!(
329 run("(str/triml \" hi\")", &mut env).unwrap(),
330 Value::string("hi")
331 );
332 assert_eq!(
333 run("(str/trimr \"hi \")", &mut env).unwrap(),
334 Value::string("hi")
335 );
336 }
337
338 #[test]
339 fn test_string_predicates() {
340 let (_, mut env) = make_env();
341 run("(require '[clojure.string :as str])", &mut env).unwrap();
342 assert_eq!(
343 run("(str/blank? \" \")", &mut env).unwrap(),
344 Value::Bool(true)
345 );
346 assert_eq!(
347 run("(str/blank? \"x\")", &mut env).unwrap(),
348 Value::Bool(false)
349 );
350 assert_eq!(
351 run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
352 Value::Bool(true)
353 );
354 assert_eq!(
355 run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
356 Value::Bool(true)
357 );
358 assert_eq!(
359 run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
360 Value::Bool(true)
361 );
362 }
363
364 #[test]
365 fn test_string_replace() {
366 let (_, mut env) = make_env();
367 run("(require '[clojure.string :as str])", &mut env).unwrap();
368 assert_eq!(
369 run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
370 Value::string("aaXXcc")
371 );
372 assert_eq!(
373 run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
374 Value::string("Xabbcc")
375 );
376 }
377
378 #[test]
379 fn test_string_split_join() {
380 let (_, mut env) = make_env();
381 run("(require '[clojure.string :as str])", &mut env).unwrap();
382 let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
383 assert!(matches!(v, Value::Vector(_)));
384 assert_eq!(
385 run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
386 Value::string("a-b-c")
387 );
388 }
389
390 #[test]
391 fn test_string_capitalize() {
392 let (_, mut env) = make_env();
393 run("(require '[clojure.string :as str])", &mut env).unwrap();
394 assert_eq!(
395 run("(str/capitalize \"hello world\")", &mut env).unwrap(),
396 Value::string("Hello world")
397 );
398 }
399
400 #[test]
401 fn test_string_split_lines() {
402 let (_, mut env) = make_env();
403 run("(require '[clojure.string :as str])", &mut env).unwrap();
404 let v = run("(str/split-lines \"a\\nb\\nc\")", &mut env).unwrap();
405 assert!(matches!(v, Value::Vector(_)));
406 }
407
408 #[test]
411 fn test_set_union() {
412 let (_, mut env) = make_env();
413 run("(require '[clojure.set :as s])", &mut env).unwrap();
414 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
415 match v {
416 Value::Set(s) => assert_eq!(s.count(), 3),
417 other => panic!("expected set, got {other:?}"),
418 }
419 }
420
421 #[test]
422 fn test_set_intersection() {
423 let (_, mut env) = make_env();
424 run("(require '[clojure.set :as s])", &mut env).unwrap();
425 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
426 match v {
427 Value::Set(s) => assert_eq!(s.count(), 2),
428 other => panic!("expected set, got {other:?}"),
429 }
430 }
431
432 #[test]
433 fn test_set_difference() {
434 let (_, mut env) = make_env();
435 run("(require '[clojure.set :as s])", &mut env).unwrap();
436 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
437 match v {
438 Value::Set(s) => assert_eq!(s.count(), 1),
439 other => panic!("expected set, got {other:?}"),
440 }
441 }
442
443 #[test]
444 fn test_set_subset_superset() {
445 let (_, mut env) = make_env();
446 run("(require '[clojure.set :as s])", &mut env).unwrap();
447 assert_eq!(
448 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
449 Value::Bool(true)
450 );
451 assert_eq!(
452 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
453 Value::Bool(true)
454 );
455 }
456
457 #[test]
458 fn test_set_map_invert() {
459 let (_, mut env) = make_env();
460 run("(require '[clojure.set :as s])", &mut env).unwrap();
461 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
462 assert!(matches!(v, Value::Map(_)));
463 }
464
465 #[test]
468 fn test_clojure_test_lazy_load() {
469 std::thread::Builder::new()
473 .stack_size(16 * 1024 * 1024)
474 .spawn(|| {
475 let (_, mut env) = make_env();
476 run(
479 "(require '[clojure.test :refer [is deftest run-tests]])",
480 &mut env,
481 )
482 .unwrap();
483 let v = run("(is (= 1 1))", &mut env).unwrap();
484 assert_eq!(v, Value::Bool(true));
485 })
486 .unwrap()
487 .join()
488 .unwrap();
489 }
490}