Skip to main content

runmat_runtime/builtins/table/object/
structure.rs

1use super::*;
2
3pub fn table_from_columns(names: Vec<String>, columns: Vec<Value>) -> BuiltinResult<Value> {
4    table_from_columns_with_properties(names, columns, None)
5}
6
7pub fn table_replace_variables_like(
8    source: &ObjectInstance,
9    variables: StructValue,
10) -> BuiltinResult<Value> {
11    let names = table_variable_names_from_object(source)?;
12    let replacement_names = variables.fields.keys().cloned().collect::<Vec<_>>();
13    if replacement_names != names {
14        return Err(invalid_variable(
15            "table: replacement variables must preserve variable names and order",
16        ));
17    }
18    let columns = names
19        .iter()
20        .filter_map(|name| variables.fields.get(name).cloned())
21        .collect::<Vec<_>>();
22    let height = validate_column_heights(&names, &columns)?;
23    let source_height = table_height(source)?;
24    if height != source_height {
25        return Err(invalid_variable(format!(
26            "table: replacement variables have {height} rows but expected {source_height}"
27        )));
28    }
29    let mut object = source.clone();
30    object
31        .properties
32        .insert(TABLE_VARIABLES_FIELD.to_string(), Value::Struct(variables));
33    Ok(Value::Object(object))
34}
35
36pub(crate) fn table_from_columns_with_properties(
37    names: Vec<String>,
38    columns: Vec<Value>,
39    row_names: Option<Vec<String>>,
40) -> BuiltinResult<Value> {
41    table_from_columns_with_class(TABLE_CLASS, names, columns, row_names)
42}
43
44pub(crate) fn table_from_columns_like(
45    source: &ObjectInstance,
46    names: Vec<String>,
47    columns: Vec<Value>,
48    row_names: Option<Vec<String>>,
49    selected_rows: Option<&[usize]>,
50) -> BuiltinResult<Value> {
51    let mut out =
52        table_from_columns_with_class(source.class_name.as_str(), names, columns, row_names)?;
53    if source.is_class(TIMETABLE_CLASS) {
54        if let Value::Object(object) = &mut out {
55            let row_times = if let Some(rows) = selected_rows {
56                selected_row_times(source, rows)?
57            } else {
58                timetable_row_times(source)?
59            };
60            set_timetable_row_times(object, row_times)?;
61        }
62    }
63    Ok(out)
64}
65
66pub(in crate::builtins::table) fn table_from_columns_with_class(
67    class_name: &str,
68    names: Vec<String>,
69    columns: Vec<Value>,
70    row_names: Option<Vec<String>>,
71) -> BuiltinResult<Value> {
72    ensure_table_class_registered();
73    if names.len() != columns.len() {
74        return Err(invalid_variable(
75            "table: number of variable names must match number of variables",
76        ));
77    }
78    validate_unique_names(&names)?;
79    let height = validate_column_heights(&names, &columns)?;
80    if let Some(row_names) = &row_names {
81        if row_names.len() != height {
82            return Err(invalid_variable(
83                "table: number of row names must match table height",
84            ));
85        }
86    }
87    let mut variables = StructValue::new();
88    for (name, value) in names.iter().cloned().zip(columns) {
89        variables.insert(name, value);
90    }
91    let props = default_properties_for_class(class_name, names, row_names);
92    let mut object = ObjectInstance::new(class_name.to_string());
93    object
94        .properties
95        .insert(TABLE_VARIABLES_FIELD.to_string(), Value::Struct(variables));
96    object.properties.insert(
97        TABLE_PROPERTIES_FIELD.to_string(),
98        Value::Struct(props.clone()),
99    );
100    object
101        .properties
102        .insert(PROPERTIES_MEMBER.to_string(), Value::Struct(props));
103    Ok(Value::Object(object))
104}
105
106pub(super) fn validate_column_heights(names: &[String], columns: &[Value]) -> BuiltinResult<usize> {
107    if columns.is_empty() {
108        return Ok(0);
109    }
110    let height = value_row_count(&columns[0])?;
111    for (name, value) in names.iter().zip(columns) {
112        let rows = value_row_count(value)?;
113        if rows != height {
114            return Err(invalid_variable(format!(
115                "table: variable '{name}' has {rows} rows but expected {height}"
116            )));
117        }
118    }
119    Ok(height)
120}
121
122pub fn is_table_value(value: &Value) -> bool {
123    table_object(value).is_some()
124}
125
126pub fn is_tabular_object(object: &ObjectInstance) -> bool {
127    is_tabular_class(object)
128}
129
130pub(in crate::builtins::table) fn table_object(value: &Value) -> Option<&ObjectInstance> {
131    match value {
132        Value::Object(object) if is_tabular_class(object) => Some(object),
133        _ => None,
134    }
135}
136
137pub(in crate::builtins::table) fn into_table_object(
138    value: Value,
139    context: &str,
140) -> BuiltinResult<ObjectInstance> {
141    match value {
142        Value::Object(object) if is_tabular_class(&object) => Ok(object),
143        other => Err(invalid_argument(format!(
144            "{context}: expected table, got {other:?}"
145        ))),
146    }
147}
148
149pub(in crate::builtins::table) fn into_timetable_object(
150    value: Value,
151    context: &str,
152) -> BuiltinResult<ObjectInstance> {
153    match value {
154        Value::Object(object) if object.is_class(TIMETABLE_CLASS) => Ok(object),
155        other => Err(invalid_argument(format!(
156            "{context}: expected timetable, got {other:?}"
157        ))),
158    }
159}
160
161pub(in crate::builtins::table) fn is_tabular_class(object: &ObjectInstance) -> bool {
162    object.is_class(TABLE_CLASS) || object.is_class(TIMETABLE_CLASS)
163}
164
165pub fn table_variables(object: &ObjectInstance) -> BuiltinResult<StructValue> {
166    match object.properties.get(TABLE_VARIABLES_FIELD) {
167        Some(Value::Struct(st)) => Ok(st.clone()),
168        Some(other) => Err(invalid_variable(format!(
169            "table: invalid internal variable storage {other:?}"
170        ))),
171        None => Ok(StructValue::new()),
172    }
173}
174
175pub fn table_variable_names_from_object(object: &ObjectInstance) -> BuiltinResult<Vec<String>> {
176    let variables = table_variables(object)?;
177    Ok(variables.fields.keys().cloned().collect())
178}
179
180pub fn table_height(object: &ObjectInstance) -> BuiltinResult<usize> {
181    let variables = table_variables(object)?;
182    match variables.fields.values().next() {
183        Some(value) => value_row_count(value),
184        None => Ok(0),
185    }
186}
187
188pub fn table_width(object: &ObjectInstance) -> BuiltinResult<usize> {
189    table_variables(object).map(|vars| vars.fields.len())
190}