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(
65 MechError2::new(
66 CannotCreateTableFromEmptyRecordListError,
67 None
68 ).with_compiler_loc()
69 );
70 }
71
72 let first = &records[0];
73 let rows = records.len();
74 let cols = first.cols;
75
76 let mut col_data: IndexMap<u64, Vec<Value>> = IndexMap::new();
77
78 for (&col_id, value) in &first.data {
79 col_data.insert(col_id, vec![value.clone()]);
80 }
81
82 let mut kinds = IndexMap::new();
83 for (col_id, kind) in first.data.keys().zip(&first.kinds) {
84 kinds.insert(*col_id, kind.clone());
85 }
86
87 let col_names = first.field_names.clone();
88
89 for record in records.iter().skip(1) {
90 first.check_record_schema(record)?;
91 for (&col_id, value) in &record.data {
92 col_data.entry(col_id).or_insert_with(Vec::new).push(value.clone());
93 }
94 }
95
96 let data: IndexMap<u64, (ValueKind, Matrix<Value>)> = col_data
97 .into_iter()
98 .map(|(col_id, values)| {
99 let kind = kinds[&col_id].clone();
100 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(values)));
101 (col_id, (kind, matrix))
102 })
103 .collect();
104
105 Ok(MechTable {rows,cols,data,col_names})
106 }
107
108 pub fn from_kind(kind: ValueKind) -> MResult<MechTable> {
109 match kind {
110 ValueKind::Table(tbl, sze) => {
111 let mut data = IndexMap::new();
112 let mut col_names = HashMap::new();
113 for (col_id, col_kind) in &tbl {
114 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(vec![Value::Empty; sze])));
115 col_names.insert(hash_str(col_id), col_id.clone());
116 data.insert(hash_str(&col_id), (col_kind.clone(), matrix));
117 }
118 Ok(MechTable { rows: sze, cols: tbl.len(), data, col_names })
119 }
120 _ => {
121 return Err(
122 MechError2::new(
123 CannotCreateTableFromNonTableKindError,
124 None
125 ).with_compiler_loc()
126 );
127 }
128 }
129 }
130
131 pub fn empty_table(&self, rows: usize) -> MechTable {
132 let mut data = IndexMap::new();
133 for col in self.data.iter() {
134 let (key, (kind, matrix)) = col;
135 let elements = vec![Value::Empty; rows];
137 let new_matrix = Matrix::DVector(Ref::new(DVector::from_vec(elements)));
138 data.insert(*key, (kind.clone(), new_matrix));
139 }
140 MechTable { rows: rows, cols: self.cols, data, col_names: self.col_names.clone() }
141 }
142
143 pub fn check_record_schema(&self, record: &MechRecord) -> MResult<bool> {
144 for (&col_id, record_value) in &record.data {
145 let (expected_kind, _column_matrix) = match self.data.get(&col_id) {
147 Some(data) => data,
148 None => continue,
149 };
150
151 let actual_kind = record_value.kind();
153 if expected_kind != &actual_kind {
154 return Err(
155 MechError2::new(
156 TableColumnKindMismatchError {
157 column_id: col_id,
158 expected_kind: expected_kind.clone(),
159 actual_kind: actual_kind.clone(),
160 },
161 None
162 ).with_compiler_loc()
163 );
164 }
165
166 if let Some(expected_name) = self.col_names.get(&col_id) {
168 if let Some(field_name) = record.field_names.get(&col_id) {
169 if expected_name != field_name {
170 return Err(
171 MechError2::new(
172 TableColumnNameMismatchError {
173 column_id: col_id,
174 expected_name: expected_name.clone(),
175 actual_name: field_name.clone(),
176 },
177 None
178 ).with_compiler_loc()
179 );
180 }
181 }
182 }
183 }
184
185 Ok(true)
186 }
187
188 pub fn check_table_schema(&self, record: &MechTable) -> MResult<bool> {
189 for (&col_id, col_name) in &self.col_names {
191 match record.col_names.get(&col_id) {
192 Some(record_name) if col_name != record_name => {
193 return Err(MechError2::new(
194 TableColumnNameMismatchError {
195 column_id: col_id,
196 expected_name: col_name.clone(),
197 actual_name: record_name.clone(),
198 },
199 None
200 ).with_compiler_loc());
201 }
202 None => {
203 return Err(MechError2::new(
204 TableColumnNotFoundError { column_id: col_id },
205 None
206 ).with_compiler_loc());
207 }
208 _ => {}
209 }
210 }
211
212 for (&col_id, (expected_kind, _)) in &self.data {
214 match record.data.get(&col_id) {
215 Some((record_kind, _)) if expected_kind != record_kind => {
216 return Err(MechError2::new(
217 TableColumnKindMismatchError {
218 column_id: col_id,
219 expected_kind: expected_kind.clone(),
220 actual_kind: record_kind.clone(),
221 },
222 None
223 ).with_compiler_loc());
224 }
225 None => {
226 return Err(MechError2::new(
227 TableColumnNotFoundError { column_id: col_id },
228 None
229 ).with_compiler_loc());
230 }
231 _ => {}
232 }
233 }
234
235 Ok(true)
236 }
237
238 pub fn append_table(&mut self, other: &MechTable) -> MResult<()> {
239 self.check_table_schema(other)?;
240
241 for (&col_id, (_, other_matrix)) in &other.data {
242 let (_, self_matrix) = self.data.get_mut(&col_id).ok_or_else(||
243 MechError2::new(
244 TableColumnNotFoundError { column_id: col_id },
245 None
246 ).with_compiler_loc()
247 )?;
248
249 self_matrix.append(other_matrix).map_err(|err|
250 MechError2::new(
251 MatrixAppendToTableError { column_id: col_id },
252 None
253 ).with_compiler_loc()
254 )?;
255 }
256
257 self.rows += other.rows;
258 Ok(())
259 }
260
261 pub fn append_record(&mut self, record: MechRecord) -> MResult<()> {
262 self.check_record_schema(&record)?;
264
265 for (&col_id, value) in &record.data {
267 if let Some((_kind, column_matrix)) = self.data.get_mut(&col_id) {
268 let result = column_matrix.push(value.clone());
269 } else {
270 continue;
271 }
272 }
273
274 self.rows += 1;
276
277 Ok(())
278 }
279
280 pub fn get_record(&self, ix: usize) -> Option<MechRecord> {
281 if ix > self.rows {
282 return None;
283 }
284
285 let mut data: IndexMap<u64, Value> = IndexMap::new();
286 data = self.data.iter().map(|(key, (kind, matrix))| {
287 let value = matrix.index1d(ix);
288 let name = self.col_names.get(key).unwrap();
289 (hash_str(name), value.clone())
290 }).collect();
291
292 let mut kinds = Vec::with_capacity(self.cols);
293 kinds = self.data.iter().map(|(_, (kind, _))| kind.clone()).collect();
294
295 let mut field_names = self.col_names.clone();
296
297 Some(MechRecord{cols: self.cols, kinds, data, field_names})
298 }
299
300 #[cfg(feature = "pretty_print")]
301 pub fn to_html(&self) -> String {
302 let mut html = String::new();
303
304 html.push_str("<table class=\"mech-table\">");
306
307 html.push_str("<thead class=\"mech-table-header\"><tr>");
309 for (key, (kind, _matrix)) in self.data.iter() {
310 let col_name = self
311 .col_names
312 .get(key)
313 .cloned()
314 .unwrap_or_else(|| key.to_string());
315
316 let kind_str = format!(
317 "<span class=\"mech-kind-annotation\"><<span class=\"mech-kind\">{}</span>></span>",
318 kind
319 );
320
321 html.push_str(&format!(
322 "<th class=\"mech-table-field\">\
323 <div class=\"mech-field\">\
324 <span class=\"mech-field-name\">{}</span>\
325 <span class=\"mech-field-kind\">{}</span>\
326 </div>\
327 </th>",
328 col_name, kind_str
329 ));
330 }
331 html.push_str("</tr></thead>");
332
333 html.push_str("<tbody class=\"mech-table-body\">");
335 for row_idx in 1..=self.rows {
336 html.push_str("<tr class=\"mech-table-row\">");
337 for (_key, (_kind, matrix)) in self.data.iter() {
338 let value = matrix.index1d(row_idx);
339 html.push_str(&format!(
340 "<td class=\"mech-table-column\">{}</td>",
341 value.to_html()
342 ));
343 }
344 html.push_str("</tr>");
345 }
346 html.push_str("</tbody></table>");
347 html
348 }
349
350 pub fn new_table(names: Vec<String>, kinds: Vec<ValueKind>, cols: Vec<Vec<Value>>) -> MechTable {
351 let col_count = names.len();
352 let row_count = if !cols.is_empty() { cols[0].len() } else { 0 };
353 let mut col_names = HashMap::new();
354 for (i, name) in names.iter().enumerate() {
355 col_names.insert(i as u64, name.clone());
356 }
357 let mut data = IndexMap::new();
358 for (col_idx, (kind, values)) in kinds.iter().zip(cols.iter()).enumerate() {
359 assert_eq!(
360 values.len(),
361 row_count,
362 "Column {} has inconsistent length (expected {} rows, got {})",
363 col_idx,
364 row_count,
365 values.len()
366 );
367 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(values.clone())));
368 data.insert(col_idx as u64, (kind.clone(), matrix));
369 }
370 MechTable::new(row_count, col_count, data, col_names)
371 }
372
373 pub fn new(rows: usize, cols: usize, data: IndexMap<u64,(ValueKind,Matrix<Value>)>, col_names: HashMap<u64,String>) -> MechTable {
374 MechTable{rows, cols, data, col_names}
375 }
376
377 pub fn kind(&self) -> ValueKind {
378 let column_kinds: Vec<(String, ValueKind)> = self.data.iter()
379 .filter_map(|(key, (kind, _))| {
380 let col_name = self.col_names.get(key)?;
381 Some((col_name.clone(), kind.clone()))
382 })
383 .collect();
384 ValueKind::Table(column_kinds, self.rows)
385 }
386
387 pub fn size_of(&self) -> usize {
388 self.data.iter().map(|(_,(_,v))| v.size_of()).sum()
389 }
390
391 pub fn rows(&self) -> usize {
392 self.rows
393 }
394
395 pub fn cols(&self) -> usize {
396 self.cols
397 }
398
399 pub fn get(&self, key: &u64) -> Option<&(ValueKind,Matrix<Value>)> {
400 self.data.get(key)
401 }
402
403 pub fn shape(&self) -> Vec<usize> {
404 vec![self.rows,self.cols]
405 }
406}
407
408#[cfg(feature = "pretty_print")]
409impl PrettyPrint for MechTable {
410 fn pretty_print(&self) -> String {
411 let mut builder = Builder::default();
412 for (k,(knd,val)) in &self.data {
413 let name = self.col_names.get(k).unwrap();
414 let val_string: String = val.as_vec().iter()
415 .map(|x| x.pretty_print())
416 .collect::<Vec<String>>()
417 .join("\n");
418 let mut col_string = vec![format!("{}<{}>", name.to_string(), knd), val_string];
419 builder.push_column(col_string);
420 }
421 let mut table = builder.build();
422 table.with(Style::modern_rounded());
423 format!("{table}")
424 }
425}
426
427#[cfg(feature = "table")]
428impl Hash for MechTable {
429 fn hash<H: Hasher>(&self, state: &mut H) {
430 for (k,(knd,val)) in self.data.iter() {
431 k.hash(state);
432 knd.hash(state);
433 val.hash(state);
434 }
435 }
436}
437
438#[derive(Debug, Clone)]
439pub struct CannotCreateTableFromEmptyRecordListError;
440
441impl MechErrorKind2 for CannotCreateTableFromEmptyRecordListError {
442 fn name(&self) -> &str {
443 "EmptyRecordList"
444 }
445 fn message(&self) -> String {
446 "Cannot create MechTable from an empty record list.".to_string()
447 }
448}
449
450#[derive(Debug, Clone)]
451pub struct CannotCreateTableFromNonTableKindError;
452
453impl MechErrorKind2 for CannotCreateTableFromNonTableKindError {
454 fn name(&self) -> &str {
455 "CannotCreateTableFromNonTableKind"
456 }
457 fn message(&self) -> String {
458 "Cannot create MechTable from non-table kind.".to_string()
459 }
460}
461
462#[derive(Debug, Clone)]
463pub struct TableColumnKindMismatchError {
464 pub column_id: u64,
465 pub expected_kind: ValueKind,
466 pub actual_kind: ValueKind,
467}
468
469impl MechErrorKind2 for TableColumnKindMismatchError {
470 fn name(&self) -> &str { "ColumnKindMismatch" }
471 fn message(&self) -> String {
472 format!("Schema mismatch: column {} kind mismatch (expected: {}, found: {}).",
473 self.column_id, self.expected_kind, self.actual_kind)
474 }
475}
476
477#[derive(Debug, Clone)]
478pub struct TableColumnNameMismatchError {
479 pub column_id: u64,
480 pub expected_name: String,
481 pub actual_name: String,
482}
483
484impl MechErrorKind2 for TableColumnNameMismatchError {
485 fn name(&self) -> &str { "ColumnNameMismatch" }
486 fn message(&self) -> String {
487 format!("Schema mismatch: column {} name mismatch (expected: '{}', found: '{}').",
488 self.column_id, self.expected_name, self.actual_name)
489 }
490}
491
492#[derive(Debug, Clone)]
493pub struct TableColumnNotFoundError {
494 pub column_id: u64,
495}
496
497impl MechErrorKind2 for TableColumnNotFoundError {
498 fn name(&self) -> &str { "ColumnNotFound" }
499 fn message(&self) -> String {
500 format!("Schema mismatch: column {} not found in table.", self.column_id)
501 }
502}
503
504#[derive(Debug, Clone)]
505pub struct MatrixAppendToTableError {
506 pub column_id: u64,
507}
508
509impl MechErrorKind2 for MatrixAppendToTableError {
510 fn name(&self) -> &str { "MatrixAppendToTableError" }
511 fn message(&self) -> String {
512 format!("Failed to append matrix for column {}.", self.column_id)
513 }
514}