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}