Skip to main content

cljrs_runtime/interp/
destructure.rs

1//! Sequential and associative destructuring for `let*`, `fn*`, and `loop*`.
2
3use crate::builtins::form::form_to_value;
4use cljrs_gc::GcPtr;
5use cljrs_reader::Form;
6use cljrs_reader::form::FormKind;
7use cljrs_value::{Keyword, PersistentList, Symbol, Value, ValueError, ValueResult};
8use std::sync::Arc;
9
10use crate::env::env::Env;
11use crate::env::error::{EvalError, EvalResult};
12
13/// Bind a destructuring pattern `pattern` against `val` in `env`.
14///
15/// Supports:
16/// - Plain symbol binding
17/// - `[a b]` sequential destructuring (recursive)
18pub fn bind_pattern(pattern: &Form, val: Value, env: &mut Env) -> EvalResult<()> {
19    match &pattern.kind {
20        FormKind::Symbol(s) => {
21            // Plain binding, including `_`.
22            env.bind(Arc::from(s.as_str()), val);
23            Ok(())
24        }
25        FormKind::Vector(forms) => bind_sequential(forms, &val, env),
26        FormKind::Map(forms) => bind_associative(forms, &val, env),
27        // A `^long x` (etc.) binding carries a primitive type hint that the
28        // tree-walk interpreter ignores; bind the underlying pattern.
29        FormKind::Meta(_, inner) => bind_pattern(inner, val, env),
30        _ => Err(EvalError::Runtime(format!(
31            "unsupported binding pattern: {:?}",
32            pattern.kind
33        ))),
34    }
35}
36
37/// Bind a sequential destructuring pattern against `val`.
38///
39/// Grammar of `pattern`:
40/// ```text
41/// [sym* (& rest)? (:as alias)?]
42/// ```
43pub fn bind_sequential(pattern: &[Form], val: &Value, env: &mut Env) -> EvalResult<()> {
44    let items = value_to_seq_vec(val).map_err(crate::env::error::value_error_to_eval_error)?;
45    let mut idx = 0usize;
46    let mut i = 0usize;
47
48    while i < pattern.len() {
49        let p = &pattern[i];
50
51        // `&` introduces a rest binding.
52        if matches!(&p.kind, FormKind::Symbol(s) if s == "&") {
53            i += 1;
54            let rest_pat = pattern
55                .get(i)
56                .ok_or_else(|| EvalError::Runtime("& in destructuring requires a name".into()))?;
57            let rest_list = if idx < items.len() {
58                let rest_vals: Vec<Value> = items[idx..].to_vec();
59                Value::List(GcPtr::new(PersistentList::from_iter(rest_vals)))
60            } else {
61                Value::Nil
62            };
63            bind_pattern(rest_pat, rest_list, env)?;
64            i += 1;
65            // Skip optional `:as` after rest.
66            if i < pattern.len()
67                && let FormKind::Keyword(k) = &pattern[i].kind
68                && k == "as"
69            {
70                i += 1;
71                let alias = pattern
72                    .get(i)
73                    .ok_or_else(|| EvalError::Runtime(":as requires a name".into()))?;
74                bind_pattern(alias, val.clone(), env)?;
75            }
76            break;
77        }
78
79        // `:as` alias — must be last.
80        if let FormKind::Keyword(k) = &p.kind
81            && k == "as"
82        {
83            i += 1;
84            let alias = pattern
85                .get(i)
86                .ok_or_else(|| EvalError::Runtime(":as requires a name".into()))?;
87            bind_pattern(alias, val.clone(), env)?;
88            break;
89        }
90
91        // Normal positional binding.
92        let item = items.get(idx).cloned().unwrap_or(Value::Nil);
93        bind_pattern(p, item, env)?;
94        idx += 1;
95        i += 1;
96    }
97
98    Ok(())
99}
100
101/// Convert a seqable Value to its elements, propagating lazy realization errors.
102/// Non-seqable values retain the historical empty-vector result.
103pub fn value_to_seq_vec(val: &Value) -> ValueResult<Vec<Value>> {
104    // Realization can park or collect. The original chain retains both the
105    // accumulated elements and each thunk's cached result throughout the walk.
106    let _root = crate::env::gc_roots::root_value(val);
107    let mut result = Vec::new();
108    let mut current = val.clone();
109    loop {
110        match current.unwrap_meta() {
111            Value::Nil => break,
112            Value::LazySeq(ls) => {
113                current = ls.get().try_realize().map_err(ValueError::Other)?;
114            }
115            Value::Cons(c) => {
116                result.push(c.get().head.clone());
117                current = c.get().tail.clone();
118            }
119            Value::List(l) => {
120                result.extend(l.get().iter().cloned());
121                break;
122            }
123            Value::Vector(v) => {
124                result.extend(v.get().iter().cloned());
125                break;
126            }
127            Value::Set(s) => {
128                result.extend(s.iter().cloned());
129                break;
130            }
131            Value::Map(m) => {
132                m.for_each(|k, v| result.push(Value::map_entry(k.clone(), v.clone())));
133                break;
134            }
135            _ => break,
136        }
137    }
138    Ok(result)
139}
140
141// ── Associative destructuring ─────────────────────────────────────────────────
142
143/// Bind a map destructuring pattern against `val` in `env`.
144///
145/// `pattern` is a flat `[key val key val ...]` slice from `FormKind::Map`.
146///
147/// Supports:
148/// - `:keys [a b c]`   — bind symbols from keyword keys `:a`, `:b`, `:c`
149/// - `:strs [a b]`     — bind symbols from string keys `"a"`, `"b"`
150/// - `:syms [a b]`     — bind symbols from symbol keys `'a`, `'b`
151/// - `:as name`        — bind the whole value to `name`
152/// - `:or {a default}` — default value for missing keys
153/// - Regular `{sym :key}` direct bindings
154pub fn bind_associative(pattern: &[Form], val: &Value, env: &mut Env) -> EvalResult<()> {
155    // First pass: collect :or defaults.
156    // The default is an EXPRESSION, held unevaluated until its symbol is bound.
157    // Storing `form_to_value` here is what made `:or {rules default-rules}` bind
158    // the SYMBOL `default-rules`; and evaluating every default up front would
159    // break `{:keys [a b] :or {b (inc a)}}`, which Clojure supports because it
160    // emits one `(get m :k default)` per symbol, in binding order.
161    let mut defaults: std::collections::HashMap<String, Form> = std::collections::HashMap::new();
162    let mut i = 0;
163    while i + 1 < pattern.len() {
164        let k = &pattern[i];
165        let v = &pattern[i + 1];
166        if let FormKind::Keyword(kw) = &k.kind
167            && kw == "or"
168        {
169            // v is a map literal {sym default ...}
170            if let FormKind::Map(or_forms) = &v.kind {
171                let mut j = 0;
172                while j + 1 < or_forms.len() {
173                    if let FormKind::Symbol(sym) = &or_forms[j].kind {
174                        defaults.insert(sym.clone(), or_forms[j + 1].clone());
175                    }
176                    j += 2;
177                }
178            }
179        }
180        i += 2;
181    }
182
183    let get_val = |key: &Value| -> Value {
184        match val.unwrap_meta() {
185            Value::Map(m) => m.get(key).unwrap_or(Value::Nil),
186            _ => Value::Nil,
187        }
188    };
189
190    let mut i = 0;
191    while i + 1 < pattern.len() {
192        let k = &pattern[i];
193        let v = &pattern[i + 1];
194        i += 2;
195
196        // A `:keys`/`:strs`/`:syms` keyword may itself be namespace-qualified
197        // (`:person/keys [a b]`), supplying a default namespace for every
198        // unqualified symbol in its vector; an individual symbol's own
199        // namespace (`ui/dest`) takes precedence over that default.
200        let directive_ns = if let FormKind::Keyword(kw) = &k.kind {
201            Keyword::parse(kw).namespace
202        } else {
203            None
204        };
205        let directive_name = if let FormKind::Keyword(kw) = &k.kind {
206            Keyword::parse(kw).name.to_string()
207        } else {
208            String::new()
209        };
210
211        match &k.kind {
212            FormKind::Keyword(_) if directive_name == "keys" => {
213                if let FormKind::Vector(syms) = &v.kind {
214                    for sym_form in syms {
215                        if let FormKind::Symbol(sym) = &sym_form.kind {
216                            let parsed = Symbol::parse(sym);
217                            let key =
218                                match parsed.namespace.clone().or_else(|| directive_ns.clone()) {
219                                    Some(ns) => {
220                                        Value::keyword(Keyword::qualified(ns, parsed.name.clone()))
221                                    }
222                                    None => Value::keyword(Keyword::simple(parsed.name.clone())),
223                                };
224                            let mut bound_val = get_val(&key);
225                            if let Some(d) = defaults.get(parsed.name.as_ref()) {
226                                // Evaluated whether or not the key was present:
227                                // Clojure's `(get m :k default)` evaluates its
228                                // third argument eagerly, so a throwing or
229                                // side-effecting default must still fire here.
230                                let dv = crate::interp::eval::eval(&d.clone(), env)?;
231                                if matches!(bound_val, Value::Nil) {
232                                    bound_val = dv;
233                                }
234                            }
235                            env.bind(parsed.name.clone(), bound_val);
236                        }
237                    }
238                }
239            }
240            FormKind::Keyword(kw) if kw == "strs" => {
241                if let FormKind::Vector(syms) = &v.kind {
242                    for sym_form in syms {
243                        if let FormKind::Symbol(sym) = &sym_form.kind {
244                            let key = Value::string(sym.clone());
245                            let mut bound_val = get_val(&key);
246                            if let Some(d) = defaults.get(sym.as_str()) {
247                                // Evaluated whether or not the key was present:
248                                // Clojure's `(get m :k default)` evaluates its
249                                // third argument eagerly, so a throwing or
250                                // side-effecting default must still fire here.
251                                let dv = crate::interp::eval::eval(&d.clone(), env)?;
252                                if matches!(bound_val, Value::Nil) {
253                                    bound_val = dv;
254                                }
255                            }
256                            env.bind(Arc::from(sym.as_str()), bound_val);
257                        }
258                    }
259                }
260            }
261            FormKind::Keyword(_) if directive_name == "syms" => {
262                if let FormKind::Vector(syms) = &v.kind {
263                    for sym_form in syms {
264                        if let FormKind::Symbol(sym) = &sym_form.kind {
265                            let parsed = Symbol::parse(sym);
266                            let key =
267                                match parsed.namespace.clone().or_else(|| directive_ns.clone()) {
268                                    Some(ns) => {
269                                        Value::symbol(Symbol::qualified(ns, parsed.name.clone()))
270                                    }
271                                    None => Value::symbol(Symbol::simple(parsed.name.clone())),
272                                };
273                            let mut bound_val = get_val(&key);
274                            if let Some(d) = defaults.get(parsed.name.as_ref()) {
275                                // Evaluated whether or not the key was present:
276                                // Clojure's `(get m :k default)` evaluates its
277                                // third argument eagerly, so a throwing or
278                                // side-effecting default must still fire here.
279                                let dv = crate::interp::eval::eval(&d.clone(), env)?;
280                                if matches!(bound_val, Value::Nil) {
281                                    bound_val = dv;
282                                }
283                            }
284                            env.bind(parsed.name.clone(), bound_val);
285                        }
286                    }
287                }
288            }
289            FormKind::Keyword(kw) if kw == "as" => {
290                if let FormKind::Symbol(sym) = &v.kind {
291                    env.bind(Arc::from(sym.as_str()), val.clone());
292                }
293            }
294            FormKind::Keyword(kw) if kw == "or" => {
295                // Already processed in the first pass.
296            }
297            _ => {
298                // Regular {binding-form lookup-key} pair.
299                // In Clojure map destructuring {a :x}, the key position is the
300                // binding target and the value position is the lookup key.
301                let lookup_key = form_to_value(v)?;
302                let mut bound_val = get_val(&lookup_key);
303                // Apply defaults for simple symbol bindings.
304                if let FormKind::Symbol(sym) = &k.kind
305                    && let Some(d) = defaults.get(sym.as_str())
306                {
307                    let dv = crate::interp::eval::eval(&d.clone(), env)?;
308                    if matches!(bound_val, Value::Nil) {
309                        bound_val = dv;
310                    }
311                }
312                // Bind via pattern to support nested destructuring.
313                bind_pattern(k, bound_val, env)?;
314            }
315        }
316    }
317    Ok(())
318}