Refactor Record and List to use TupleData

The `Value::List` and `Value::Record` variants have been consolidated
into a single `Value::Tuple` variant. This new variant uses a
`TupleData` struct to store values and an optional `Rc<Vec<Keyword>>`
for keys, allowing it to represent both ordered lists/tuples and
key-value records.

This change simplifies the internal representation and improves
performance by allowing schema sharing for records. It also includes
updates to the compiler, runtime, and tests to reflect the new
structure.
This commit is contained in:
Michael Schimmel
2026-02-20 16:13:38 +01:00
parent ca2c85a8a4
commit 87259584ee
6 changed files with 148 additions and 96 deletions
+67 -47
View File
@@ -55,6 +55,14 @@ pub trait Object: fmt::Debug {
fn as_any(&self) -> &dyn Any;
}
/// Internal storage for Tuples (and Records) to allow sharing schema (keys) between instances.
#[derive(Debug, Clone)]
pub struct TupleData {
pub values: Vec<Value>,
/// Optional names for slots. If present, this is semantically a Record.
pub keys: Option<Rc<Vec<Keyword>>>,
}
/// Core data value in Myc Script (similar to TDataValue)
#[derive(Clone)]
pub enum Value {
@@ -65,8 +73,7 @@ pub enum Value {
DateTime(i64),
Text(Rc<str>),
Keyword(Keyword),
List(Rc<Vec<Value>>),
Record(Rc<Vec<(Keyword, Value)>>),
Tuple(Rc<TupleData>), // Replaces List and Record
Function(Rc<dyn Fn(Vec<Value>) -> Value>),
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
Cell(Rc<RefCell<Value>>), // Boxed value for captures
@@ -217,6 +224,14 @@ impl Value {
}
}
pub fn make_list(values: Vec<Value>) -> Self {
Value::Tuple(Rc::new(TupleData { values, keys: None }))
}
pub fn make_record(keys: Vec<Keyword>, values: Vec<Value>) -> Self {
Value::Tuple(Rc::new(TupleData { values, keys: Some(Rc::new(keys)) }))
}
pub fn static_type(&self) -> StaticType {
match self {
Value::Void => StaticType::Void,
@@ -226,41 +241,45 @@ impl Value {
Value::DateTime(_) => StaticType::DateTime,
Value::Text(_) => StaticType::Text,
Value::Keyword(_) => StaticType::Keyword,
Value::List(l) => {
if l.is_empty() {
return StaticType::Vector(Box::new(StaticType::Any), 0);
}
let element_types: Vec<_> = l.iter().map(|v| v.static_type()).collect();
// Check for Homogeneity (Vector)
let first_ty = &element_types[0];
let all_same = element_types.iter().all(|t| t == first_ty);
if all_same {
match first_ty {
StaticType::Vector(inner, len) => {
// Possible Matrix
StaticType::Matrix(inner.clone(), vec![l.len(), *len])
},
StaticType::Matrix(inner, shape) => {
let mut new_shape = vec![l.len()];
new_shape.extend(shape);
StaticType::Matrix(inner.clone(), new_shape)
},
_ => StaticType::Vector(Box::new(first_ty.clone()), l.len())
Value::Tuple(t) => {
if let Some(keys) = &t.keys {
// It's a Record
let mut fields = Vec::with_capacity(t.values.len());
for (i, v) in t.values.iter().enumerate() {
fields.push((keys[i], v.static_type()));
}
StaticType::Record(Rc::new(fields))
} else {
StaticType::Tuple(element_types)
// It's a List/Tuple
let l = &t.values;
if l.is_empty() {
return StaticType::Vector(Box::new(StaticType::Any), 0);
}
let element_types: Vec<_> = l.iter().map(|v| v.static_type()).collect();
// Check for Homogeneity (Vector)
let first_ty = &element_types[0];
let all_same = element_types.iter().all(|t| t == first_ty);
if all_same {
match first_ty {
StaticType::Vector(inner, len) => {
// Possible Matrix
StaticType::Matrix(inner.clone(), vec![l.len(), *len])
},
StaticType::Matrix(inner, shape) => {
let mut new_shape = vec![l.len()];
new_shape.extend(shape);
StaticType::Matrix(inner.clone(), new_shape)
},
_ => StaticType::Vector(Box::new(first_ty.clone()), l.len())
}
} else {
StaticType::Tuple(element_types)
}
}
},
Value::Record(r) => {
let mut fields = Vec::with_capacity(r.len());
for (k, v) in r.iter() {
fields.push((*k, v.static_type()));
}
StaticType::Record(Rc::new(fields))
},
Value::Function(_) => StaticType::Any, // Dynamic function
Value::Object(o) => StaticType::Object(o.type_name()),
Value::Cell(c) => c.borrow().static_type(),
@@ -284,21 +303,22 @@ impl fmt::Display for Value {
},
Value::Text(t) => write!(f, "\"{}\"", t),
Value::Keyword(k) => write!(f, ":{}", k.name()),
Value::List(l) => {
write!(f, "[")?;
for (i, val) in l.iter().enumerate() {
if i > 0 { write!(f, " ")?; }
write!(f, "{}", val)?;
Value::Tuple(t) => {
if let Some(keys) = &t.keys {
write!(f, "{{")?;
for i in 0..t.values.len() {
if i > 0 { write!(f, ", ")?; }
write!(f, ":{} {}", keys[i].name(), t.values[i])?;
}
write!(f, "}}")
} else {
write!(f, "[")?;
for (i, val) in t.values.iter().enumerate() {
if i > 0 { write!(f, " ")?; }
write!(f, "{}", val)?;
}
write!(f, "]")
}
write!(f, "]")
},
Value::Record(r) => {
write!(f, "{{")?;
for (i, (k, v)) in r.iter().enumerate() {
if i > 0 { write!(f, ", ")?; }
write!(f, ":{} {}", k.name(), v)?;
}
write!(f, "}}")
},
Value::Function(_) => write!(f, "<native fn>"),
Value::Object(o) => write!(f, "<{}>", o.type_name()),