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