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"))]
151pub fn standard_env() -> Arc<GlobalEnv> {
152 let globals = cljrs_eval::standard_env_minimal();
153 register(&globals);
154
155 #[cfg(feature = "prebuild-ir")]
157 {
158 match cljrs_ir::deserialize_bundle(PREBUILT_IR) {
159 Ok(bundle) if !bundle.is_empty() => {
160 cljrs_eval::register_compiler_sources(&globals);
163
164 let loaded = cljrs_eval::load_prebuilt_ir(&globals, &bundle);
165 cljrs_logging::feat_debug!(
166 "ir",
167 "loaded {loaded} prebuilt IR arities from bundle ({} entries)",
168 bundle.len()
169 );
170
171 let g = globals.clone();
176 let _ = std::thread::Builder::new()
177 .stack_size(16 * 1024 * 1024)
178 .spawn(move || {
179 let mut env = cljrs_eval::Env::new(g.clone(), "user");
180 if cljrs_eval::ensure_compiler_loaded(&g, &mut env) {
181 load_prebuilt_compiler_ir(&g);
182 }
183 });
184
185 return globals;
186 }
187 _ => {
188 }
190 }
191 }
192
193 cljrs_eval::register_compiler_sources(&globals);
197 {
198 let g = globals.clone();
199 let _ = std::thread::Builder::new()
200 .stack_size(16 * 1024 * 1024)
201 .spawn(move || {
202 let mut env = cljrs_eval::Env::new(g.clone(), "user");
203 if cljrs_eval::ensure_compiler_loaded(&g, &mut env) {
204 load_prebuilt_compiler_ir(&g);
205 }
206 })
207 .and_then(|h| h.join().map_err(|_| std::io::Error::other("join failed")));
208 }
209
210 globals
211}
212
213#[cfg(not(target_arch = "wasm32"))]
215pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
216 let globals = standard_env();
217 globals.set_source_paths(source_paths);
218 globals
219}
220
221#[cfg(not(target_arch = "wasm32"))]
223pub fn standard_env_with_paths_and_config(
224 source_paths: Vec<std::path::PathBuf>,
225 gc_config: Arc<GcConfig>,
226) -> Arc<GlobalEnv> {
227 let globals = standard_env();
228 globals.set_source_paths(source_paths);
229 globals.set_gc_config(gc_config.clone());
230 cljrs_gc::HEAP.set_config(gc_config);
232 let roots_globals = globals.clone();
235 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
236 use cljrs_gc::GcVisitor as _;
237 let namespaces = roots_globals.namespaces.read().unwrap();
238 for (_name, ns_ptr) in namespaces.iter() {
239 visitor.visit(ns_ptr);
240 }
241 });
242 globals
243}
244
245#[cfg(test)]
248mod tests {
249 use super::*;
250 use cljrs_eval::{Env, EvalResult, eval};
251 use cljrs_reader::Parser;
252 use cljrs_value::Value;
253
254 fn make_env() -> (Arc<GlobalEnv>, Env) {
255 let globals = standard_env();
256 let env = Env::new(globals.clone(), "user");
257 (globals, env)
258 }
259
260 #[allow(clippy::result_large_err)]
261 fn run(src: &str, env: &mut Env) -> EvalResult {
262 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
263 let forms = parser.parse_all().expect("parse error");
264 let mut result = Value::Nil;
265 for form in forms {
266 result = eval(&form, env)?;
267 }
268 Ok(result)
269 }
270
271 #[test]
274 fn test_string_upper_lower() {
275 let (_, mut env) = make_env();
276 run("(require '[clojure.string :as str])", &mut env).unwrap();
277 assert_eq!(
278 run("(str/upper-case \"hello\")", &mut env).unwrap(),
279 Value::string("HELLO")
280 );
281 assert_eq!(
282 run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
283 Value::string("world")
284 );
285 }
286
287 #[test]
288 fn test_string_trim() {
289 let (_, mut env) = make_env();
290 run("(require '[clojure.string :as str])", &mut env).unwrap();
291 assert_eq!(
292 run("(str/trim \" hello \")", &mut env).unwrap(),
293 Value::string("hello")
294 );
295 assert_eq!(
296 run("(str/triml \" hi\")", &mut env).unwrap(),
297 Value::string("hi")
298 );
299 assert_eq!(
300 run("(str/trimr \"hi \")", &mut env).unwrap(),
301 Value::string("hi")
302 );
303 }
304
305 #[test]
306 fn test_string_predicates() {
307 let (_, mut env) = make_env();
308 run("(require '[clojure.string :as str])", &mut env).unwrap();
309 assert_eq!(
310 run("(str/blank? \" \")", &mut env).unwrap(),
311 Value::Bool(true)
312 );
313 assert_eq!(
314 run("(str/blank? \"x\")", &mut env).unwrap(),
315 Value::Bool(false)
316 );
317 assert_eq!(
318 run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
319 Value::Bool(true)
320 );
321 assert_eq!(
322 run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
323 Value::Bool(true)
324 );
325 assert_eq!(
326 run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
327 Value::Bool(true)
328 );
329 }
330
331 #[test]
332 fn test_string_replace() {
333 let (_, mut env) = make_env();
334 run("(require '[clojure.string :as str])", &mut env).unwrap();
335 assert_eq!(
336 run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
337 Value::string("aaXXcc")
338 );
339 assert_eq!(
340 run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
341 Value::string("Xabbcc")
342 );
343 }
344
345 #[test]
346 fn test_string_split_join() {
347 let (_, mut env) = make_env();
348 run("(require '[clojure.string :as str])", &mut env).unwrap();
349 let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
350 assert!(matches!(v, Value::Vector(_)));
351 assert_eq!(
352 run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
353 Value::string("a-b-c")
354 );
355 }
356
357 #[test]
358 fn test_string_capitalize() {
359 let (_, mut env) = make_env();
360 run("(require '[clojure.string :as str])", &mut env).unwrap();
361 assert_eq!(
362 run("(str/capitalize \"hello world\")", &mut env).unwrap(),
363 Value::string("Hello world")
364 );
365 }
366
367 #[test]
368 fn test_string_split_lines() {
369 let (_, mut env) = make_env();
370 run("(require '[clojure.string :as str])", &mut env).unwrap();
371 let v = run("(str/split-lines \"a\\nb\\nc\")", &mut env).unwrap();
372 assert!(matches!(v, Value::Vector(_)));
373 }
374
375 #[test]
378 fn test_set_union() {
379 let (_, mut env) = make_env();
380 run("(require '[clojure.set :as s])", &mut env).unwrap();
381 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
382 match v {
383 Value::Set(s) => assert_eq!(s.count(), 3),
384 other => panic!("expected set, got {other:?}"),
385 }
386 }
387
388 #[test]
389 fn test_set_intersection() {
390 let (_, mut env) = make_env();
391 run("(require '[clojure.set :as s])", &mut env).unwrap();
392 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
393 match v {
394 Value::Set(s) => assert_eq!(s.count(), 2),
395 other => panic!("expected set, got {other:?}"),
396 }
397 }
398
399 #[test]
400 fn test_set_difference() {
401 let (_, mut env) = make_env();
402 run("(require '[clojure.set :as s])", &mut env).unwrap();
403 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
404 match v {
405 Value::Set(s) => assert_eq!(s.count(), 1),
406 other => panic!("expected set, got {other:?}"),
407 }
408 }
409
410 #[test]
411 fn test_set_subset_superset() {
412 let (_, mut env) = make_env();
413 run("(require '[clojure.set :as s])", &mut env).unwrap();
414 assert_eq!(
415 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
416 Value::Bool(true)
417 );
418 assert_eq!(
419 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
420 Value::Bool(true)
421 );
422 }
423
424 #[test]
425 fn test_set_map_invert() {
426 let (_, mut env) = make_env();
427 run("(require '[clojure.set :as s])", &mut env).unwrap();
428 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
429 assert!(matches!(v, Value::Map(_)));
430 }
431
432 #[test]
435 fn test_clojure_test_lazy_load() {
436 std::thread::Builder::new()
440 .stack_size(16 * 1024 * 1024)
441 .spawn(|| {
442 let (_, mut env) = make_env();
443 run(
446 "(require '[clojure.test :refer [is deftest run-tests]])",
447 &mut env,
448 )
449 .unwrap();
450 let v = run("(is (= 1 1))", &mut env).unwrap();
451 assert_eq!(v, Value::Bool(true));
452 })
453 .unwrap()
454 .join()
455 .unwrap();
456 }
457}