1use crate::*;
2#[cfg(feature = "matrix")]
3use crate::matrix::Matrix;
4use indexmap::map::*;
5
6use nalgebra::{DMatrix, DVector, RowDVector};
7
8#[cfg(feature = "table")]
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct MechTable {
13 pub rows: usize,
14 pub cols: usize,
15 pub data: IndexMap<u64,(ValueKind,Matrix<Value>)>,
16 pub col_names: HashMap<u64,String>,
17}
18
19#[cfg(feature = "table")]
20impl MechTable {
21
22 pub fn from_records(records: Vec<MechRecord>) -> MResult<MechTable> {
23 if records.is_empty() {
24 return Err(MechError { id: line!(), file: file!().to_string(), tokens: vec![], msg: "Cannot create MechTable from empty record list.".to_string(), kind: MechErrorKind::None});
25 }
26
27 let first = &records[0];
28 let rows = records.len();
29 let cols = first.cols;
30
31 let mut col_data: IndexMap<u64, Vec<Value>> = IndexMap::new();
32
33 for (&col_id, value) in &first.data {
34 col_data.insert(col_id, vec![value.clone()]);
35 }
36
37 let mut kinds = IndexMap::new();
38 for (col_id, kind) in first.data.keys().zip(&first.kinds) {
39 kinds.insert(*col_id, kind.clone());
40 }
41
42 let col_names = first.field_names.clone();
43
44 for record in records.iter().skip(1) {
45 first.check_record_schema(record)?;
46 for (&col_id, value) in &record.data {
47 col_data.entry(col_id).or_insert_with(Vec::new).push(value.clone());
48 }
49 }
50
51 let data: IndexMap<u64, (ValueKind, Matrix<Value>)> = col_data
52 .into_iter()
53 .map(|(col_id, values)| {
54 let kind = kinds[&col_id].clone();
55 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(values)));
56 (col_id, (kind, matrix))
57 })
58 .collect();
59
60 Ok(MechTable {rows,cols,data,col_names})
61 }
62
63 pub fn from_kind(kind: ValueKind) -> MResult<MechTable> {
64 match kind {
65 ValueKind::Table(tbl,sze) => {
66 let mut data = IndexMap::new();
67 let mut col_names = HashMap::new();
68 for (col_id, col_kind) in &tbl {
69 let matrix = Matrix::DVector(Ref::new(DVector::from_vec(vec![Value::Empty; sze])));
70 col_names.insert(hash_str(col_id), col_id.clone());
71 data.insert(hash_str(&col_id), (col_kind.clone(), matrix));
72 }
73 Ok(MechTable {rows: sze, cols: tbl.len(), data, col_names})
74 }
75 _ => {
76 return Err(MechError { id: line!(), file: file!().to_string(), tokens: vec![], msg: "Cannot create MechTable from non-table kind.".to_string(), kind: MechErrorKind::None });
77 }
78 }
79 }
80
81 pub fn empty_table(&self, rows: usize) -> MechTable {
82 let mut data = IndexMap::new();
83 for col in self.data.iter() {
84 let (key, (kind, matrix)) = col;
85 let elements = vec![Value::Empty; rows];
87 let new_matrix = Matrix::DVector(Ref::new(DVector::from_vec(elements)));
88 data.insert(*key, (kind.clone(), new_matrix));
89 }
90 MechTable { rows: rows, cols: self.cols, data, col_names: self.col_names.clone() }
91 }
92
93 pub fn check_record_schema(&self, record: &MechRecord) -> MResult<bool> {
94
95 for (&col_id, record_value) in &record.data {
96 let (expected_kind, column_matrix) = match self.data.get(&col_id) {
99 Some(data) => data,
100 None => {
101 continue;
102 }
103 };
104
105 let actual_kind = record_value.kind();
107
108 if expected_kind != &actual_kind {
109 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,});
110 }
111
112 if let Some(expected_name) = self.col_names.get(&col_id) {
114 if let Some(field_name) = record.field_names.get(&col_id) {
115 if expected_name != field_name {
116 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,});
117 }
118 }
119 }
120 }
121
122 Ok(true)
123 }
124
125 pub fn check_table_schema(&self, record: &MechTable) -> MResult<bool> {
126
127 for (&col_id, col_name) in &self.col_names {
129 if let Some(record_name) = record.col_names.get(&col_id) {
130 if col_name != record_name {
131 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,});
132 }
133 } else {
134 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,});
135 }
136 }
137
138 for (&col_id, (expected_kind, _)) in &self.data {
140 if let Some((record_kind, _)) = record.data.get(&col_id) {
141 if expected_kind != record_kind {
142 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,});
143 }
144 } else {
145 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,});
146 }
147 }
148
149 Ok(true)
150 }
151
152 pub fn append_table(&mut self, other: &MechTable) -> MResult<()> {
153 self.check_table_schema(other)?;
154 for (&col_id, (_, other_matrix)) in &other.data {
155 let (_, self_matrix) = self.data.get_mut(&col_id).ok_or(MechError {
156 id: line!(),
157 file: file!().to_string(),
158 tokens: vec![],
159 msg: format!("Column {} not found in destination table", col_id),
160 kind: MechErrorKind::None,
161 })?;
162
163 self_matrix.append(other_matrix).map_err(|err| MechError {
164 id: line!(),
165 file: file!().to_string(),
166 tokens: vec![],
167 msg: "".to_string(),
168 kind: MechErrorKind::None,
169 })?;
170 }
171 self.rows += other.rows;
172 Ok(())
173 }
174
175 pub fn append_record(&mut self, record: MechRecord) -> MResult<()> {
176 self.check_record_schema(&record)?;
178
179 for (&col_id, value) in &record.data {
181 if let Some((_kind, column_matrix)) = self.data.get_mut(&col_id) {
182 let result = column_matrix.push(value.clone());
183 } else {
184 continue;
185 }
186 }
187
188 self.rows += 1;
190
191 Ok(())
192 }
193
194 pub fn get_record(&self, ix: usize) -> Option<MechRecord> {
195 if ix > self.rows {
196 return None;
197 }
198
199 let mut data: IndexMap<u64, Value> = IndexMap::new();
200 data = self.data.iter().map(|(key, (kind, matrix))| {
201 let value = matrix.index1d(ix);
202 let name = self.col_names.get(key).unwrap();
203 (hash_str(name), value.clone())
204 }).collect();
205
206 let mut kinds = Vec::with_capacity(self.cols);
207 kinds = self.data.iter().map(|(_, (kind, _))| kind.clone()).collect();
208
209 let mut field_names = self.col_names.clone();
210
211 Some(MechRecord{cols: self.cols, kinds, data, field_names})
212 }
213
214 #[cfg(feature = "pretty_print")]
215 pub fn to_html(&self) -> String {
216 let mut html = String::new();
217
218 html.push_str("<table class=\"mech-table\">");
220
221 html.push_str("<thead class=\"mech-table-header\"><tr>");
223 for (key, (kind, _matrix)) in self.data.iter() {
224 let col_name = self
225 .col_names
226 .get(key)
227 .cloned()
228 .unwrap_or_else(|| key.to_string());
229
230 let kind_str = format!(
231 "<span class=\"mech-kind-annotation\"><<span class=\"mech-kind\">{}</span>></span>",
232 kind
233 );
234
235 html.push_str(&format!(
236 "<th class=\"mech-table-field\">\
237 <div class=\"mech-field\">\
238 <span class=\"mech-field-name\">{}</span>\
239 <span class=\"mech-field-kind\">{}</span>\
240 </div>\
241 </th>",
242 col_name, kind_str
243 ));
244 }
245 html.push_str("</tr></thead>");
246
247 html.push_str("<tbody class=\"mech-table-body\">");
249 for row_idx in 1..=self.rows {
250 html.push_str("<tr class=\"mech-table-row\">");
251 for (_key, (_kind, matrix)) in self.data.iter() {
252 let value = matrix.index1d(row_idx);
253 html.push_str(&format!(
254 "<td class=\"mech-table-column\">{}</td>",
255 value.to_html()
256 ));
257 }
258 html.push_str("</tr>");
259 }
260 html.push_str("</tbody></table>");
261 html
262 }
263
264 pub fn new(rows: usize, cols: usize, data: IndexMap<u64,(ValueKind,Matrix<Value>)>, col_names: HashMap<u64,String>) -> MechTable {
265 MechTable{rows, cols, data, col_names}
266 }
267
268 pub fn kind(&self) -> ValueKind {
269 let column_kinds: Vec<(String, ValueKind)> = self.data.iter()
270 .filter_map(|(key, (kind, _))| {
271 let col_name = self.col_names.get(key)?;
272 Some((col_name.clone(), kind.clone()))
273 })
274 .collect();
275 ValueKind::Table(column_kinds, self.rows)
276 }
277
278 pub fn size_of(&self) -> usize {
279 self.data.iter().map(|(_,(_,v))| v.size_of()).sum()
280 }
281
282 pub fn rows(&self) -> usize {
283 self.rows
284 }
285
286 pub fn cols(&self) -> usize {
287 self.cols
288 }
289
290 pub fn get(&self, key: &u64) -> Option<&(ValueKind,Matrix<Value>)> {
291 self.data.get(key)
292 }
293
294 pub fn shape(&self) -> Vec<usize> {
295 vec![self.rows,self.cols]
296 }
297}
298
299#[cfg(feature = "pretty_print")]
300impl PrettyPrint for MechTable {
301 fn pretty_print(&self) -> String {
302 let mut builder = Builder::default();
303 for (k,(knd,val)) in &self.data {
304 let name = self.col_names.get(k).unwrap();
305 let val_string: String = val.as_vec().iter()
306 .map(|x| x.pretty_print())
307 .collect::<Vec<String>>()
308 .join("\n");
309 let mut col_string = vec![format!("{}<{}>", name.to_string(), knd), val_string];
310 builder.push_column(col_string);
311 }
312 let mut table = builder.build();
313 table.with(Style::modern_rounded());
314 format!("{table}")
315 }
316}
317
318#[cfg(feature = "table")]
319impl Hash for MechTable {
320 fn hash<H: Hasher>(&self, state: &mut H) {
321 for (k,(knd,val)) in self.data.iter() {
322 k.hash(state);
323 knd.hash(state);
324 val.hash(state);
325 }
326 }
327}