Skip to main content

runmat_core/
value_metadata.rs

1use runmat_builtins::{LogicalArray, Value};
2
3/// MATLAB-style class name for a runtime value.
4pub fn matlab_class_name(value: &Value) -> String {
5    match value {
6        Value::Num(_) | Value::ComplexTensor(_) | Value::Complex(_, _) => "double".to_string(),
7        Value::Tensor(tensor) => tensor.dtype.class_name().to_string(),
8        Value::Int(iv) => iv.class_name().to_string(),
9        Value::Bool(_) | Value::LogicalArray(_) => "logical".to_string(),
10        Value::String(_) | Value::StringArray(_) => "string".to_string(),
11        Value::CharArray(_) => "char".to_string(),
12        Value::Cell(_) => "cell".to_string(),
13        Value::Struct(_) => "struct".to_string(),
14        Value::GpuTensor(_) => "gpuArray".to_string(),
15        Value::FunctionHandle(_) | Value::Closure(_) => "function_handle".to_string(),
16        Value::HandleObject(handle) => {
17            if handle.class_name.is_empty() {
18                "handle".to_string()
19            } else {
20                handle.class_name.clone()
21            }
22        }
23        Value::Listener(_) => "event.listener".to_string(),
24        // Internal destructuring helper; shouldn't surface in user-facing values,
25        // but handle it defensively for completeness.
26        Value::OutputList(_) => "OutputList".to_string(),
27        Value::Object(obj) => obj.class_name.clone(),
28        Value::ClassRef(_) => "meta.class".to_string(),
29        Value::MException(_) => "MException".to_string(),
30    }
31}
32
33/// Returns the MATLAB-style shape for the provided value when applicable.
34pub fn value_shape(value: &Value) -> Option<Vec<usize>> {
35    match value {
36        Value::Num(_) | Value::Int(_) | Value::Bool(_) | Value::Complex(_, _) => Some(vec![1, 1]),
37        Value::LogicalArray(arr) => Some(arr.shape.clone()),
38        Value::StringArray(sa) => Some(sa.shape.clone()),
39        Value::String(s) => Some(vec![1, s.chars().count()]),
40        Value::CharArray(ca) => Some(vec![ca.rows, ca.cols]),
41        Value::Tensor(t) => Some(t.shape.clone()),
42        Value::ComplexTensor(t) => Some(t.shape.clone()),
43        Value::Cell(ca) => Some(ca.shape.clone()),
44        Value::GpuTensor(handle) => Some(handle.shape.clone()),
45        Value::Object(obj) if obj.is_class("datetime") => match obj.properties.get("__serial") {
46            Some(Value::Tensor(tensor)) => Some(tensor.shape.clone()),
47            Some(Value::Num(_)) => Some(vec![1, 1]),
48            _ => None,
49        },
50        _ => None,
51    }
52}
53
54/// Returns a MATLAB dtype label for numeric values when available.
55pub fn numeric_dtype_label(value: &Value) -> Option<&'static str> {
56    match value {
57        Value::Num(_) | Value::Complex(_, _) => Some("double"),
58        Value::Tensor(t) => Some(t.dtype.class_name()),
59        Value::LogicalArray(_) => Some("logical"),
60        Value::Int(iv) => Some(iv.class_name()),
61        _ => None,
62    }
63}
64
65/// Rough estimate of the in-memory footprint for the provided value, in bytes.
66pub fn approximate_size_bytes(value: &Value) -> Option<u64> {
67    Some(match value {
68        Value::Num(_) | Value::Int(_) | Value::Complex(_, _) => 8,
69        Value::Bool(_) => 1,
70        Value::LogicalArray(arr) => arr.data.len() as u64,
71        Value::Tensor(t) => (t.data.len() * 8) as u64,
72        Value::ComplexTensor(t) => (t.data.len() * 16) as u64,
73        Value::String(s) => s.len() as u64,
74        Value::StringArray(sa) => sa.data.iter().map(|s| s.len() as u64).sum(),
75        Value::CharArray(ca) => (ca.rows * ca.cols) as u64,
76        _ => return None,
77    })
78}
79
80/// Produce a numeric preview (up to `limit` elements) for scalars and dense arrays.
81pub fn preview_numeric_values(value: &Value, limit: usize) -> Option<(Vec<f64>, bool)> {
82    match value {
83        Value::Num(n) => Some((vec![*n], false)),
84        Value::Int(iv) => Some((vec![iv.to_f64()], false)),
85        Value::Bool(flag) => Some((vec![if *flag { 1.0 } else { 0.0 }], false)),
86        Value::Tensor(t) => Some(preview_f64_slice(&t.data, limit)),
87        Value::LogicalArray(arr) => Some(preview_logical_slice(arr, limit)),
88        Value::StringArray(_) | Value::String(_) | Value::CharArray(_) => None,
89        Value::ComplexTensor(_) | Value::Complex(_, _) => None,
90        Value::Cell(_)
91        | Value::Struct(_)
92        | Value::Object(_)
93        | Value::HandleObject(_)
94        | Value::Listener(_)
95        | Value::OutputList(_)
96        | Value::FunctionHandle(_)
97        | Value::Closure(_)
98        | Value::ClassRef(_)
99        | Value::MException(_)
100        | Value::GpuTensor(_) => None,
101    }
102}
103
104fn preview_f64_slice(data: &[f64], limit: usize) -> (Vec<f64>, bool) {
105    if data.len() > limit {
106        (data[..limit].to_vec(), true)
107    } else {
108        (data.to_vec(), false)
109    }
110}
111
112fn preview_logical_slice(arr: &LogicalArray, limit: usize) -> (Vec<f64>, bool) {
113    let truncated = arr.data.len() > limit;
114    let mut preview = Vec::with_capacity(arr.data.len().min(limit));
115    for value in arr.data.iter().take(limit) {
116        preview.push(if *value == 0 { 0.0 } else { 1.0 });
117    }
118    (preview, truncated)
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use runmat_builtins::{NumericDType, ObjectInstance, Tensor};
125
126    #[test]
127    fn approximate_size_bytes_uses_f64_width_for_integer_dtypes() {
128        // Tensor.data is always Vec<f64> (8 bytes/element) regardless of dtype.
129        let u8_tensor = Tensor::new_with_dtype(vec![1.0, 2.0, 3.0], vec![3, 1], NumericDType::U8)
130            .expect("tensor");
131        let u16_tensor = Tensor::new_with_dtype(vec![1.0, 2.0, 3.0], vec![3, 1], NumericDType::U16)
132            .expect("tensor");
133        let f32_tensor = Tensor::new_with_dtype(vec![1.0, 2.0, 3.0], vec![3, 1], NumericDType::F32)
134            .expect("tensor");
135
136        assert_eq!(approximate_size_bytes(&Value::Tensor(u8_tensor)), Some(24));
137        assert_eq!(approximate_size_bytes(&Value::Tensor(u16_tensor)), Some(24));
138        assert_eq!(approximate_size_bytes(&Value::Tensor(f32_tensor)), Some(24));
139    }
140
141    #[test]
142    fn datetime_object_shape_comes_from_internal_serial_tensor() {
143        let mut object = ObjectInstance::new("datetime".to_string());
144        object.properties.insert(
145            "__serial".to_string(),
146            Value::Tensor(Tensor::new(vec![739351.0, 739352.0], vec![2, 1]).expect("tensor")),
147        );
148
149        assert_eq!(value_shape(&Value::Object(object)), Some(vec![2, 1]));
150    }
151}