Skip to main content

cljrs_interp/
lib.rs

1#![allow(clippy::result_large_err)]
2#![allow(clippy::type_complexity)]
3
4use cljrs_builtins::builtins;
5use cljrs_env::env::{Env, GlobalEnv};
6use cljrs_env::error::EvalResult;
7use cljrs_reader::Form;
8use cljrs_value::{CljxFn, Value};
9use std::sync::Arc;
10
11pub mod apply;
12mod arity;
13pub mod destructure;
14pub mod eval;
15pub mod macros;
16pub mod special;
17pub mod syntax_quote;
18#[cfg(not(target_arch = "wasm32"))]
19pub mod versioned;
20mod virtualize;
21
22/// Create a minimal `GlobalEnv` with `clojure.core` builtins and bootstrap
23/// HOFs, but without any stdlib namespaces pre-loaded.
24///
25/// Used by `cljrs-stdlib` as a foundation; also useful for lightweight tests
26/// that don't need stdlib.  Call [`standard_env`] for a batteries-included
27/// environment suitable for eval-crate tests.
28pub fn standard_env_minimal(
29    eval_fn: Option<fn(&Form, &mut Env) -> EvalResult>,
30    call_cljrs_fn: Option<fn(&CljxFn, &[Value], &mut Env) -> EvalResult>,
31    on_fn_defined: Option<fn(&CljxFn, &mut Env)>,
32) -> Arc<GlobalEnv> {
33    let globals = GlobalEnv::new(
34        eval_fn.unwrap_or(eval::eval),
35        call_cljrs_fn.unwrap_or(apply::call_cljrs_fn),
36        on_fn_defined,
37    );
38
39    // Register all native builtins in clojure.core.
40    builtins::register_all(&globals, "clojure.core");
41
42    // Set up user namespace referring clojure.core.
43    globals.get_or_create_ns("user");
44    globals.refer_all("user", "clojure.core");
45
46    // Eval bootstrap Clojure source in clojure.core.
47    {
48        let mut env = Env::new(globals.clone(), "clojure.core");
49        let src = builtins::BOOTSTRAP_SOURCE;
50        let mut parser = cljrs_reader::Parser::new(src.to_string(), "<bootstrap>".to_string());
51        match parser.parse_all() {
52            Ok(forms) => {
53                for form in forms {
54                    let _alloc_frame = cljrs_gc::push_alloc_frame();
55                    if let Err(e) = eval::eval(&form, &mut env) {
56                        eprintln!("[bootstrap warning] {}: {:?}", form.span.start, e);
57                    }
58                }
59            }
60            Err(e) => eprintln!("[bootstrap parse error] {:?}", e),
61        }
62    }
63
64    // Re-refer clojure.core after bootstrap defines HOFs.
65    globals.refer_all("user", "clojure.core");
66
67    // Mark clojure.core as loaded so (require 'clojure.core) is a no-op.
68    globals.mark_loaded("clojure.core");
69
70    // Set *ns* to the "user" namespace (the default REPL namespace).
71    {
72        let mut env = Env::new(globals.clone(), "user");
73        special::sync_star_ns(&mut env);
74    }
75
76    globals
77}
78
79/// Create a `GlobalEnv` pre-populated with `clojure.core` built-ins,
80/// bootstrap HOFs, and `clojure.test` (eagerly loaded so eval-crate tests
81/// can use `(require '[clojure.test ...])` without a source path).
82///
83/// For the `cljrs` binary, prefer `cljrs_stdlib::standard_env()` which loads
84/// `clojure.test` and other stdlib namespaces lazily via the registry.
85pub fn standard_env(
86    eval_fn: Option<fn(&Form, &mut Env) -> EvalResult>,
87    call_cljrs_fn: Option<fn(&CljxFn, &[Value], &mut Env) -> EvalResult>,
88    on_fn_defined: Option<fn(&CljxFn, &mut Env)>,
89) -> Arc<GlobalEnv> {
90    let globals = standard_env_minimal(eval_fn, call_cljrs_fn, on_fn_defined);
91
92    // Eagerly load clojure.test so eval-crate tests can `require` it.
93    {
94        let mut env = Env::new(globals.clone(), "clojure.core");
95        let src = builtins::CLOJURE_TEST_SOURCE;
96        let mut parser = cljrs_reader::Parser::new(src.to_string(), "<clojure.test>".to_string());
97        match parser.parse_all() {
98            Ok(forms) => {
99                for form in forms {
100                    let _alloc_frame = cljrs_gc::push_alloc_frame();
101                    if let Err(e) = eval::eval(&form, &mut env) {
102                        eprintln!("[clojure.test warning] {}: {:?}", form.span.start, e);
103                    }
104                }
105            }
106            Err(e) => eprintln!("[clojure.test parse error] {:?}", e),
107        }
108        globals.mark_loaded("clojure.test");
109    }
110
111    // Compiler namespaces are NOT loaded here — the Clojure compiler's deep
112    // recursion can overflow the default 8MB test thread stack.  Instead,
113    // callers that need IR lowering should call `ensure_compiler_loaded()`
114    // on a thread with sufficient stack (see cljrs-stdlib / cljrs binary).
115
116    // Restore *ns* to "user" — loading above may change it.
117    {
118        let mut env = Env::new(globals.clone(), "user");
119        special::sync_star_ns(&mut env);
120    }
121
122    globals
123}
124
125/// Create a `GlobalEnv` with built-ins, bootstrap HOFs, and configured source paths.
126pub fn standard_env_with_paths(
127    eval_fn: Option<fn(&Form, &mut Env) -> EvalResult>,
128    call_cljrs_fn: Option<fn(&CljxFn, &[Value], &mut Env) -> EvalResult>,
129    on_fn_defined: Option<fn(&CljxFn, &mut Env)>,
130    source_paths: Vec<std::path::PathBuf>,
131) -> Arc<GlobalEnv> {
132    let globals = standard_env(eval_fn, call_cljrs_fn, on_fn_defined);
133    globals.set_source_paths(source_paths);
134    globals
135}