Skip to main content

cljrs_eval/
lower.rs

1//! Clojure-based IR lowering orchestration.
2//!
3//! This module bridges the Clojure compiler front-end (`cljrs.compiler.anf`)
4//! with the Rust IR interpreter.  It calls the Clojure `lower-fn-body`
5//! function to lower macro-expanded Form ASTs to IR data, then converts
6//! the result to a Rust `IrFunction` via `ir_convert`.
7
8use std::sync::Arc;
9
10use cljrs_ir::IrFunction;
11use cljrs_reader::Form;
12use cljrs_value::Value;
13
14use cljrs_env::env::Env;
15
16// ── Error type ──────────────────────────────────────────────────────────────
17
18#[derive(Debug)]
19pub enum LowerError {
20    /// The compiler namespaces are not loaded yet (still bootstrapping).
21    NotReady,
22    /// The Clojure lowering function returned an error.
23    LowerFailed(String),
24    /// IR conversion from Clojure data to Rust types failed.
25    ConvertFailed(String),
26}
27
28impl std::fmt::Display for LowerError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            LowerError::NotReady => write!(f, "compiler not ready"),
32            LowerError::LowerFailed(msg) => write!(f, "lowering failed: {msg}"),
33            LowerError::ConvertFailed(msg) => write!(f, "IR conversion failed: {msg}"),
34        }
35    }
36}
37
38// ── Public entry point ──────────────────────────────────────────────────────
39
40/// Lower a function arity's body to IR via the Clojure compiler front-end.
41///
42/// Returns `Err(NotReady)` if the compiler namespaces haven't been loaded.
43/// Returns `Err(LowerFailed)` if the Clojure lowering function fails.
44/// Returns `Err(ConvertFailed)` if the Clojure data → Rust IR conversion fails.
45pub fn lower_arity(
46    name: Option<&str>,
47    params: &[Arc<str>],
48    rest_param: Option<&Arc<str>>,
49    body: &[Form],
50    ns: &Arc<str>,
51    env: &mut Env,
52) -> Result<IrFunction, LowerError> {
53    // Check if compiler is ready.
54    if !env
55        .globals
56        .compiler_ready
57        .load(std::sync::atomic::Ordering::Acquire)
58    {
59        return Err(LowerError::NotReady);
60    }
61
62    lower_arity_inner(name, params, rest_param, body, ns, env)
63}
64
65fn lower_arity_inner(
66    name: Option<&str>,
67    params: &[Arc<str>],
68    rest_param: Option<&Arc<str>>,
69    body: &[Form],
70    ns: &Arc<str>,
71    env: &mut Env,
72) -> Result<IrFunction, LowerError> {
73    use cljrs_gc::GcPtr;
74    use cljrs_value::collections::vector::PersistentVector;
75
76    let globals = &env.globals;
77
78    // Look up the lower-fn-body function.
79    let lower_fn = globals
80        .lookup_var_in_ns("cljrs.compiler.anf", "lower-fn-body")
81        .ok_or_else(|| {
82            LowerError::LowerFailed("cljrs.compiler.anf/lower-fn-body not found".to_string())
83        })?;
84    let lower_fn_val = lower_fn.get().deref().unwrap_or(Value::Nil);
85
86    // Macro-expand the body forms before lowering to IR.
87    // The ANF compiler does not expand macros; we must do it here so that
88    // macro calls (e.g. `cond`, `when`, `and`) become their `if`-chain
89    // expansions rather than unresolvable function calls at runtime.
90    let expanded_body: Vec<Form> = body
91        .iter()
92        .map(|f| cljrs_interp::macros::macroexpand_all(f, env).unwrap_or_else(|_| f.clone()))
93        .collect();
94    let body = expanded_body.as_slice();
95
96    // Build arguments:
97    // 1. fname (string or nil)
98    let fname_val = match name {
99        Some(n) => Value::string(n.to_string()),
100        None => Value::Nil,
101    };
102
103    // 2. ns (string)
104    let ns_val = Value::string(ns.to_string());
105
106    // 3. params (vector of strings) — includes rest param if present
107    let mut param_strs: Vec<Value> = params
108        .iter()
109        .map(|p| Value::string(p.to_string()))
110        .collect();
111    if let Some(rest) = rest_param {
112        // The Clojure lowerer expects all params including rest as a flat list.
113        // The last param in a variadic arity is the rest param.
114        param_strs.push(Value::string(rest.to_string()));
115    }
116    let params_val = Value::Vector(GcPtr::new(PersistentVector::from_iter(param_strs)));
117
118    // 4. body-forms (vector of form values)
119    let body_forms_val = Value::Vector(GcPtr::new(PersistentVector::from_iter(
120        body.iter().map(cljrs_builtins::form::form_to_value),
121    )));
122
123    // Push eval context so callback::invoke can work.
124    cljrs_env::callback::push_eval_context(env);
125
126    // Set IR_LOWERING_ACTIVE to prevent eager lowering of closures
127    // created inside the Clojure compiler during this lowering call.
128    use crate::apply::IR_LOWERING_ACTIVE;
129    let was_active = IR_LOWERING_ACTIVE.get();
130    IR_LOWERING_ACTIVE.set(true);
131
132    // Call the Clojure lowering function.
133    let ir_data = cljrs_env::callback::invoke(
134        &lower_fn_val,
135        vec![fname_val, ns_val, params_val, body_forms_val],
136    );
137
138    // Restore lowering flag and pop eval context.
139    IR_LOWERING_ACTIVE.with(|c| c.set(was_active));
140    cljrs_env::callback::pop_eval_context();
141
142    let ir_data = ir_data.map_err(|e| LowerError::LowerFailed(format!("{e:?}")))?;
143
144    // Convert the result Value → IrFunction.
145    crate::ir_convert::value_to_ir_function(&ir_data)
146        .map_err(|e| LowerError::ConvertFailed(format!("{e}")))
147}