345 lines
11 KiB
Rust
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\""));
|
|
}
|
|
}
|