runmat_runtime/builtins/table/object/
structure.rs1use 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}