1use serde_json::json;
2use tidepool_eval::value::Value;
3use tidepool_repr::datacon_table::DataConTable;
4use tidepool_repr::types::{DataConId, Literal};
5
6const MAX_DEPTH: usize = 1000;
7const MAX_LIST_LEN: usize = 10000;
8
9#[derive(Debug)]
12pub struct EvalResult {
13 value: Value,
14 table: DataConTable,
15}
16
17impl EvalResult {
18 pub(crate) fn new(value: Value, table: DataConTable) -> Self {
19 Self { value, table }
20 }
21
22 pub fn to_json(&self) -> serde_json::Value {
24 value_to_json(&self.value, &self.table, 0)
25 }
26
27 pub fn to_string_pretty(&self) -> String {
29 let j = self.to_json();
30 match &j {
32 serde_json::Value::Number(_) | serde_json::Value::Bool(_) | serde_json::Value::Null => {
33 j.to_string()
34 }
35 serde_json::Value::String(s) => {
36 if s.len() <= 80 {
38 j.to_string()
39 } else {
40 serde_json::to_string_pretty(&j).unwrap_or_else(|_| j.to_string())
41 }
42 }
43 _ => serde_json::to_string_pretty(&j).unwrap_or_else(|_| j.to_string()),
44 }
45 }
46
47 pub fn into_value(self) -> Value {
49 self.value
50 }
51
52 pub fn value(&self) -> &Value {
54 &self.value
55 }
56
57 pub fn table(&self) -> &DataConTable {
59 &self.table
60 }
61}
62
63fn con_name(id: DataConId, table: &DataConTable) -> &str {
64 table.name_of(id).unwrap_or("<unknown>")
65}
66
67pub fn value_to_json(val: &Value, table: &DataConTable, depth: usize) -> serde_json::Value {
69 if depth > MAX_DEPTH {
70 return json!("<depth limit>");
71 }
72 let d = depth + 1;
73
74 match val {
75 Value::Lit(lit) => literal_to_json(lit),
77
78 Value::Con(id, fields) => {
80 let name = con_name(*id, table);
81 match (name, fields.as_slice()) {
82 ("True", []) => json!(true),
84 ("False", []) => json!(false),
85
86 ("()", []) => json!(null),
88
89 ("Nothing", []) => json!(null),
91 ("Just", [x]) => value_to_json(x, table, d),
92
93 ("Pure", [x]) => value_to_json(x, table, d),
95
96 ("I#", [x]) | ("W#", [x]) | ("D#", [x]) | ("F#", [x]) => value_to_json(x, table, d),
98 ("C#", [x]) => value_to_json(x, table, d),
99
100 ("Text", [ba_val, off_val, len_val]) => {
103 let raw_ba = {
107 let mut cur = ba_val;
108 loop {
109 match cur {
110 Value::ByteArray(bs) => break Some(bs.clone()),
111 Value::Con(id, fields)
112 if con_name(*id, table) == "ByteArray" && fields.len() == 1 =>
113 {
114 cur = &fields[0];
115 }
116 _ => break None,
117 }
118 }
119 };
120 if let Some(bs) = raw_ba {
121 let borrowed = bs.lock().unwrap_or_else(|e| e.into_inner());
122 let off = extract_boxed_int(off_val, table).unwrap_or(0) as usize;
123 let len = extract_boxed_int(len_val, table).unwrap_or(borrowed.len() as i64)
124 as usize;
125 let end = (off + len).min(borrowed.len());
126 match std::str::from_utf8(&borrowed[off..end]) {
127 Ok(s) => json!(s),
128 Err(_) => json!(format!("<Text invalid UTF-8 len={}>", len)),
129 }
130 } else {
131 let field_jsons: Vec<serde_json::Value> =
132 fields.iter().map(|f| value_to_json(f, table, d)).collect();
133 json!({"constructor": "Text", "fields": field_jsons})
134 }
135 }
136
137 ("[]", []) => {
139 json!([])
141 }
142 (":", [head, tail]) => collect_list(head, tail, table, d),
143
144 (n, fields)
146 if n.starts_with('(')
147 && n.ends_with(')')
148 && n.chars().all(|c| c == '(' || c == ')' || c == ',')
149 && fields.len() >= 2 =>
150 {
151 let elems: Vec<serde_json::Value> =
152 fields.iter().map(|f| value_to_json(f, table, d)).collect();
153 json!(elems)
154 }
155
156 ("IS", [x]) => value_to_json(x, table, d),
159 ("IP", _) => json!("<big-integer>"),
161 ("IN", _) => json!("<big-integer>"),
163
164 ("Scientific", [coeff, exp_val]) => {
166 let c = match value_to_json(coeff, table, d) {
167 serde_json::Value::Number(n) => n.as_i64().unwrap_or(0),
168 _ => 0,
169 };
170 let e = match value_to_json(exp_val, table, d) {
171 serde_json::Value::Number(n) => n.as_i64().unwrap_or(0),
172 _ => 0,
173 };
174 if e >= 0 {
176 let val = c * 10i64.pow(e as u32);
177 json!(val)
178 } else {
179 let val = c as f64 * 10f64.powi(e as i32);
180 json!(val)
181 }
182 }
183
184 ("Null", []) => json!(null),
186 ("Bool", [x]) => value_to_json(x, table, d),
187 ("Number", [x]) => value_to_json(x, table, d),
188 ("String", [x]) => value_to_json(x, table, d),
189 ("Array", [vec_val]) => value_to_json(vec_val, table, d),
190 ("Object", [map_val]) => map_to_json_object(map_val, table, d),
191
192 ("Vector", fields) => {
198 let array_elems = fields.iter().rev().find_map(|f| match f {
201 Value::Con(_, elems) if !elems.is_empty() => Some(elems),
202 _ => None,
203 });
204 if let Some(elems) = array_elems {
205 let arr: Vec<serde_json::Value> =
206 elems.iter().map(|e| value_to_json(e, table, d)).collect();
207 json!(arr)
208 } else {
209 json!([])
210 }
211 }
212
213 (_name, fields) => {
215 if fields.is_empty() {
216 json!(name)
217 } else {
218 let field_jsons: Vec<serde_json::Value> =
219 fields.iter().map(|f| value_to_json(f, table, d)).collect();
220 json!({
221 "constructor": name,
222 "fields": field_jsons
223 })
224 }
225 }
226 }
227 }
228
229 Value::Closure(_, _, _) => json!("<closure>"),
231 Value::ThunkRef(_) => json!("<thunk>"),
232 Value::JoinCont(_, _, _) => json!("<join>"),
233 Value::ConFun(id, _, _) => {
234 let name = con_name(*id, table);
235 json!(format!("<partially-applied {}>", name))
236 }
237 Value::ByteArray(bs) => {
238 let borrowed = bs.lock().unwrap_or_else(|e| e.into_inner());
239 match std::str::from_utf8(&borrowed) {
240 Ok(s) => json!(s),
241 Err(_) => json!(format!("<ByteArray# len={}>", borrowed.len())),
242 }
243 }
244 }
245}
246
247fn map_to_json_object(val: &Value, table: &DataConTable, depth: usize) -> serde_json::Value {
250 let mut entries = serde_json::Map::new();
251 collect_map_entries(val, table, depth, &mut entries);
252 serde_json::Value::Object(entries)
253}
254
255fn collect_map_entries(
256 val: &Value,
257 table: &DataConTable,
258 depth: usize,
259 out: &mut serde_json::Map<String, serde_json::Value>,
260) {
261 if depth > MAX_DEPTH {
262 return;
263 }
264 if let Value::Con(id, fields) = val {
265 let name = con_name(*id, table);
266 match (name, fields.as_slice()) {
267 ("Tip", []) => {}
268 ("Bin", [_size, k, v, left, right]) => {
270 collect_map_entries(left, table, depth + 1, out);
271 let key_str = match value_to_json(k, table, depth + 1) {
272 serde_json::Value::String(s) => s,
273 other => other.to_string(),
274 };
275 out.insert(key_str, value_to_json(v, table, depth + 1));
276 collect_map_entries(right, table, depth + 1, out);
277 }
278 _ => {}
279 }
280 }
281}
282
283fn extract_boxed_int(val: &Value, table: &DataConTable) -> Option<i64> {
286 let mut cur = val;
287 loop {
288 match cur {
289 Value::Lit(Literal::LitInt(n)) => return Some(*n),
290 Value::Con(id, fields) if fields.len() == 1 && table.get_by_name("I#") == Some(*id) => {
291 cur = &fields[0];
292 }
293 _ => return None,
294 }
295 }
296}
297
298fn extract_char(val: &Value, table: &DataConTable) -> Option<char> {
301 match val {
302 Value::Lit(Literal::LitChar(c)) => Some(*c),
303 Value::Con(id, fields) if con_name(*id, table) == "C#" && fields.len() == 1 => {
304 extract_char_inner(&fields[0], table)
305 }
306 _ => None,
307 }
308}
309
310fn extract_char_inner(val: &Value, table: &DataConTable) -> Option<char> {
312 match val {
313 Value::Lit(Literal::LitChar(c)) => Some(*c),
314 Value::Con(id, fields) if con_name(*id, table) == "Text" && fields.len() == 3 => {
316 let len = extract_boxed_int(&fields[2], table)?;
317 if len != 1 {
318 return None;
319 }
320 let off = extract_boxed_int(&fields[1], table).unwrap_or(0) as usize;
321 let raw_ba = {
323 let mut cur = &fields[0];
324 loop {
325 match cur {
326 Value::ByteArray(bs) => break Some(bs.clone()),
327 Value::Con(cid, cfields)
328 if con_name(*cid, table) == "ByteArray" && cfields.len() == 1 =>
329 {
330 cur = &cfields[0];
331 }
332 _ => break None,
333 }
334 }
335 };
336 let bs = raw_ba?;
337 let borrowed = bs.lock().unwrap_or_else(|e| e.into_inner());
338 let byte = *borrowed.get(off)?;
339 Some(byte as char)
340 }
341 _ => None,
342 }
343}
344
345fn literal_to_json(lit: &Literal) -> serde_json::Value {
346 match lit {
347 Literal::LitInt(n) => json!(n),
348 Literal::LitWord(n) => json!(n),
349 Literal::LitChar(c) => json!(c.to_string()),
350 Literal::LitString(bytes) => match std::str::from_utf8(bytes) {
351 Ok(s) => json!(s),
352 Err(_) => json!(format!("<binary:{} bytes>", bytes.len())),
353 },
354 Literal::LitFloat(bits) => {
355 let f = f32::from_bits(*bits as u32) as f64;
356 if f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
357 json!(f as i64)
358 } else {
359 serde_json::Number::from_f64(f)
360 .map(serde_json::Value::Number)
361 .unwrap_or(json!(null))
362 }
363 }
364 Literal::LitDouble(bits) => {
365 let f = f64::from_bits(*bits);
366 if f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
368 json!(f as i64)
369 } else {
370 serde_json::Number::from_f64(f)
371 .map(serde_json::Value::Number)
372 .unwrap_or(json!(null))
373 }
374 }
375 }
376}
377
378fn collect_list(
380 head: &Value,
381 tail: &Value,
382 table: &DataConTable,
383 depth: usize,
384) -> serde_json::Value {
385 let mut elems = vec![head];
386 let mut current = tail;
387 let mut count = 1usize;
388
389 loop {
390 if count >= MAX_LIST_LEN {
391 let mut arr: Vec<serde_json::Value> = elems
393 .iter()
394 .map(|e| value_to_json(e, table, depth))
395 .collect();
396 arr.push(json!("..."));
397 return json!(arr);
398 }
399 match current {
400 Value::Con(id, fields) => {
401 let name = con_name(*id, table);
402 match (name, fields.as_slice()) {
403 ("[]", []) => break,
404 (":", [h, t]) => {
405 elems.push(h);
406 current = t;
407 count += 1;
408 }
409 _ => {
410 let mut arr: Vec<serde_json::Value> = elems
412 .iter()
413 .map(|e| value_to_json(e, table, depth))
414 .collect();
415 arr.push(value_to_json(current, table, depth));
416 return json!(arr);
417 }
418 }
419 }
420 _ => {
421 let mut arr: Vec<serde_json::Value> = elems
423 .iter()
424 .map(|e| value_to_json(e, table, depth))
425 .collect();
426 arr.push(value_to_json(current, table, depth));
427 return json!(arr);
428 }
429 }
430 }
431
432 let mut all_chars = true;
438 let mut char_buf = String::new();
439 for e in &elems {
440 if let Some(c) = extract_char(e, table) {
441 char_buf.push(c);
442 } else {
443 all_chars = false;
444 break;
445 }
446 }
447
448 if all_chars && !char_buf.is_empty() {
449 json!(char_buf)
450 } else {
451 let arr: Vec<serde_json::Value> = elems
452 .iter()
453 .map(|e| value_to_json(e, table, depth))
454 .collect();
455 json!(arr)
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462 use std::sync::{Arc, Mutex};
463 use tidepool_repr::datacon::DataCon;
464 use tidepool_repr::types::DataConId;
465
466 fn test_table() -> DataConTable {
467 let mut t = DataConTable::new();
468 let cons = [
469 (0, "Nothing", 0),
470 (1, "Just", 1),
471 (2, "True", 0),
472 (3, "False", 0),
473 (4, "()", 0),
474 (5, "I#", 1),
475 (6, "C#", 1),
476 (7, ":", 2),
477 (8, "[]", 0),
478 (9, "Text", 3),
479 (10, "(,)", 2),
480 (11, "(,,)", 3),
481 (12, "ByteArray", 1),
482 ];
483 for (id, name, arity) in cons {
484 t.insert(DataCon {
485 id: DataConId(id),
486 name: name.into(),
487 tag: id as u32,
488 rep_arity: arity,
489 field_bangs: vec![],
490 qualified_name: None,
491 });
492 }
493 t
494 }
495
496 #[test]
497 fn test_render_lit_int() {
498 let table = test_table();
499 let val = Value::Lit(Literal::LitInt(42));
500 assert_eq!(value_to_json(&val, &table, 0), json!(42));
501 }
502
503 #[test]
504 fn test_render_lit_string() {
505 let table = test_table();
506 let val = Value::Lit(Literal::LitString(b"hello".to_vec()));
507 assert_eq!(value_to_json(&val, &table, 0), json!("hello"));
508 }
509
510 #[test]
511 fn test_render_bool() {
512 let table = test_table();
513 let true_val = Value::Con(table.get_by_name("True").unwrap(), vec![]);
514 let false_val = Value::Con(table.get_by_name("False").unwrap(), vec![]);
515 assert_eq!(value_to_json(&true_val, &table, 0), json!(true));
516 assert_eq!(value_to_json(&false_val, &table, 0), json!(false));
517 }
518
519 #[test]
520 fn test_render_option() {
521 let table = test_table();
522 let nothing = Value::Con(table.get_by_name("Nothing").unwrap(), vec![]);
523 let just = Value::Con(
524 table.get_by_name("Just").unwrap(),
525 vec![Value::Lit(Literal::LitInt(42))],
526 );
527 assert_eq!(value_to_json(¬hing, &table, 0), json!(null));
528 assert_eq!(value_to_json(&just, &table, 0), json!(42));
529 }
530
531 #[test]
532 fn test_render_unit() {
533 let table = test_table();
534 let unit = Value::Con(table.get_by_name("()").unwrap(), vec![]);
535 assert_eq!(value_to_json(&unit, &table, 0), json!(null));
536 }
537
538 #[test]
539 fn test_render_list_int() {
540 let table = test_table();
541 let nil_id = table.get_by_name("[]").unwrap();
542 let cons_id = table.get_by_name(":").unwrap();
543
544 let list = Value::Con(
546 cons_id,
547 vec![
548 Value::Lit(Literal::LitInt(1)),
549 Value::Con(
550 cons_id,
551 vec![Value::Lit(Literal::LitInt(2)), Value::Con(nil_id, vec![])],
552 ),
553 ],
554 );
555 assert_eq!(value_to_json(&list, &table, 0), json!([1, 2]));
556 }
557
558 #[test]
559 fn test_render_text() {
560 let table = test_table();
561 let text_id = table.get_by_name("Text").unwrap();
562 let ba = Value::ByteArray(Arc::new(Mutex::new(b"hello".to_vec())));
563 let val = Value::Con(
564 text_id,
565 vec![
566 ba,
567 Value::Lit(Literal::LitInt(0)),
568 Value::Lit(Literal::LitInt(5)),
569 ],
570 );
571 assert_eq!(value_to_json(&val, &table, 0), json!("hello"));
572 }
573
574 #[test]
575 fn test_render_list_string() {
576 let table = test_table();
577 let nil_id = table.get_by_name("[]").unwrap();
578 let cons_id = table.get_by_name(":").unwrap();
579
580 let list = Value::Con(
582 cons_id,
583 vec![
584 Value::Lit(Literal::LitString(b"a".to_vec())),
585 Value::Con(
586 cons_id,
587 vec![
588 Value::Lit(Literal::LitString(b"b".to_vec())),
589 Value::Con(nil_id, vec![]),
590 ],
591 ),
592 ],
593 );
594 assert_eq!(value_to_json(&list, &table, 0), json!(["a", "b"]));
595 }
596
597 #[test]
598 fn test_render_tuple() {
599 let table = test_table();
600 let pair_id = table.get_by_name("(,)").unwrap();
601 let triple_id = table.get_by_name("(,,)").unwrap();
602
603 let pair = Value::Con(
604 pair_id,
605 vec![
606 Value::Lit(Literal::LitInt(1)),
607 Value::Lit(Literal::LitInt(2)),
608 ],
609 );
610 let triple = Value::Con(
611 triple_id,
612 vec![
613 Value::Lit(Literal::LitInt(1)),
614 Value::Lit(Literal::LitInt(2)),
615 Value::Lit(Literal::LitInt(3)),
616 ],
617 );
618
619 assert_eq!(value_to_json(&pair, &table, 0), json!([1, 2]));
620 assert_eq!(value_to_json(&triple, &table, 0), json!([1, 2, 3]));
621 }
622
623 #[test]
624 fn test_render_char_list_as_string() {
625 let table = test_table();
626 let nil_id = table.get_by_name("[]").unwrap();
627 let cons_id = table.get_by_name(":").unwrap();
628 let c_hash_id = table.get_by_name("C#").unwrap();
629
630 let list = Value::Con(
632 cons_id,
633 vec![
634 Value::Con(c_hash_id, vec![Value::Lit(Literal::LitChar('h'))]),
635 Value::Con(
636 cons_id,
637 vec![
638 Value::Con(c_hash_id, vec![Value::Lit(Literal::LitChar('i'))]),
639 Value::Con(nil_id, vec![]),
640 ],
641 ),
642 ],
643 );
644 assert_eq!(value_to_json(&list, &table, 0), json!("hi"));
645 }
646}