1use std::sync::Arc;
14
15use cljrs_eval::GlobalEnv;
16use cljrs_gc::GcConfig;
17
18mod core_async;
19mod edn;
20pub mod io;
21mod set;
22mod string;
23const CLOJURE_TEST_SRC: &str = include_str!("clojure/test.cljrs");
26const CLOJURE_STRING_SRC: &str = include_str!("clojure/string.cljrs");
27const CLOJURE_SET_SRC: &str = include_str!("clojure/set.cljrs");
28const CLOJURE_TEMPLATE_SRC: &str = include_str!("clojure/template.cljrs");
29const CLOJURE_RUST_IO_SRC: &str = include_str!("clojure/rust/io.cljrs");
30const CLOJURE_EDN_SRC: &str = include_str!("clojure/edn.cljrs");
31const CLOJURE_WALK_SRC: &str = include_str!("clojure/walk.cljrs");
32const CLOJURE_DATA_SRC: &str = include_str!("clojure/data.cljrs");
33const COLJURE_ZIP_SRC: &str = include_str!("clojure/zip.cljrs");
34
35macro_rules! register_fns {
40 ($globals:expr, $ns:expr, [ $( ($name:expr, $arity:expr, $func:expr) ),* $(,)? ]) => {{
41 use cljrs_gc::GcPtr;
42 use cljrs_value::{NativeFn, Value};
43 let ns: &str = $ns;
44 $(
45 {
46 let nf = NativeFn::new($name, $arity, $func);
47 $globals.intern(ns, std::sync::Arc::from($name), Value::NativeFunction(GcPtr::new(nf)));
48 }
49 )*
50 }};
51}
52
53pub(crate) use register_fns;
54
55pub fn register(globals: &Arc<GlobalEnv>) {
64 string::register(globals, "clojure.string");
67 globals.register_builtin_source("clojure.string", CLOJURE_STRING_SRC);
68
69 set::register(globals, "clojure.set");
71 globals.register_builtin_source("clojure.set", CLOJURE_SET_SRC);
72
73 globals.register_builtin_source("clojure.template", CLOJURE_TEMPLATE_SRC);
75
76 globals.register_builtin_source("clojure.test", CLOJURE_TEST_SRC);
78
79 io::register(globals, "clojure.rust.io");
81 globals.register_builtin_source("clojure.rust.io", CLOJURE_RUST_IO_SRC);
82
83 edn::register(globals, "clojure.edn");
85 globals.register_builtin_source("clojure.edn", CLOJURE_EDN_SRC);
86
87 globals.register_builtin_source("clojure.walk", CLOJURE_WALK_SRC);
89
90 globals.register_builtin_source("clojure.data", CLOJURE_DATA_SRC);
92
93 globals.register_builtin_source("clojure.zip", COLJURE_ZIP_SRC);
95}
96
97#[cfg(feature = "prebuild-ir")]
101static PREBUILT_IR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/core_ir.bin"));
102
103#[cfg(feature = "prebuild-ir")]
107static PREBUILT_COMPILER_IR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/compiler_ir.bin"));
108
109#[cfg(feature = "prebuild-ir")]
117fn load_prebuilt_compiler_ir(globals: &Arc<GlobalEnv>) {
118 match cljrs_ir::deserialize_bundle(PREBUILT_COMPILER_IR) {
119 Ok(bundle) if !bundle.is_empty() => {
120 let n = cljrs_eval::load_prebuilt_ir(globals, &bundle);
121 cljrs_logging::feat_debug!(
122 "ir",
123 "loaded {n} prebuilt compiler IR arities from bundle ({} entries)",
124 bundle.len()
125 );
126 }
127 Ok(_) => {}
128 Err(e) => {
129 cljrs_logging::feat_debug!("ir", "compiler IR bundle deserialize failed: {e}");
130 }
131 }
132}
133
134#[cfg(not(feature = "prebuild-ir"))]
135fn load_prebuilt_compiler_ir(_globals: &Arc<GlobalEnv>) {}
136
137pub fn standard_env() -> Arc<GlobalEnv> {
143 let globals = cljrs_eval::standard_env_minimal();
144 register(&globals);
145
146 #[cfg(feature = "prebuild-ir")]
148 {
149 match cljrs_ir::deserialize_bundle(PREBUILT_IR) {
150 Ok(bundle) if !bundle.is_empty() => {
151 cljrs_eval::register_compiler_sources(&globals);
154
155 let loaded = cljrs_eval::load_prebuilt_ir(&globals, &bundle);
156 cljrs_logging::feat_debug!(
157 "ir",
158 "loaded {loaded} prebuilt IR arities from bundle ({} entries)",
159 bundle.len()
160 );
161
162 let g = globals.clone();
167 let _ = std::thread::Builder::new()
168 .stack_size(16 * 1024 * 1024)
169 .spawn(move || {
170 let mut env = cljrs_eval::Env::new(g.clone(), "user");
171 if cljrs_eval::ensure_compiler_loaded(&g, &mut env) {
172 load_prebuilt_compiler_ir(&g);
173 }
174 });
175
176 return globals;
177 }
178 _ => {
179 }
181 }
182 }
183
184 cljrs_eval::register_compiler_sources(&globals);
188 {
189 let g = globals.clone();
190 let _ = std::thread::Builder::new()
191 .stack_size(16 * 1024 * 1024)
192 .spawn(move || {
193 let mut env = cljrs_eval::Env::new(g.clone(), "user");
194 if cljrs_eval::ensure_compiler_loaded(&g, &mut env) {
195 load_prebuilt_compiler_ir(&g);
196 }
197 })
198 .and_then(|h| h.join().map_err(|_| std::io::Error::other("join failed")));
199 }
200
201 globals
202}
203
204pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
206 let globals = standard_env();
207 globals.set_source_paths(source_paths);
208 globals
209}
210
211pub fn standard_env_with_paths_and_config(
213 source_paths: Vec<std::path::PathBuf>,
214 gc_config: Arc<GcConfig>,
215) -> Arc<GlobalEnv> {
216 let globals = standard_env();
217 globals.set_source_paths(source_paths);
218 globals.set_gc_config(gc_config.clone());
219 cljrs_gc::HEAP.set_config(gc_config);
221 let roots_globals = globals.clone();
224 cljrs_gc::HEAP.register_root_tracer(move |visitor| {
225 use cljrs_gc::GcVisitor as _;
226 let namespaces = roots_globals.namespaces.read().unwrap();
227 for (_name, ns_ptr) in namespaces.iter() {
228 visitor.visit(ns_ptr);
229 }
230 });
231 globals
232}
233
234#[cfg(test)]
237mod tests {
238 use super::*;
239 use cljrs_eval::{Env, EvalResult, eval};
240 use cljrs_reader::Parser;
241 use cljrs_value::Value;
242
243 fn make_env() -> (Arc<GlobalEnv>, Env) {
244 let globals = standard_env();
245 let env = Env::new(globals.clone(), "user");
246 (globals, env)
247 }
248
249 #[allow(clippy::result_large_err)]
250 fn run(src: &str, env: &mut Env) -> EvalResult {
251 let mut parser = Parser::new(src.to_string(), "<test>".to_string());
252 let forms = parser.parse_all().expect("parse error");
253 let mut result = Value::Nil;
254 for form in forms {
255 result = eval(&form, env)?;
256 }
257 Ok(result)
258 }
259
260 #[test]
263 fn test_string_upper_lower() {
264 let (_, mut env) = make_env();
265 run("(require '[clojure.string :as str])", &mut env).unwrap();
266 assert_eq!(
267 run("(str/upper-case \"hello\")", &mut env).unwrap(),
268 Value::string("HELLO")
269 );
270 assert_eq!(
271 run("(str/lower-case \"WORLD\")", &mut env).unwrap(),
272 Value::string("world")
273 );
274 }
275
276 #[test]
277 fn test_string_trim() {
278 let (_, mut env) = make_env();
279 run("(require '[clojure.string :as str])", &mut env).unwrap();
280 assert_eq!(
281 run("(str/trim \" hello \")", &mut env).unwrap(),
282 Value::string("hello")
283 );
284 assert_eq!(
285 run("(str/triml \" hi\")", &mut env).unwrap(),
286 Value::string("hi")
287 );
288 assert_eq!(
289 run("(str/trimr \"hi \")", &mut env).unwrap(),
290 Value::string("hi")
291 );
292 }
293
294 #[test]
295 fn test_string_predicates() {
296 let (_, mut env) = make_env();
297 run("(require '[clojure.string :as str])", &mut env).unwrap();
298 assert_eq!(
299 run("(str/blank? \" \")", &mut env).unwrap(),
300 Value::Bool(true)
301 );
302 assert_eq!(
303 run("(str/blank? \"x\")", &mut env).unwrap(),
304 Value::Bool(false)
305 );
306 assert_eq!(
307 run("(str/starts-with? \"hello\" \"hel\")", &mut env).unwrap(),
308 Value::Bool(true)
309 );
310 assert_eq!(
311 run("(str/ends-with? \"hello\" \"llo\")", &mut env).unwrap(),
312 Value::Bool(true)
313 );
314 assert_eq!(
315 run("(str/includes? \"hello\" \"ell\")", &mut env).unwrap(),
316 Value::Bool(true)
317 );
318 }
319
320 #[test]
321 fn test_string_replace() {
322 let (_, mut env) = make_env();
323 run("(require '[clojure.string :as str])", &mut env).unwrap();
324 assert_eq!(
325 run("(str/replace \"aabbcc\" \"bb\" \"XX\")", &mut env).unwrap(),
326 Value::string("aaXXcc")
327 );
328 assert_eq!(
329 run("(str/replace-first \"aabbcc\" \"a\" \"X\")", &mut env).unwrap(),
330 Value::string("Xabbcc")
331 );
332 }
333
334 #[test]
335 fn test_string_split_join() {
336 let (_, mut env) = make_env();
337 run("(require '[clojure.string :as str])", &mut env).unwrap();
338 let v = run("(str/split \"a,b,c\" \",\")", &mut env).unwrap();
339 assert!(matches!(v, Value::Vector(_)));
340 assert_eq!(
341 run("(str/join \"-\" [\"a\" \"b\" \"c\"])", &mut env).unwrap(),
342 Value::string("a-b-c")
343 );
344 }
345
346 #[test]
347 fn test_string_capitalize() {
348 let (_, mut env) = make_env();
349 run("(require '[clojure.string :as str])", &mut env).unwrap();
350 assert_eq!(
351 run("(str/capitalize \"hello world\")", &mut env).unwrap(),
352 Value::string("Hello world")
353 );
354 }
355
356 #[test]
357 fn test_string_split_lines() {
358 let (_, mut env) = make_env();
359 run("(require '[clojure.string :as str])", &mut env).unwrap();
360 let v = run("(str/split-lines \"a\\nb\\nc\")", &mut env).unwrap();
361 assert!(matches!(v, Value::Vector(_)));
362 }
363
364 #[test]
367 fn test_set_union() {
368 let (_, mut env) = make_env();
369 run("(require '[clojure.set :as s])", &mut env).unwrap();
370 let v = run("(s/union #{1 2} #{2 3})", &mut env).unwrap();
371 match v {
372 Value::Set(s) => assert_eq!(s.count(), 3),
373 other => panic!("expected set, got {other:?}"),
374 }
375 }
376
377 #[test]
378 fn test_set_intersection() {
379 let (_, mut env) = make_env();
380 run("(require '[clojure.set :as s])", &mut env).unwrap();
381 let v = run("(s/intersection #{1 2 3} #{2 3 4})", &mut env).unwrap();
382 match v {
383 Value::Set(s) => assert_eq!(s.count(), 2),
384 other => panic!("expected set, got {other:?}"),
385 }
386 }
387
388 #[test]
389 fn test_set_difference() {
390 let (_, mut env) = make_env();
391 run("(require '[clojure.set :as s])", &mut env).unwrap();
392 let v = run("(s/difference #{1 2 3} #{2 3})", &mut env).unwrap();
393 match v {
394 Value::Set(s) => assert_eq!(s.count(), 1),
395 other => panic!("expected set, got {other:?}"),
396 }
397 }
398
399 #[test]
400 fn test_set_subset_superset() {
401 let (_, mut env) = make_env();
402 run("(require '[clojure.set :as s])", &mut env).unwrap();
403 assert_eq!(
404 run("(s/subset? #{1 2} #{1 2 3})", &mut env).unwrap(),
405 Value::Bool(true)
406 );
407 assert_eq!(
408 run("(s/superset? #{1 2 3} #{1 2})", &mut env).unwrap(),
409 Value::Bool(true)
410 );
411 }
412
413 #[test]
414 fn test_set_map_invert() {
415 let (_, mut env) = make_env();
416 run("(require '[clojure.set :as s])", &mut env).unwrap();
417 let v = run("(s/map-invert {:a 1 :b 2})", &mut env).unwrap();
418 assert!(matches!(v, Value::Map(_)));
419 }
420
421 #[test]
424 fn test_clojure_test_lazy_load() {
425 std::thread::Builder::new()
429 .stack_size(16 * 1024 * 1024)
430 .spawn(|| {
431 let (_, mut env) = make_env();
432 run(
435 "(require '[clojure.test :refer [is deftest run-tests]])",
436 &mut env,
437 )
438 .unwrap();
439 let v = run("(is (= 1 1))", &mut env).unwrap();
440 assert_eq!(v, Value::Bool(true));
441 })
442 .unwrap()
443 .join()
444 .unwrap();
445 }
446}