Skip to main content

cljrs_eval/
ir_convert.rs

1//! Convert Clojure Value data (maps/vectors/keywords) → Rust IR types.
2//!
3//! This module is the boundary contract between the Clojure-based compiler
4//! front-end and the Rust-based codegen backend. It reads IR data structures
5//! represented as plain Clojure data and produces the `IrFunction`, `Block`,
6//! `Inst`, and `Terminator` types that `codegen.rs` consumes.
7
8use std::sync::Arc;
9
10use cljrs_types::span::Span;
11use cljrs_value::{Keyword, MapValue, Value};
12
13use cljrs_ir::*;
14
15// ── Error type ──────────────────────────────────────────────────────────────
16
17#[derive(Debug)]
18pub enum ConvertError {
19    /// A required field is missing from the map.
20    MissingField(String),
21    /// A field has the wrong type.
22    TypeError(String),
23    /// An unknown keyword was encountered.
24    UnknownVariant(String),
25}
26
27impl std::fmt::Display for ConvertError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            ConvertError::MissingField(s) => write!(f, "missing field: {s}"),
31            ConvertError::TypeError(s) => write!(f, "type error: {s}"),
32            ConvertError::UnknownVariant(s) => write!(f, "unknown variant: {s}"),
33        }
34    }
35}
36
37impl std::error::Error for ConvertError {}
38
39type ConvertResult<T> = Result<T, ConvertError>;
40
41// ── Map field extraction helpers ────────────────────────────────────────────
42
43fn get_field(map: &MapValue, key: &str) -> ConvertResult<Value> {
44    let kw = Value::keyword(Keyword::simple(key));
45    map.get(&kw)
46        .filter(|v| !matches!(v, Value::Nil))
47        .ok_or_else(|| ConvertError::MissingField(key.to_string()))
48}
49
50fn get_field_opt(map: &MapValue, key: &str) -> Option<Value> {
51    let kw = Value::keyword(Keyword::simple(key));
52    map.get(&kw).filter(|v| !matches!(v, Value::Nil))
53}
54
55fn as_map(val: &Value) -> ConvertResult<&MapValue> {
56    match val {
57        Value::Map(m) => Ok(m),
58        other => Err(ConvertError::TypeError(format!(
59            "expected map, got {}",
60            other.type_name()
61        ))),
62    }
63}
64
65fn as_long(val: &Value) -> ConvertResult<i64> {
66    match val {
67        Value::Long(n) => Ok(*n),
68        other => Err(ConvertError::TypeError(format!(
69            "expected long, got {}",
70            other.type_name()
71        ))),
72    }
73}
74
75fn as_u32(val: &Value) -> ConvertResult<u32> {
76    as_long(val).map(|n| n as u32)
77}
78
79fn as_str(val: &Value) -> ConvertResult<Arc<str>> {
80    match val {
81        Value::Str(s) => Ok(Arc::from(s.get().as_str())),
82        other => Err(ConvertError::TypeError(format!(
83            "expected string, got {}",
84            other.type_name()
85        ))),
86    }
87}
88
89fn as_keyword_name(val: &Value) -> ConvertResult<Arc<str>> {
90    match val {
91        Value::Keyword(kw) => Ok(Arc::clone(&kw.get().name)),
92        other => Err(ConvertError::TypeError(format!(
93            "expected keyword, got {}",
94            other.type_name()
95        ))),
96    }
97}
98
99fn as_vec(val: &Value) -> ConvertResult<Vec<Value>> {
100    match val {
101        Value::Vector(v) => {
102            let pv = v.get();
103            let mut result = Vec::with_capacity(pv.count());
104            for i in 0..pv.count() {
105                if let Some(item) = pv.nth(i) {
106                    result.push(item.clone());
107                }
108            }
109            Ok(result)
110        }
111        Value::List(l) => {
112            let mut result = Vec::new();
113            let mut cur = l.get().clone();
114            while let Some(v) = cur.first() {
115                result.push(v.clone());
116                let rest = cur.rest();
117                cur = (*rest).clone();
118            }
119            Ok(result)
120        }
121        Value::Nil => Ok(vec![]),
122        other => Err(ConvertError::TypeError(format!(
123            "expected vector/list, got {}",
124            other.type_name()
125        ))),
126    }
127}
128
129fn as_var_id(val: &Value) -> ConvertResult<VarId> {
130    as_u32(val).map(VarId)
131}
132
133fn as_block_id(val: &Value) -> ConvertResult<BlockId> {
134    as_u32(val).map(BlockId)
135}
136
137// ── Top-level conversion ────────────────────────────────────────────────────
138
139/// Convert a Clojure Value (map) → `IrFunction`.
140pub fn value_to_ir_function(val: &Value) -> ConvertResult<IrFunction> {
141    let map = as_map(val)?;
142
143    let name = get_field_opt(map, "name").map(|v| as_str(&v)).transpose()?;
144    let next_var = as_u32(&get_field(map, "next-var")?)?;
145    let next_block = as_u32(&get_field(map, "next-block")?)?;
146
147    // Parse params: vector of [name var-id] pairs
148    let params_val = get_field(map, "params")?;
149    let params_vec = as_vec(&params_val)?;
150    let mut params = Vec::with_capacity(params_vec.len());
151    for p in &params_vec {
152        let pair = as_vec(p)?;
153        if pair.len() != 2 {
154            return Err(ConvertError::TypeError(
155                "param must be [name var-id]".into(),
156            ));
157        }
158        let name = as_str(&pair[0])?;
159        let var_id = as_var_id(&pair[1])?;
160        params.push((name, var_id));
161    }
162
163    // Parse blocks
164    let blocks_val = get_field(map, "blocks")?;
165    let blocks_vec = as_vec(&blocks_val)?;
166    let mut blocks = Vec::with_capacity(blocks_vec.len());
167    for b in &blocks_vec {
168        blocks.push(value_to_block(b)?);
169    }
170
171    // Parse subfunctions (optional — may not be present)
172    let subfunctions = if let Some(subs_val) = get_field_opt(map, "subfunctions") {
173        let subs_vec = as_vec(&subs_val)?;
174        let mut subs = Vec::with_capacity(subs_vec.len());
175        for s in &subs_vec {
176            subs.push(value_to_ir_function(s)?);
177        }
178        subs
179    } else {
180        vec![]
181    };
182
183    Ok(IrFunction {
184        name,
185        params,
186        blocks,
187        next_var,
188        next_block,
189        span: None,
190        subfunctions,
191    })
192}
193
194/// Convert a Clojure Value (map) → `Block`.
195fn value_to_block(val: &Value) -> ConvertResult<Block> {
196    let map = as_map(val)?;
197
198    let id = as_block_id(&get_field(map, "id")?)?;
199
200    let phis_val = get_field(map, "phis")?;
201    let phis_vec = as_vec(&phis_val)?;
202    let mut phis = Vec::with_capacity(phis_vec.len());
203    for p in &phis_vec {
204        phis.push(value_to_inst(p)?);
205    }
206
207    let insts_val = get_field(map, "insts")?;
208    let insts_vec = as_vec(&insts_val)?;
209    let mut insts = Vec::with_capacity(insts_vec.len());
210    for i in &insts_vec {
211        insts.push(value_to_inst(i)?);
212    }
213
214    let term_val = get_field(map, "terminator")?;
215    let terminator = value_to_terminator(&term_val)?;
216
217    Ok(Block {
218        id,
219        phis,
220        insts,
221        terminator,
222    })
223}
224
225/// Convert a Clojure Value (map) → `Inst`.
226fn value_to_inst(val: &Value) -> ConvertResult<Inst> {
227    let map = as_map(val)?;
228    let op = as_keyword_name(&get_field(map, "op")?)?;
229
230    match op.as_ref() {
231        "const" => {
232            let dst = as_var_id(&get_field(map, "dst")?)?;
233            let const_val = get_field(map, "value")?;
234            let c = value_to_const(&const_val)?;
235            Ok(Inst::Const(dst, c))
236        }
237        "load-local" => {
238            let dst = as_var_id(&get_field(map, "dst")?)?;
239            let name = as_str(&get_field(map, "name")?)?;
240            Ok(Inst::LoadLocal(dst, name))
241        }
242        "load-global" => {
243            let dst = as_var_id(&get_field(map, "dst")?)?;
244            let ns = as_str(&get_field(map, "ns")?)?;
245            let name = as_str(&get_field(map, "name")?)?;
246            Ok(Inst::LoadGlobal(dst, ns, name))
247        }
248        "load-var" => {
249            let dst = as_var_id(&get_field(map, "dst")?)?;
250            let ns = as_str(&get_field(map, "ns")?)?;
251            let name = as_str(&get_field(map, "name")?)?;
252            Ok(Inst::LoadVar(dst, ns, name))
253        }
254        "alloc-vector" => {
255            let dst = as_var_id(&get_field(map, "dst")?)?;
256            let elems = as_var_id_vec(&get_field(map, "elems")?)?;
257            Ok(Inst::AllocVector(dst, elems))
258        }
259        "alloc-map" => {
260            let dst = as_var_id(&get_field(map, "dst")?)?;
261            let pairs_val = get_field(map, "pairs")?;
262            let pairs_vec = as_vec(&pairs_val)?;
263            let mut pairs = Vec::with_capacity(pairs_vec.len());
264            for p in &pairs_vec {
265                let pair = as_vec(p)?;
266                if pair.len() != 2 {
267                    return Err(ConvertError::TypeError("map pair must be [k v]".into()));
268                }
269                pairs.push((as_var_id(&pair[0])?, as_var_id(&pair[1])?));
270            }
271            Ok(Inst::AllocMap(dst, pairs))
272        }
273        "alloc-set" => {
274            let dst = as_var_id(&get_field(map, "dst")?)?;
275            let elems = as_var_id_vec(&get_field(map, "elems")?)?;
276            Ok(Inst::AllocSet(dst, elems))
277        }
278        "alloc-list" => {
279            let dst = as_var_id(&get_field(map, "dst")?)?;
280            let elems = as_var_id_vec(&get_field(map, "elems")?)?;
281            Ok(Inst::AllocList(dst, elems))
282        }
283        "alloc-cons" => {
284            let dst = as_var_id(&get_field(map, "dst")?)?;
285            let head = as_var_id(&get_field(map, "head")?)?;
286            let tail = as_var_id(&get_field(map, "tail")?)?;
287            Ok(Inst::AllocCons(dst, head, tail))
288        }
289        "alloc-closure" => {
290            let dst = as_var_id(&get_field(map, "dst")?)?;
291            let captures = as_var_id_vec(&get_field(map, "captures")?)?;
292            let name = get_field_opt(map, "closure-name")
293                .map(|v| as_str(&v))
294                .transpose()?;
295            // Parse arity function names
296            let arity_fn_names = if let Some(v) = get_field_opt(map, "arity-fn-names") {
297                as_vec(&v)?
298                    .iter()
299                    .map(as_str)
300                    .collect::<ConvertResult<Vec<_>>>()?
301            } else {
302                vec![]
303            };
304            // Parse parameter counts
305            let param_counts = if let Some(v) = get_field_opt(map, "param-counts") {
306                as_vec(&v)?
307                    .iter()
308                    .map(|n| as_long(n).map(|i| i as usize))
309                    .collect::<ConvertResult<Vec<_>>>()?
310            } else {
311                vec![]
312            };
313            // Parse capture names
314            let capture_names = if let Some(v) = get_field_opt(map, "capture-names") {
315                as_vec(&v)?
316                    .iter()
317                    .map(as_str)
318                    .collect::<ConvertResult<Vec<_>>>()?
319            } else {
320                vec![]
321            };
322            // Parse variadic flags (defaults to all false if not present)
323            let is_variadic = if let Some(v) = get_field_opt(map, "is-variadic") {
324                as_vec(&v)?
325                    .iter()
326                    .map(|b| match b {
327                        Value::Bool(v) => Ok(*v),
328                        _ => Err(ConvertError::TypeError(
329                            "expected bool for is-variadic".into(),
330                        )),
331                    })
332                    .collect::<ConvertResult<Vec<_>>>()?
333            } else {
334                vec![false; param_counts.len()]
335            };
336            let tmpl = ClosureTemplate {
337                name,
338                arity_fn_names,
339                param_counts,
340                is_variadic,
341                capture_names,
342            };
343            Ok(Inst::AllocClosure(dst, tmpl, captures))
344        }
345        "call-known" => {
346            let dst = as_var_id(&get_field(map, "dst")?)?;
347            let func_kw = as_keyword_name(&get_field(map, "func")?)?;
348            let known = keyword_to_known_fn(&func_kw).ok_or_else(|| {
349                ConvertError::UnknownVariant(format!("unknown KnownFn: {func_kw}"))
350            })?;
351            let args = as_var_id_vec(&get_field(map, "args")?)?;
352            Ok(Inst::CallKnown(dst, known, args))
353        }
354        "call" => {
355            let dst = as_var_id(&get_field(map, "dst")?)?;
356            let callee = as_var_id(&get_field(map, "callee")?)?;
357            let args = as_var_id_vec(&get_field(map, "args")?)?;
358            Ok(Inst::Call(dst, callee, args))
359        }
360        "deref" => {
361            let dst = as_var_id(&get_field(map, "dst")?)?;
362            let src = as_var_id(&get_field(map, "src")?)?;
363            Ok(Inst::Deref(dst, src))
364        }
365        "def-var" => {
366            let dst = as_var_id(&get_field(map, "dst")?)?;
367            let ns = as_str(&get_field(map, "ns")?)?;
368            let name = as_str(&get_field(map, "name")?)?;
369            let value = as_var_id(&get_field(map, "value")?)?;
370            Ok(Inst::DefVar(dst, ns, name, value))
371        }
372        "set!" => {
373            let var = as_var_id(&get_field(map, "var")?)?;
374            let value = as_var_id(&get_field(map, "value")?)?;
375            Ok(Inst::SetBang(var, value))
376        }
377        "throw" => {
378            let val = as_var_id(&get_field(map, "value")?)?;
379            Ok(Inst::Throw(val))
380        }
381        "phi" => {
382            let dst = as_var_id(&get_field(map, "dst")?)?;
383            let entries_val = get_field(map, "entries")?;
384            let entries_vec = as_vec(&entries_val)?;
385            let mut entries = Vec::with_capacity(entries_vec.len());
386            for e in &entries_vec {
387                let pair = as_vec(e)?;
388                if pair.len() != 2 {
389                    return Err(ConvertError::TypeError(
390                        "phi entry must be [block-id var-id]".into(),
391                    ));
392                }
393                entries.push((as_block_id(&pair[0])?, as_var_id(&pair[1])?));
394            }
395            Ok(Inst::Phi(dst, entries))
396        }
397        "recur" => {
398            let args = as_var_id_vec(&get_field(map, "args")?)?;
399            Ok(Inst::Recur(args))
400        }
401        "source-loc" => {
402            let file = as_str(&get_field(map, "file")?)?;
403            let line = as_u32(&get_field(map, "line")?)?;
404            let col = as_u32(&get_field(map, "col")?)?;
405            Ok(Inst::SourceLoc(Span::new(
406                Arc::new(file.to_string()),
407                0,
408                0,
409                line,
410                col,
411            )))
412        }
413        "region-start" => {
414            let dst = as_var_id(&get_field(map, "dst")?)?;
415            Ok(Inst::RegionStart(dst))
416        }
417        "region-alloc" => {
418            let dst = as_var_id(&get_field(map, "dst")?)?;
419            let region = as_var_id(&get_field(map, "region")?)?;
420            let kind_kw = as_keyword_name(&get_field(map, "kind")?)?;
421            let kind = keyword_to_region_alloc_kind(&kind_kw).ok_or_else(|| {
422                ConvertError::UnknownVariant(format!("unknown RegionAllocKind: {kind_kw}"))
423            })?;
424            let operands = as_var_id_vec(&get_field(map, "operands")?)?;
425            Ok(Inst::RegionAlloc(dst, region, kind, operands))
426        }
427        "region-end" => {
428            let region = as_var_id(&get_field(map, "region")?)?;
429            Ok(Inst::RegionEnd(region))
430        }
431        other => Err(ConvertError::UnknownVariant(format!(
432            "unknown inst op: {other}"
433        ))),
434    }
435}
436
437/// Convert a Clojure Value (map) → `Terminator`.
438fn value_to_terminator(val: &Value) -> ConvertResult<Terminator> {
439    let map = as_map(val)?;
440    let op = as_keyword_name(&get_field(map, "op")?)?;
441
442    match op.as_ref() {
443        "jump" => {
444            let target = as_block_id(&get_field(map, "target")?)?;
445            Ok(Terminator::Jump(target))
446        }
447        "branch" => {
448            let cond = as_var_id(&get_field(map, "cond")?)?;
449            let then_block = as_block_id(&get_field(map, "then-block")?)?;
450            let else_block = as_block_id(&get_field(map, "else-block")?)?;
451            Ok(Terminator::Branch {
452                cond,
453                then_block,
454                else_block,
455            })
456        }
457        "return" => {
458            let var = as_var_id(&get_field(map, "var")?)?;
459            Ok(Terminator::Return(var))
460        }
461        "recur-jump" => {
462            let target = as_block_id(&get_field(map, "target")?)?;
463            let args = as_var_id_vec(&get_field(map, "args")?)?;
464            Ok(Terminator::RecurJump { target, args })
465        }
466        "unreachable" => Ok(Terminator::Unreachable),
467        other => Err(ConvertError::UnknownVariant(format!(
468            "unknown terminator op: {other}"
469        ))),
470    }
471}
472
473/// Convert a Clojure Value (map) → `Const`.
474fn value_to_const(val: &Value) -> ConvertResult<Const> {
475    let map = as_map(val)?;
476    let ty = as_keyword_name(&get_field(map, "type")?)?;
477
478    match ty.as_ref() {
479        "nil" => Ok(Const::Nil),
480        "bool" => {
481            let v = get_field(map, "val")?;
482            match v {
483                Value::Bool(b) => Ok(Const::Bool(b)),
484                _ => Err(ConvertError::TypeError(
485                    "expected bool for :bool const".into(),
486                )),
487            }
488        }
489        "long" => {
490            let v = as_long(&get_field(map, "val")?)?;
491            Ok(Const::Long(v))
492        }
493        "double" => {
494            let v = get_field(map, "val")?;
495            match v {
496                Value::Double(d) => Ok(Const::Double(d)),
497                Value::Long(n) => Ok(Const::Double(n as f64)),
498                _ => Err(ConvertError::TypeError(
499                    "expected double for :double const".into(),
500                )),
501            }
502        }
503        "string" => {
504            let v = as_str(&get_field(map, "val")?)?;
505            Ok(Const::Str(v))
506        }
507        "keyword" => {
508            let v = as_str(&get_field(map, "val")?)?;
509            Ok(Const::Keyword(v))
510        }
511        "symbol" => {
512            let v = as_str(&get_field(map, "val")?)?;
513            Ok(Const::Symbol(v))
514        }
515        "char" => {
516            let v = get_field(map, "val")?;
517            match v {
518                Value::Char(c) => Ok(Const::Char(c)),
519                _ => Err(ConvertError::TypeError(
520                    "expected char for :char const".into(),
521                )),
522            }
523        }
524        other => Err(ConvertError::UnknownVariant(format!(
525            "unknown const type: {other}"
526        ))),
527    }
528}
529
530// ── Known function mapping ──────────────────────────────────────────────────
531
532/// Map a keyword name to a `KnownFn` variant.
533pub fn keyword_to_known_fn(kw: &str) -> Option<KnownFn> {
534    match kw {
535        "vector" => Some(KnownFn::Vector),
536        "hash-map" => Some(KnownFn::HashMap),
537        "hash-set" => Some(KnownFn::HashSet),
538        "list" => Some(KnownFn::List),
539        "assoc" => Some(KnownFn::Assoc),
540        "dissoc" => Some(KnownFn::Dissoc),
541        "conj" => Some(KnownFn::Conj),
542        "disj" => Some(KnownFn::Disj),
543        "get" => Some(KnownFn::Get),
544        "nth" => Some(KnownFn::Nth),
545        "count" => Some(KnownFn::Count),
546        "contains" => Some(KnownFn::Contains),
547        "transient" => Some(KnownFn::Transient),
548        "assoc!" => Some(KnownFn::AssocBang),
549        "conj!" => Some(KnownFn::ConjBang),
550        "persistent!" => Some(KnownFn::PersistentBang),
551        "first" => Some(KnownFn::First),
552        "rest" => Some(KnownFn::Rest),
553        "next" => Some(KnownFn::Next),
554        "cons" => Some(KnownFn::Cons),
555        "seq" => Some(KnownFn::Seq),
556        "lazy-seq" => Some(KnownFn::LazySeq),
557        "+" => Some(KnownFn::Add),
558        "-" => Some(KnownFn::Sub),
559        "*" => Some(KnownFn::Mul),
560        "/" => Some(KnownFn::Div),
561        "rem" => Some(KnownFn::Rem),
562        "=" => Some(KnownFn::Eq),
563        "<" => Some(KnownFn::Lt),
564        ">" => Some(KnownFn::Gt),
565        "<=" => Some(KnownFn::Lte),
566        ">=" => Some(KnownFn::Gte),
567        "nil?" => Some(KnownFn::IsNil),
568        "seq?" => Some(KnownFn::IsSeq),
569        "vector?" => Some(KnownFn::IsVector),
570        "map?" => Some(KnownFn::IsMap),
571        "identical?" => Some(KnownFn::Identical),
572        "str" => Some(KnownFn::Str),
573        "deref" => Some(KnownFn::Deref),
574        "println" => Some(KnownFn::Println),
575        "pr" => Some(KnownFn::Pr),
576        "atom-deref" => Some(KnownFn::AtomDeref),
577        "atom-reset" => Some(KnownFn::AtomReset),
578        "atom-swap" => Some(KnownFn::AtomSwap),
579        "apply" => Some(KnownFn::Apply),
580        "try-catch-finally" => Some(KnownFn::TryCatchFinally),
581        "set!-var" => Some(KnownFn::SetBangVar),
582        "with-bindings" => Some(KnownFn::WithBindings),
583        "with-out-str" => Some(KnownFn::WithOutStr),
584        "reduce2" => Some(KnownFn::Reduce2),
585        "reduce3" => Some(KnownFn::Reduce3),
586        "map" => Some(KnownFn::Map),
587        "filter" => Some(KnownFn::Filter),
588        "mapv" => Some(KnownFn::Mapv),
589        "filterv" => Some(KnownFn::Filterv),
590        "some" => Some(KnownFn::Some),
591        "every?" => Some(KnownFn::Every),
592        "into" => Some(KnownFn::Into),
593        "into3" => Some(KnownFn::Into3),
594        "group-by" => Some(KnownFn::GroupBy),
595        "partition2" => Some(KnownFn::Partition2),
596        "partition3" => Some(KnownFn::Partition3),
597        "partition4" => Some(KnownFn::Partition4),
598        "frequencies" => Some(KnownFn::Frequencies),
599        "keep" => Some(KnownFn::Keep),
600        "remove" => Some(KnownFn::Remove),
601        "map-indexed" => Some(KnownFn::MapIndexed),
602        "zipmap" => Some(KnownFn::Zipmap),
603        "juxt" => Some(KnownFn::Juxt),
604        "comp" => Some(KnownFn::Comp),
605        "partial" => Some(KnownFn::Partial),
606        "complement" => Some(KnownFn::Complement),
607        "concat" => Some(KnownFn::Concat),
608        "range1" => Some(KnownFn::Range1),
609        "range2" => Some(KnownFn::Range2),
610        "range3" => Some(KnownFn::Range3),
611        "take" => Some(KnownFn::Take),
612        "drop" => Some(KnownFn::Drop),
613        "reverse" => Some(KnownFn::Reverse),
614        "sort" => Some(KnownFn::Sort),
615        "sort-by" => Some(KnownFn::SortBy),
616        "keys" => Some(KnownFn::Keys),
617        "vals" => Some(KnownFn::Vals),
618        "merge" => Some(KnownFn::Merge),
619        "update" => Some(KnownFn::Update),
620        "get-in" => Some(KnownFn::GetIn),
621        "assoc-in" => Some(KnownFn::AssocIn),
622        "number?" => Some(KnownFn::IsNumber),
623        "string?" => Some(KnownFn::IsString),
624        "keyword?" => Some(KnownFn::IsKeyword),
625        "symbol?" => Some(KnownFn::IsSymbol),
626        "boolean?" => Some(KnownFn::IsBool),
627        "int?" => Some(KnownFn::IsInt),
628        "prn" => Some(KnownFn::Prn),
629        "print" => Some(KnownFn::Print),
630        "atom" => Some(KnownFn::Atom),
631        _ => None,
632    }
633}
634
635/// Map a keyword name to a `RegionAllocKind` variant.
636fn keyword_to_region_alloc_kind(kw: &str) -> Option<RegionAllocKind> {
637    match kw {
638        "vector" => Some(RegionAllocKind::Vector),
639        "map" => Some(RegionAllocKind::Map),
640        "set" => Some(RegionAllocKind::Set),
641        "list" => Some(RegionAllocKind::List),
642        "cons" => Some(RegionAllocKind::Cons),
643        _ => None,
644    }
645}
646
647// ── Helpers ─────────────────────────────────────────────────────────────────
648
649fn as_var_id_vec(val: &Value) -> ConvertResult<Vec<VarId>> {
650    let vec = as_vec(val)?;
651    vec.iter().map(as_var_id).collect()
652}
653
654#[cfg(test)]
655mod tests {
656    use super::*;
657    use cljrs_value::Keyword;
658
659    fn kw(s: &str) -> Value {
660        Value::keyword(Keyword::simple(s))
661    }
662
663    fn make_map(pairs: Vec<(Value, Value)>) -> Value {
664        let mut m = MapValue::empty();
665        for (k, v) in pairs {
666            m = m.assoc(k, v);
667        }
668        Value::Map(m)
669    }
670
671    fn make_vec(items: Vec<Value>) -> Value {
672        use cljrs_gc::GcPtr;
673        use cljrs_value::collections::vector::PersistentVector;
674        Value::Vector(GcPtr::new(PersistentVector::from_iter(items)))
675    }
676
677    #[test]
678    fn test_const_nil() {
679        let val = make_map(vec![(kw("type"), kw("nil"))]);
680        let c = value_to_const(&val).unwrap();
681        assert!(matches!(c, Const::Nil));
682    }
683
684    #[test]
685    fn test_const_long() {
686        let val = make_map(vec![(kw("type"), kw("long")), (kw("val"), Value::Long(42))]);
687        let c = value_to_const(&val).unwrap();
688        assert!(matches!(c, Const::Long(42)));
689    }
690
691    #[test]
692    fn test_terminator_return() {
693        let val = make_map(vec![(kw("op"), kw("return")), (kw("var"), Value::Long(0))]);
694        let t = value_to_terminator(&val).unwrap();
695        assert!(matches!(t, Terminator::Return(VarId(0))));
696    }
697
698    #[test]
699    fn test_inst_const() {
700        let const_map = make_map(vec![(kw("type"), kw("long")), (kw("val"), Value::Long(42))]);
701        let val = make_map(vec![
702            (kw("op"), kw("const")),
703            (kw("dst"), Value::Long(0)),
704            (kw("value"), const_map),
705        ]);
706        let inst = value_to_inst(&val).unwrap();
707        assert!(matches!(inst, Inst::Const(VarId(0), Const::Long(42))));
708    }
709
710    #[test]
711    fn test_simple_ir_function() {
712        // Build: {:name "test" :params [["x" 0]] :blocks [...] :next-var 2 :next-block 1}
713        let const_val = make_map(vec![(kw("type"), kw("long")), (kw("val"), Value::Long(42))]);
714        let inst = make_map(vec![
715            (kw("op"), kw("const")),
716            (kw("dst"), Value::Long(1)),
717            (kw("value"), const_val),
718        ]);
719        let terminator = make_map(vec![(kw("op"), kw("return")), (kw("var"), Value::Long(1))]);
720        let block = make_map(vec![
721            (kw("id"), Value::Long(0)),
722            (kw("phis"), make_vec(vec![])),
723            (kw("insts"), make_vec(vec![inst])),
724            (kw("terminator"), terminator),
725        ]);
726        let param = make_vec(vec![Value::string("x".to_string()), Value::Long(0)]);
727        let ir_val = make_map(vec![
728            (kw("name"), Value::string("test".to_string())),
729            (kw("params"), make_vec(vec![param])),
730            (kw("blocks"), make_vec(vec![block])),
731            (kw("next-var"), Value::Long(2)),
732            (kw("next-block"), Value::Long(1)),
733        ]);
734
735        let ir = value_to_ir_function(&ir_val).unwrap();
736        assert_eq!(ir.name.as_deref(), Some("test"));
737        assert_eq!(ir.params.len(), 1);
738        assert_eq!(ir.params[0].0.as_ref(), "x");
739        assert_eq!(ir.blocks.len(), 1);
740        assert_eq!(ir.next_var, 2);
741        assert_eq!(ir.next_block, 1);
742    }
743}