From ca2c85a8a4d826a773ca081604915bea0728d4d8 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 20 Feb 2026 15:10:07 +0100 Subject: [PATCH] Refactor map and record representation Replaces the use of `BTreeMap` and `HashMap` for maps and records with `Vec<(Keyword, Value)>` and `Vec<(Keyword, StaticType)>`. This simplifies the data structure and allows for ordered iteration, which is important for serialization and comparison. Also updates the `unpack` function in `vm.rs` to handle records as well as lists when destructuring. --- examples/data.myc | 2 +- examples/record_to_tuple.myc | 10 ++++++++ src/ast/compiler/type_checker.rs | 8 +++--- src/ast/rtl/type_registry.rs | 20 +++++++-------- src/ast/types.rs | 14 +++++------ src/ast/vm.rs | 43 +++++++++++++++++++++----------- 6 files changed, 60 insertions(+), 37 deletions(-) create mode 100644 examples/record_to_tuple.myc diff --git a/examples/data.myc b/examples/data.myc index fc2da33..82903e5 100644 --- a/examples/data.myc +++ b/examples/data.myc @@ -1,6 +1,6 @@ ;; Complex Data Structure Test ;; Benchmark: 51.4us -;; Output: {:input 10, :name "Fibonacci", :output 55} +;; Output: {:name "Fibonacci", :input 10, :output 55} (do (def fib (fn [n] (if (< n 2) diff --git a/examples/record_to_tuple.myc b/examples/record_to_tuple.myc new file mode 100644 index 0000000..d1f153f --- /dev/null +++ b/examples/record_to_tuple.myc @@ -0,0 +1,10 @@ +;; Record to Tuple Mapping Test +;; Output: 30 +(do + (def rec {:x 10 :y 20}) + + ;; Map record to tuple pattern + (def add-tuple (fn [[a b]] (+ a b))) + + (add-tuple rec) +) diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 2001367..4add772 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -309,13 +309,13 @@ impl TypeChecker { BoundKind::Map { entries } => { let mut typed_entries = Vec::new(); - let mut fields = std::collections::BTreeMap::new(); + let mut fields = Vec::new(); for (k, v) in entries { let kt = self.check_node(k, ctx)?; let vt = self.check_node(v, ctx)?; if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = &kt.kind { - fields.insert(*kw, vt.ty.clone()); + fields.push((*kw, vt.ty.clone())); } typed_entries.push((kt, vt)); @@ -484,8 +484,8 @@ mod tests { let ty = get_ret_type(&typed); if let StaticType::Record(fields) = ty { assert_eq!(fields.len(), 2); - assert_eq!(fields.get(&Keyword::intern("x")), Some(&StaticType::Int)); - assert_eq!(fields.get(&Keyword::intern("y")), Some(&StaticType::Float)); + assert_eq!(fields[0], (Keyword::intern("x"), StaticType::Int)); + assert_eq!(fields[1], (Keyword::intern("y"), StaticType::Float)); } else { panic!("Expected Record, got {:?}", ty); } diff --git a/src/ast/rtl/type_registry.rs b/src/ast/rtl/type_registry.rs index 6e82b84..92a1d06 100644 --- a/src/ast/rtl/type_registry.rs +++ b/src/ast/rtl/type_registry.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, BTreeMap}; +use std::collections::HashMap; use std::rc::Rc; use std::any::{TypeId}; use crate::ast::types::{Value, StaticType, Keyword, Signature}; @@ -74,7 +74,7 @@ impl TypeRegistry { /// Helper to build the shadow record for an instance. /// This is used inside `Scriptable::wrap`. pub struct RecordBuilder { - fields: HashMap, + fields: Vec<(Keyword, Value)>, } impl Default for RecordBuilder { @@ -86,7 +86,7 @@ impl Default for RecordBuilder { impl RecordBuilder { pub fn new() -> Self { Self { - fields: HashMap::new(), + fields: Vec::new(), } } @@ -94,7 +94,7 @@ impl RecordBuilder { where F: Fn(Vec) -> Value + 'static { let key = Keyword::intern(name); - self.fields.insert(key, Value::Function(Rc::new(func))); + self.fields.push((key, Value::Function(Rc::new(func)))); self } @@ -118,7 +118,7 @@ impl RecordBuilder { /// Helper to build the StaticType definition. pub struct TypeBuilder { - fields: BTreeMap, + fields: Vec<(Keyword, StaticType)>, } impl Default for TypeBuilder { @@ -130,14 +130,14 @@ impl Default for TypeBuilder { impl TypeBuilder { pub fn new() -> Self { Self { - fields: BTreeMap::new(), + fields: Vec::new(), } } pub fn method(mut self, name: &str, params: Vec, ret: StaticType) -> Self { let key = Keyword::intern(name); let sig = StaticType::Function(Box::new(Signature { params: StaticType::Tuple(params), ret })); - self.fields.insert(key, sig); + self.fields.push((key, sig)); self } @@ -189,14 +189,14 @@ mod tests { // Check static type let st = TypeRegistry::resolve_type::(®istry); if let StaticType::Record(fields) = st { - assert!(fields.contains_key(&Keyword::intern("greet"))); + assert!(fields.iter().any(|(k, _)| *k == Keyword::intern("greet"))); } else { panic!("Expected Record type"); } // Check runtime behavior if let Value::Record(fields) = wrapped { - let greet_fn = fields.get(&Keyword::intern("greet")).unwrap(); + let greet_fn = &fields.iter().find(|(k, _)| *k == Keyword::intern("greet")).unwrap().1; if let Value::Function(f) = greet_fn { let res = f(vec![]); if let Value::Text(s) = res { @@ -226,7 +226,7 @@ mod tests { if let Value::Function(f) = factory_val { let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]); if let Value::Record(fields) = instance { - let greet_fn = fields.get(&Keyword::intern("greet")).unwrap(); + let greet_fn = &fields.iter().find(|(k, _)| *k == Keyword::intern("greet")).unwrap().1; if let Value::Function(gf) = greet_fn { let res = gf(vec![]); if let Value::Text(s) = res { diff --git a/src/ast/types.rs b/src/ast/types.rs index 8a0deeb..056a070 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, BTreeMap}; +use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; use std::fmt; @@ -66,7 +66,7 @@ pub enum Value { Text(Rc), Keyword(Keyword), List(Rc>), - Record(Rc>), + Record(Rc>), Function(Rc) -> Value>), Object(Rc), // For compiled Closures and other opaque types Cell(Rc>), // Boxed value for captures @@ -93,7 +93,7 @@ pub enum StaticType { Tuple(Vec), // Heterogeneous fixed-size Vector(Box, usize), // Homogeneous fixed-size Matrix(Box, Vec), // Multi-dimensional homogeneous - Record(Rc>), + Record(Rc>), Function(Box), FunctionOverloads(Vec), Object(&'static str), @@ -255,9 +255,9 @@ impl Value { } }, Value::Record(r) => { - let mut fields = BTreeMap::new(); + let mut fields = Vec::with_capacity(r.len()); for (k, v) in r.iter() { - fields.insert(*k, v.static_type()); + fields.push((*k, v.static_type())); } StaticType::Record(Rc::new(fields)) }, @@ -293,10 +293,8 @@ impl fmt::Display for Value { write!(f, "]") }, Value::Record(r) => { - let mut entries: Vec<_> = r.iter().collect(); - entries.sort_by_key(|(k, _)| k.name()); write!(f, "{{")?; - for (i, (k, v)) in entries.iter().enumerate() { + for (i, (k, v)) in r.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, ":{} {}", k.name(), v)?; } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 482c114..5ebc064 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -1,6 +1,5 @@ use std::rc::Rc; use std::cell::RefCell; -use std::collections::HashMap; use std::any::Any; use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; use crate::ast::types::{Value, Object}; @@ -318,13 +317,13 @@ macro_rules! dispatch_eval { }, BoundKind::Map { entries } => { - let mut map = HashMap::new(); + let mut map = Vec::with_capacity(entries.len()); for (k, v) in entries { let key = $self.$eval_method($($observer,)? k)?; let val = $self.$eval_method($($observer,)? v)?; if let Value::Keyword(kw) = key { - map.insert(kw, val); + map.push((kw, val)); } else { return Err(format!("Map key must be keyword, got {}", key)); } @@ -678,19 +677,35 @@ impl VM { Ok(()) } BoundKind::Tuple { elements } => { - // If the current value at offset is a List, we dive into it. - // Otherwise, we assume the list was already flattened (e.g. by Specializer). - if let Some(Value::List(l)) = values.get(*offset) { - *offset += 1; - let mut sub_offset = 0; - for el in elements { - self.unpack(el, l, &mut sub_offset)?; - } - } else { - for el in elements { - self.unpack(el, values, offset)?; + // If the current value at offset is a List or Record, we dive into it. + // Otherwise, we assume the sequence was already flattened (e.g. by Specializer). + if let Some(val) = values.get(*offset) { + match val { + Value::List(l) => { + *offset += 1; + let mut sub_offset = 0; + for el in elements { + self.unpack(el, l, &mut sub_offset)?; + } + return Ok(()); + } + Value::Record(r) => { + *offset += 1; + let mut sub_offset = 0; + // Treat record values as a tuple sequence in definition order + let record_values: Vec<_> = r.iter().map(|(_, v)| v.clone()).collect(); + for el in elements { + self.unpack(el, &record_values, &mut sub_offset)?; + } + return Ok(()); + } + _ => {} } } + + for el in elements { + self.unpack(el, values, offset)?; + } Ok(()) } _ => Err("Invalid node in parameter pattern".to_string()),