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};
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);
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 any sequential Value to a Vec of its elements.
102pub fn value_to_seq_vec(val: &Value) -> Vec<Value> {
103    match val {
104        Value::WithMeta(inner, _) => value_to_seq_vec(inner),
105        Value::Nil => vec![],
106        Value::LazySeq(ls) => value_to_seq_vec(&ls.get().realize()),
107        Value::Cons(c) => {
108            let mut result = vec![c.get().head.clone()];
109            let mut tail = c.get().tail.clone();
110            loop {
111                match tail {
112                    Value::Nil => break,
113                    Value::List(l) => {
114                        result.extend(l.get().iter().cloned());
115                        break;
116                    }
117                    Value::Cons(next_c) => {
118                        result.push(next_c.get().head.clone());
119                        tail = next_c.get().tail.clone();
120                    }
121                    Value::LazySeq(ls) => {
122                        tail = ls.get().realize();
123                    }
124                    _ => break,
125                }
126            }
127            result
128        }
129        Value::List(l) => l.get().iter().cloned().collect(),
130        Value::Vector(v) => v.get().iter().cloned().collect(),
131        Value::Set(s) => s.iter().cloned().collect(),
132        Value::Map(m) => {
133            let mut result = Vec::new();
134            m.for_each(|k, v| {
135                result.push(Value::map_entry(k.clone(), v.clone()));
136            });
137            result
138        }
139        _ => vec![],
140    }
141}
142
143// ── Associative destructuring ─────────────────────────────────────────────────
144
145/// Bind a map destructuring pattern against `val` in `env`.
146///
147/// `pattern` is a flat `[key val key val ...]` slice from `FormKind::Map`.
148///
149/// Supports:
150/// - `:keys [a b c]`   — bind symbols from keyword keys `:a`, `:b`, `:c`
151/// - `:strs [a b]`     — bind symbols from string keys `"a"`, `"b"`
152/// - `:syms [a b]`     — bind symbols from symbol keys `'a`, `'b`
153/// - `:as name`        — bind the whole value to `name`
154/// - `:or {a default}` — default value for missing keys
155/// - Regular `{sym :key}` direct bindings
156pub fn bind_associative(pattern: &[Form], val: &Value, env: &mut Env) -> EvalResult<()> {
157    // First pass: collect :or defaults.
158    // The default is an EXPRESSION, held unevaluated until its symbol is bound.
159    // Storing `form_to_value` here is what made `:or {rules default-rules}` bind
160    // the SYMBOL `default-rules`; and evaluating every default up front would
161    // break `{:keys [a b] :or {b (inc a)}}`, which Clojure supports because it
162    // emits one `(get m :k default)` per symbol, in binding order.
163    let mut defaults: std::collections::HashMap<String, Form> = std::collections::HashMap::new();
164    let mut i = 0;
165    while i + 1 < pattern.len() {
166        let k = &pattern[i];
167        let v = &pattern[i + 1];
168        if let FormKind::Keyword(kw) = &k.kind
169            && kw == "or"
170        {
171            // v is a map literal {sym default ...}
172            if let FormKind::Map(or_forms) = &v.kind {
173                let mut j = 0;
174                while j + 1 < or_forms.len() {
175                    if let FormKind::Symbol(sym) = &or_forms[j].kind {
176                        defaults.insert(sym.clone(), or_forms[j + 1].clone());
177                    }
178                    j += 2;
179                }
180            }
181        }
182        i += 2;
183    }
184
185    let get_val = |key: &Value| -> Value {
186        match val.unwrap_meta() {
187            Value::Map(m) => m.get(key).unwrap_or(Value::Nil),
188            _ => Value::Nil,
189        }
190    };
191
192    let mut i = 0;
193    while i + 1 < pattern.len() {
194        let k = &pattern[i];
195        let v = &pattern[i + 1];
196        i += 2;
197
198        // A `:keys`/`:strs`/`:syms` keyword may itself be namespace-qualified
199        // (`:person/keys [a b]`), supplying a default namespace for every
200        // unqualified symbol in its vector; an individual symbol's own
201        // namespace (`ui/dest`) takes precedence over that default.
202        let directive_ns = if let FormKind::Keyword(kw) = &k.kind {
203            Keyword::parse(kw).namespace
204        } else {
205            None
206        };
207        let directive_name = if let FormKind::Keyword(kw) = &k.kind {
208            Keyword::parse(kw).name.to_string()
209        } else {
210            String::new()
211        };
212
213        match &k.kind {
214            FormKind::Keyword(_) if directive_name == "keys" => {
215                if let FormKind::Vector(syms) = &v.kind {
216                    for sym_form in syms {
217                        if let FormKind::Symbol(sym) = &sym_form.kind {
218                            let parsed = Symbol::parse(sym);
219                            let key =
220                                match parsed.namespace.clone().or_else(|| directive_ns.clone()) {
221                                    Some(ns) => {
222                                        Value::keyword(Keyword::qualified(ns, parsed.name.clone()))
223                                    }
224                                    None => Value::keyword(Keyword::simple(parsed.name.clone())),
225                                };
226                            let mut bound_val = get_val(&key);
227                            if let Some(d) = defaults.get(parsed.name.as_ref()) {
228                                // Evaluated whether or not the key was present:
229                                // Clojure's `(get m :k default)` evaluates its
230                                // third argument eagerly, so a throwing or
231                                // side-effecting default must still fire here.
232                                let dv = crate::interp::eval::eval(&d.clone(), env)?;
233                                if matches!(bound_val, Value::Nil) {
234                                    bound_val = dv;
235                                }
236                            }
237                            env.bind(parsed.name.clone(), bound_val);
238                        }
239                    }
240                }
241            }
242            FormKind::Keyword(kw) if kw == "strs" => {
243                if let FormKind::Vector(syms) = &v.kind {
244                    for sym_form in syms {
245                        if let FormKind::Symbol(sym) = &sym_form.kind {
246                            let key = Value::string(sym.clone());
247                            let mut bound_val = get_val(&key);
248                            if let Some(d) = defaults.get(sym.as_str()) {
249                                // Evaluated whether or not the key was present:
250                                // Clojure's `(get m :k default)` evaluates its
251                                // third argument eagerly, so a throwing or
252                                // side-effecting default must still fire here.
253                                let dv = crate::interp::eval::eval(&d.clone(), env)?;
254                                if matches!(bound_val, Value::Nil) {
255                                    bound_val = dv;
256                                }
257                            }
258                            env.bind(Arc::from(sym.as_str()), bound_val);
259                        }
260                    }
261                }
262            }
263            FormKind::Keyword(_) if directive_name == "syms" => {
264                if let FormKind::Vector(syms) = &v.kind {
265                    for sym_form in syms {
266                        if let FormKind::Symbol(sym) = &sym_form.kind {
267                            let parsed = Symbol::parse(sym);
268                            let key =
269                                match parsed.namespace.clone().or_else(|| directive_ns.clone()) {
270                                    Some(ns) => {
271                                        Value::symbol(Symbol::qualified(ns, parsed.name.clone()))
272                                    }
273                                    None => Value::symbol(Symbol::simple(parsed.name.clone())),
274                                };
275                            let mut bound_val = get_val(&key);
276                            if let Some(d) = defaults.get(parsed.name.as_ref()) {
277                                // Evaluated whether or not the key was present:
278                                // Clojure's `(get m :k default)` evaluates its
279                                // third argument eagerly, so a throwing or
280                                // side-effecting default must still fire here.
281                                let dv = crate::interp::eval::eval(&d.clone(), env)?;
282                                if matches!(bound_val, Value::Nil) {
283                                    bound_val = dv;
284                                }
285                            }
286                            env.bind(parsed.name.clone(), bound_val);
287                        }
288                    }
289                }
290            }
291            FormKind::Keyword(kw) if kw == "as" => {
292                if let FormKind::Symbol(sym) = &v.kind {
293                    env.bind(Arc::from(sym.as_str()), val.clone());
294                }
295            }
296            FormKind::Keyword(kw) if kw == "or" => {
297                // Already processed in the first pass.
298            }
299            _ => {
300                // Regular {binding-form lookup-key} pair.
301                // In Clojure map destructuring {a :x}, the key position is the
302                // binding target and the value position is the lookup key.
303                let lookup_key = form_to_value(v)?;
304                let mut bound_val = get_val(&lookup_key);
305                // Apply defaults for simple symbol bindings.
306                if let FormKind::Symbol(sym) = &k.kind
307                    && let Some(d) = defaults.get(sym.as_str())
308                {
309                    let dv = crate::interp::eval::eval(&d.clone(), env)?;
310                    if matches!(bound_val, Value::Nil) {
311                        bound_val = dv;
312                    }
313                }
314                // Bind via pattern to support nested destructuring.
315                bind_pattern(k, bound_val, env)?;
316            }
317        }
318    }
319    Ok(())
320}