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    let mut defaults: std::collections::HashMap<String, Value> = std::collections::HashMap::new();
159    let mut i = 0;
160    while i + 1 < pattern.len() {
161        let k = &pattern[i];
162        let v = &pattern[i + 1];
163        if let FormKind::Keyword(kw) = &k.kind
164            && kw == "or"
165        {
166            // v is a map literal {sym default ...}
167            if let FormKind::Map(or_forms) = &v.kind {
168                let mut j = 0;
169                while j + 1 < or_forms.len() {
170                    if let FormKind::Symbol(sym) = &or_forms[j].kind {
171                        defaults.insert(sym.clone(), form_to_value(&or_forms[j + 1])?);
172                    }
173                    j += 2;
174                }
175            }
176        }
177        i += 2;
178    }
179
180    let get_val = |key: &Value| -> Value {
181        match val.unwrap_meta() {
182            Value::Map(m) => m.get(key).unwrap_or(Value::Nil),
183            _ => Value::Nil,
184        }
185    };
186
187    let mut i = 0;
188    while i + 1 < pattern.len() {
189        let k = &pattern[i];
190        let v = &pattern[i + 1];
191        i += 2;
192
193        // A `:keys`/`:strs`/`:syms` keyword may itself be namespace-qualified
194        // (`:person/keys [a b]`), supplying a default namespace for every
195        // unqualified symbol in its vector; an individual symbol's own
196        // namespace (`ui/dest`) takes precedence over that default.
197        let directive_ns = if let FormKind::Keyword(kw) = &k.kind {
198            Keyword::parse(kw).namespace
199        } else {
200            None
201        };
202        let directive_name = if let FormKind::Keyword(kw) = &k.kind {
203            Keyword::parse(kw).name.to_string()
204        } else {
205            String::new()
206        };
207
208        match &k.kind {
209            FormKind::Keyword(_) if directive_name == "keys" => {
210                if let FormKind::Vector(syms) = &v.kind {
211                    for sym_form in syms {
212                        if let FormKind::Symbol(sym) = &sym_form.kind {
213                            let parsed = Symbol::parse(sym);
214                            let key =
215                                match parsed.namespace.clone().or_else(|| directive_ns.clone()) {
216                                    Some(ns) => {
217                                        Value::keyword(Keyword::qualified(ns, parsed.name.clone()))
218                                    }
219                                    None => Value::keyword(Keyword::simple(parsed.name.clone())),
220                                };
221                            let mut bound_val = get_val(&key);
222                            if matches!(bound_val, Value::Nil)
223                                && let Some(d) = defaults.get(parsed.name.as_ref())
224                            {
225                                bound_val = d.clone();
226                            }
227                            env.bind(parsed.name.clone(), bound_val);
228                        }
229                    }
230                }
231            }
232            FormKind::Keyword(kw) if kw == "strs" => {
233                if let FormKind::Vector(syms) = &v.kind {
234                    for sym_form in syms {
235                        if let FormKind::Symbol(sym) = &sym_form.kind {
236                            let key = Value::string(sym.clone());
237                            let mut bound_val = get_val(&key);
238                            if matches!(bound_val, Value::Nil)
239                                && let Some(d) = defaults.get(sym.as_str())
240                            {
241                                bound_val = d.clone();
242                            }
243                            env.bind(Arc::from(sym.as_str()), bound_val);
244                        }
245                    }
246                }
247            }
248            FormKind::Keyword(_) if directive_name == "syms" => {
249                if let FormKind::Vector(syms) = &v.kind {
250                    for sym_form in syms {
251                        if let FormKind::Symbol(sym) = &sym_form.kind {
252                            let parsed = Symbol::parse(sym);
253                            let key =
254                                match parsed.namespace.clone().or_else(|| directive_ns.clone()) {
255                                    Some(ns) => {
256                                        Value::symbol(Symbol::qualified(ns, parsed.name.clone()))
257                                    }
258                                    None => Value::symbol(Symbol::simple(parsed.name.clone())),
259                                };
260                            let mut bound_val = get_val(&key);
261                            if matches!(bound_val, Value::Nil)
262                                && let Some(d) = defaults.get(parsed.name.as_ref())
263                            {
264                                bound_val = d.clone();
265                            }
266                            env.bind(parsed.name.clone(), bound_val);
267                        }
268                    }
269                }
270            }
271            FormKind::Keyword(kw) if kw == "as" => {
272                if let FormKind::Symbol(sym) = &v.kind {
273                    env.bind(Arc::from(sym.as_str()), val.clone());
274                }
275            }
276            FormKind::Keyword(kw) if kw == "or" => {
277                // Already processed in the first pass.
278            }
279            _ => {
280                // Regular {binding-form lookup-key} pair.
281                // In Clojure map destructuring {a :x}, the key position is the
282                // binding target and the value position is the lookup key.
283                let lookup_key = form_to_value(v)?;
284                let mut bound_val = get_val(&lookup_key);
285                // Apply defaults for simple symbol bindings.
286                if matches!(bound_val, Value::Nil)
287                    && let FormKind::Symbol(sym) = &k.kind
288                    && let Some(d) = defaults.get(sym.as_str())
289                {
290                    bound_val = d.clone();
291                }
292                // Bind via pattern to support nested destructuring.
293                bind_pattern(k, bound_val, env)?;
294            }
295        }
296    }
297    Ok(())
298}