1use crate::*;
2#[cfg(feature = "matrix")]
3use crate::matrix::Matrix;
4use indexmap::map::*;
5use std::cmp::Ordering;
6use nalgebra::{DMatrix, DVector, RowDVector};
7use std::collections::{HashMap, HashSet};
8
9#[cfg(feature = "table")]
12#[derive(Clone, Debug)]
13pub struct MechTable {
14 pub rows: usize,
15 pub cols: usize,
16 pub data: IndexMap<u64,(ValueKind,Matrix<Value>)>,
17 pub col_names: HashMap<u64,String>,
18}
19
20#[cfg(feature = "table")]
21impl PartialEq for MechTable {
22 fn eq(&self, other: &Self) -> bool {
23 if self.rows != other.rows {
25 return false;
26 }
27 for (col_id, col_name) in &self.col_names {
29 let other_col_id = match other.col_names.iter().find(|(_, n)| *n == col_name) {
31 Some((id, _)) => *id, None => return false,
33 };
34 let (self_kind, self_column) = match self.data.get(col_id) {
36 Some(entry) => entry,
37 None => return false,
38 };
39 let (other_kind, other_column) = match other.data.get(&other_col_id) {
40 Some(entry) => entry,
41 None => return false,
42 };
43 if self_kind != other_kind {
45 return false;
46 }
47 if self_column != other_column {
49 return false;
50 }
51 }
52 true
53 }
54}
55
56#[cfg(feature = "table")]
57impl Eq for MechTable {}
58
59#[cfg(feature = "table")]
60impl MechTable {
61
62 pub fn from_records(records: Vec<MechRecord>) -> MResult<MechTable> {
63 if records.is_empty() {
64 return Err(MechError { id: line!(), file: file!().to_string(), tokens: vec![], msg: "Cannot create MechTable from empty record list.".to_string(), kind: MechErrorKind::None});
65 }
66
67 let first = &records[0];
68 let rows = records.len();
69 let cols = first.cols;
70
71 let mut col_data: IndexMap<u64, Vec<Value>> = IndexMap::new();
72
73 for (&col_id, value) in &first.data {
74 col_data.insert(col_id, vec![value.clone()]);
75 }
76
77 let mut kinds = IndexMap::new();
78 for (col_id, kind) in first.data.keys().zip(&first.kinds) {
79 kinds.insert(*col_id, kind.clone());
80 }
81
82 let col_names = first.field_names.clone();
83
84 for record in records.iter().skip(1) {
85 first.check_record_schema(record)?;
86 for (&col_id, value) in &record.data {
87 col_data.entry(col_id).or_insert_with(Vec::new).push(value.clone());
88 }
89 }
90
91 let data: IndexMap<u64, (ValueKind, Matrix<Value>)> = col_data
92 .into_iter()
93 .map(|(col_id, values)| {
94 let kind = kinds[&col_id].clone();
95 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(values)));
96 (col_id, (kind, matrix))
97 })
98 .collect();
99
100 Ok(MechTable {rows,cols,data,col_names})
101 }
102
103 pub fn from_kind(kind: ValueKind) -> MResult<MechTable> {
104 match kind {
105 ValueKind::Table(tbl,sze) => {
106 let mut data = IndexMap::new();
107 let mut col_names = HashMap::new();
108 for (col_id, col_kind) in &tbl {
109 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(vec![Value::Empty; sze])));
110 col_names.insert(hash_str(col_id), col_id.clone());
111 data.insert(hash_str(&col_id), (col_kind.clone(), matrix));
112 }
113 Ok(MechTable {rows: sze, cols: tbl.len(), data, col_names})
114 }
115 _ => {
116 return Err(MechError { id: line!(), file: file!().to_string(), tokens: vec![], msg: "Cannot create MechTable from non-table kind.".to_string(), kind: MechErrorKind::None });
117 }
118 }
119 }
120
121 pub fn empty_table(&self, rows: usize) -> MechTable {
122 let mut data = IndexMap::new();
123 for col in self.data.iter() {
124 let (key, (kind, matrix)) = col;
125 let elements = vec![Value::Empty; rows];
127 let new_matrix = Matrix::DVector(Ref::new(DVector::from_vec(elements)));
128 data.insert(*key, (kind.clone(), new_matrix));
129 }
130 MechTable { rows: rows, cols: self.cols, data, col_names: self.col_names.clone() }
131 }
132
133 pub fn check_record_schema(&self, record: &MechRecord) -> MResult<bool> {
134
135 for (&col_id, record_value) in &record.data {
136 let (expected_kind, column_matrix) = match self.data.get(&col_id) {
139 Some(data) => data,
140 None => {
141 continue;
142 }
143 };
144
145 let actual_kind = record_value.kind();
147
148 if expected_kind != &actual_kind {
149 return Err(MechError {id: line!(),file: file!().to_string(),tokens: vec![],msg: format!("Schema mismatch: column {} kind mismatch (expected: {:?}, found: {:?})",col_id, expected_kind, actual_kind),kind: MechErrorKind::None,});
150 }
151
152 if let Some(expected_name) = self.col_names.get(&col_id) {
154 if let Some(field_name) = record.field_names.get(&col_id) {
155 if expected_name != field_name {
156 return Err(MechError {id: line!(),file: file!().to_string(),tokens: vec![],msg: format!("Schema mismatch: column {} name mismatch (expected: '{}', found: '{}')",col_id, expected_name, field_name),kind: MechErrorKind::None,});
157 }
158 }
159 }
160 }
161
162 Ok(true)
163 }
164
165 pub fn check_table_schema(&self, record: &MechTable) -> MResult<bool> {
166
167 for (&col_id, col_name) in &self.col_names {
169 if let Some(record_name) = record.col_names.get(&col_id) {
170 if col_name != record_name {
171 return Err(MechError {id: line!(),file: file!().to_string(),tokens: vec![],msg: format!("Schema mismatch: column {} name mismatch (expected: '{}', found: '{}')",col_id, col_name, record_name),kind: MechErrorKind::None,});
172 }
173 } else {
174 return Err(MechError {id: line!(),file: file!().to_string(),tokens: vec![],msg: format!("Schema mismatch: column {} not found in record",col_id),kind: MechErrorKind::None,});
175 }
176 }
177
178 for (&col_id, (expected_kind, _)) in &self.data {
180 if let Some((record_kind, _)) = record.data.get(&col_id) {
181 if expected_kind != record_kind {
182 return Err(MechError {id: line!(),file: file!().to_string(),tokens: vec![],msg: format!("Schema mismatch: column {} kind mismatch (expected: {:?}, found: {:?})",col_id, expected_kind, record_kind),kind: MechErrorKind::None,});
183 }
184 } else {
185 return Err(MechError {id: line!(),file: file!().to_string(),tokens: vec![],msg: format!("Schema mismatch: column {} not found in record",col_id),kind: MechErrorKind::None,});
186 }
187 }
188
189 Ok(true)
190 }
191
192 pub fn append_table(&mut self, other: &MechTable) -> MResult<()> {
193 self.check_table_schema(other)?;
194 for (&col_id, (_, other_matrix)) in &other.data {
195 let (_, self_matrix) = self.data.get_mut(&col_id).ok_or(MechError {
196 id: line!(),
197 file: file!().to_string(),
198 tokens: vec![],
199 msg: format!("Column {} not found in destination table", col_id),
200 kind: MechErrorKind::None,
201 })?;
202
203 self_matrix.append(other_matrix).map_err(|err| MechError {
204 id: line!(),
205 file: file!().to_string(),
206 tokens: vec![],
207 msg: "".to_string(),
208 kind: MechErrorKind::None,
209 })?;
210 }
211 self.rows += other.rows;
212 Ok(())
213 }
214
215 pub fn append_record(&mut self, record: MechRecord) -> MResult<()> {
216 self.check_record_schema(&record)?;
218
219 for (&col_id, value) in &record.data {
221 if let Some((_kind, column_matrix)) = self.data.get_mut(&col_id) {
222 let result = column_matrix.push(value.clone());
223 } else {
224 continue;
225 }
226 }
227
228 self.rows += 1;
230
231 Ok(())
232 }
233
234 pub fn get_record(&self, ix: usize) -> Option<MechRecord> {
235 if ix > self.rows {
236 return None;
237 }
238
239 let mut data: IndexMap<u64, Value> = IndexMap::new();
240 data = self.data.iter().map(|(key, (kind, matrix))| {
241 let value = matrix.index1d(ix);
242 let name = self.col_names.get(key).unwrap();
243 (hash_str(name), value.clone())
244 }).collect();
245
246 let mut kinds = Vec::with_capacity(self.cols);
247 kinds = self.data.iter().map(|(_, (kind, _))| kind.clone()).collect();
248
249 let mut field_names = self.col_names.clone();
250
251 Some(MechRecord{cols: self.cols, kinds, data, field_names})
252 }
253
254 #[cfg(feature = "pretty_print")]
255 pub fn to_html(&self) -> String {
256 let mut html = String::new();
257
258 html.push_str("<table class=\"mech-table\">");
260
261 html.push_str("<thead class=\"mech-table-header\"><tr>");
263 for (key, (kind, _matrix)) in self.data.iter() {
264 let col_name = self
265 .col_names
266 .get(key)
267 .cloned()
268 .unwrap_or_else(|| key.to_string());
269
270 let kind_str = format!(
271 "<span class=\"mech-kind-annotation\"><<span class=\"mech-kind\">{}</span>></span>",
272 kind
273 );
274
275 html.push_str(&format!(
276 "<th class=\"mech-table-field\">\
277 <div class=\"mech-field\">\
278 <span class=\"mech-field-name\">{}</span>\
279 <span class=\"mech-field-kind\">{}</span>\
280 </div>\
281 </th>",
282 col_name, kind_str
283 ));
284 }
285 html.push_str("</tr></thead>");
286
287 html.push_str("<tbody class=\"mech-table-body\">");
289 for row_idx in 1..=self.rows {
290 html.push_str("<tr class=\"mech-table-row\">");
291 for (_key, (_kind, matrix)) in self.data.iter() {
292 let value = matrix.index1d(row_idx);
293 html.push_str(&format!(
294 "<td class=\"mech-table-column\">{}</td>",
295 value.to_html()
296 ));
297 }
298 html.push_str("</tr>");
299 }
300 html.push_str("</tbody></table>");
301 html
302 }
303
304 pub fn new_table(names: Vec<String>, kinds: Vec<ValueKind>, cols: Vec<Vec<Value>>) -> MechTable {
305 let col_count = names.len();
306 let row_count = if !cols.is_empty() { cols[0].len() } else { 0 };
307 let mut col_names = HashMap::new();
308 for (i, name) in names.iter().enumerate() {
309 col_names.insert(i as u64, name.clone());
310 }
311 let mut data = IndexMap::new();
312 for (col_idx, (kind, values)) in kinds.iter().zip(cols.iter()).enumerate() {
313 assert_eq!(
314 values.len(),
315 row_count,
316 "Column {} has inconsistent length (expected {} rows, got {})",
317 col_idx,
318 row_count,
319 values.len()
320 );
321 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(values.clone())));
322 data.insert(col_idx as u64, (kind.clone(), matrix));
323 }
324 MechTable::new(row_count, col_count, data, col_names)
325 }
326
327 pub fn new(rows: usize, cols: usize, data: IndexMap<u64,(ValueKind,Matrix<Value>)>, col_names: HashMap<u64,String>) -> MechTable {
328 MechTable{rows, cols, data, col_names}
329 }
330
331 pub fn kind(&self) -> ValueKind {
332 let column_kinds: Vec<(String, ValueKind)> = self.data.iter()
333 .filter_map(|(key, (kind, _))| {
334 let col_name = self.col_names.get(key)?;
335 Some((col_name.clone(), kind.clone()))
336 })
337 .collect();
338 ValueKind::Table(column_kinds, self.rows)
339 }
340
341 pub fn size_of(&self) -> usize {
342 self.data.iter().map(|(_,(_,v))| v.size_of()).sum()
343 }
344
345 pub fn rows(&self) -> usize {
346 self.rows
347 }
348
349 pub fn cols(&self) -> usize {
350 self.cols
351 }
352
353 pub fn get(&self, key: &u64) -> Option<&(ValueKind,Matrix<Value>)> {
354 self.data.get(key)
355 }
356
357 pub fn shape(&self) -> Vec<usize> {
358 vec![self.rows,self.cols]
359 }
360}
361
362#[cfg(feature = "pretty_print")]
363impl PrettyPrint for MechTable {
364 fn pretty_print(&self) -> String {
365 let mut builder = Builder::default();
366 for (k,(knd,val)) in &self.data {
367 let name = self.col_names.get(k).unwrap();
368 let val_string: String = val.as_vec().iter()
369 .map(|x| x.pretty_print())
370 .collect::<Vec<String>>()
371 .join("\n");
372 let mut col_string = vec![format!("{}<{}>", name.to_string(), knd), val_string];
373 builder.push_column(col_string);
374 }
375 let mut table = builder.build();
376 table.with(Style::modern_rounded());
377 format!("{table}")
378 }
379}
380
381#[cfg(feature = "table")]
382impl Hash for MechTable {
383 fn hash<H: Hasher>(&self, state: &mut H) {
384 for (k,(knd,val)) in self.data.iter() {
385 k.hash(state);
386 knd.hash(state);
387 val.hash(state);
388 }
389 }
390}