Acord/core/src/eval.rs

345 lines
11 KiB
Rust

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<EvalResult>,
pub errors: Vec<EvalError>,
}
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<Vec<String>> = 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<String, String> {
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<Vec<String>> = 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\""));
}
}