use serde::Serialize; use crate::doc::{classify_document, LineKind}; use crate::interp; #[derive(Debug, Clone, Serialize)] pub struct EvalResult { pub line: usize, pub result: String, pub format: String, } #[derive(Debug, Clone, Serialize)] pub struct EvalError { pub line: usize, pub error: String, } #[derive(Debug, Clone, Serialize)] pub struct DocumentResult { pub results: Vec, pub errors: Vec, } pub fn evaluate_document(text: &str) -> DocumentResult { let classified = classify_document(text); let mut results = Vec::new(); let mut errors = Vec::new(); let mut lines: Vec<(usize, &str, bool)> = Vec::new(); for cl in &classified { match cl.kind { LineKind::Cordial => lines.push((cl.index, &cl.content, false)), LineKind::Eval => lines.push((cl.index, &cl.content, true)), LineKind::Comment | LineKind::Markdown => {} } } let interp_results = interp::interpret_document(&lines); for ir in interp_results { let fmt = match ir.format { interp::EvalFormat::Inline => "inline", interp::EvalFormat::Table => "table", interp::EvalFormat::Tree => "tree", }; match ir.value { Some(interp::Value::Error(e)) => { errors.push(EvalError { line: ir.line, error: e }); } Some(v) => { let s = match ir.format { interp::EvalFormat::Table => value_to_table_json(&v), interp::EvalFormat::Tree => value_to_tree_json(&v), interp::EvalFormat::Inline => v.display(), }; if !s.is_empty() { results.push(EvalResult { line: ir.line, result: s, format: fmt.to_string() }); } } None => {} } } DocumentResult { results, errors } } fn value_to_table_json(val: &interp::Value) -> String { match val { interp::Value::Array(rows) => { let table: Vec> = rows.iter().map(|row| { match row { interp::Value::Array(cols) => cols.iter().map(|c| c.display()).collect(), other => vec![other.display()], } }).collect(); serde_json::to_string(&table).unwrap_or_else(|_| val.display()) } _ => val.display(), } } fn value_to_tree_json(val: &interp::Value) -> String { fn to_json(v: &interp::Value) -> serde_json::Value { match v { interp::Value::Array(items) => { serde_json::Value::Array(items.iter().map(|i| to_json(i)).collect()) } interp::Value::Number(n) => { serde_json::Value::Number( serde_json::Number::from_f64(*n) .unwrap_or_else(|| serde_json::Number::from(0)) ) } interp::Value::Bool(b) => serde_json::Value::Bool(*b), interp::Value::Str(s) => serde_json::Value::String(s.clone()), other => serde_json::Value::String(other.display()), } } serde_json::to_string(&to_json(val)).unwrap_or_else(|_| val.display()) } pub fn evaluate_line(text: &str) -> Result { let mut interp = interp::Interpreter::new(); match interp.eval_expr_str(text) { Ok(v) => Ok(v.display()), Err(_) => { // fall back to cord-expr/cord-trig for trig and CORDIC expressions let graph = cord_expr::parse_expr(text)?; let val = cord_trig::eval::evaluate(&graph, 0.0, 0.0, 0.0); Ok(format_value(val)) } } } fn format_value(val: f64) -> String { if val == val.trunc() && val.abs() < 1e15 { format!("{}", val as i64) } else { let s = format!("{:.10}", val); let s = s.trim_end_matches('0'); let s = s.trim_end_matches('.'); s.to_string() } } #[cfg(test)] mod tests { use super::*; #[test] fn simple_eval() { let result = evaluate_line("2 + 3").unwrap(); assert_eq!(result, "5"); } #[test] fn eval_with_variables() { let doc = "let a = 5\nlet b = 3\n/= a + b"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "8"); assert_eq!(result.results[0].line, 2); } #[test] fn eval_with_markdown() { let doc = "# Title\nlet val = 10\nSome text\n/= val * 2"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "20"); } #[test] fn eval_trig() { let result = evaluate_line("sin(0)").unwrap(); assert_eq!(result, "0"); } #[test] fn eval_function_def() { let doc = "f(a) = a * a\n/= f(5)"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "25"); } #[test] fn multiple_evals() { let doc = "let a = 3\n/= a\nlet b = 7\n/= a + b"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 2); assert_eq!(result.results[0].result, "3"); assert_eq!(result.results[1].result, "10"); } #[test] fn format_integer() { assert_eq!(format_value(42.0), "42"); } #[test] fn format_float() { let s = format_value(3.14); assert!(s.starts_with("3.14")); } #[test] fn eval_x_plus_5() { let doc = "let x = 10\n/= x + 5"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "15"); } #[test] fn eval_string_concat() { let doc = "let x = \"hello\"\nlet y = \"world\"\n/= x + \" \" + y"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "hello world"); } #[test] fn eval_booleans() { let doc = "let x = true\n/= x\n/= 1 > 0"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 2); assert_eq!(result.results[0].result, "true"); assert_eq!(result.results[1].result, "true"); } #[test] fn eval_while_loop() { let doc = "let i = 0\nlet sum = 0\nwhile (i < 10) {\n sum = sum + i\n i = i + 1\n}\n/= sum"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "45"); } #[test] fn eval_fn_block() { let doc = "fn add(a, b) {\n a + b\n}\n/= add(3, 4)"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "7"); } #[test] fn eval_type_annotation() { let doc = "let x: int = 3.7\n/= x"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "3"); } #[test] fn eval_type_annotation_bool_error() { let doc = "let x: bool = 2\n/= x"; let result = evaluate_document(doc); assert!(result.errors.len() >= 1); assert!(result.errors[0].error.contains("not a valid bool")); } #[test] fn eval_array() { let doc = "let arr = [1, \"two\", true]\n/= arr"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "[1, \"two\", true]"); } #[test] fn eval_error_recovery() { let doc = "let x = undefined_var\nlet y = 5\n/= y"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "5"); assert!(result.errors.len() >= 1); } #[test] fn eval_mixed_markdown_and_code() { let doc = "# Notes\nlet x = 10\nSome text here\nwhile (x > 0) {\n x = x - 1\n}\n/= x"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "0"); } #[test] fn eval_if_else() { let doc = "let x = 10\nif (x > 5) {\n x = 1\n} else {\n x = 0\n}\n/= x"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "1"); } #[test] fn eval_for_loop() { let doc = "let sum = 0\nfor i in [1, 2, 3] {\n sum = sum + i\n}\n/= sum"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "6"); } #[test] fn eval_array_index() { let doc = "let arr = [10, 20, 30]\n/= arr[1]"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "20"); } #[test] fn eval_fn_return() { let doc = "fn max(a, b) {\n if (a > b) {\n return a\n }\n return b\n}\n/= max(3, 7)"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].result, "7"); } #[test] fn eval_table_format() { let doc = "let data = [[\"Name\", \"Age\"], [\"Alice\", 30], [\"Bob\", 25]]\n/=| data"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].format, "table"); let parsed: Vec> = serde_json::from_str(&result.results[0].result).unwrap(); assert_eq!(parsed.len(), 3); assert_eq!(parsed[0], vec!["Name", "Age"]); } #[test] fn eval_tree_format() { let doc = "let tree = [1, [2, 3], [4, [5]]]\n/=\\ tree"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].format, "tree"); let parsed: serde_json::Value = serde_json::from_str(&result.results[0].result).unwrap(); assert!(parsed.is_array()); } #[test] fn eval_inline_format_default() { let doc = "let x = 42\n/= x"; let result = evaluate_document(doc); assert_eq!(result.results[0].format, "inline"); } #[test] fn eval_table_flat_array() { let doc = "/=| [1, 2, 3]"; let result = evaluate_document(doc); assert_eq!(result.results.len(), 1); assert_eq!(result.results[0].format, "table"); } #[test] fn eval_document_json_has_format() { let doc = "let x = 42\n/= x\n/=| [[1, 2], [3, 4]]"; let result = evaluate_document(doc); let json = serde_json::to_string(&result).unwrap(); assert!(json.contains("\"format\":\"inline\"")); assert!(json.contains("\"format\":\"table\"")); } }