extend Cordial with strings, booleans, while loops, functions, type annotations, arrays, and error recovery
This commit is contained in:
parent
5ebf308104
commit
479c8a9482
|
|
@ -38,15 +38,42 @@ pub fn classify_line(index: usize, raw: &str) -> ClassifiedLine {
|
|||
fn is_cordial(line: &str) -> bool {
|
||||
if line.starts_with("let ") {
|
||||
let rest = &line[4..];
|
||||
if let Some(colon_pos) = rest.find(':') {
|
||||
let before_colon = rest[..colon_pos].trim();
|
||||
if is_ident(before_colon) {
|
||||
let after_colon = &rest[colon_pos + 1..];
|
||||
if after_colon.contains('=') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(eq_pos) = rest.find('=') {
|
||||
let after_eq = rest.as_bytes().get(eq_pos + 1);
|
||||
if after_eq != Some(&b'=') {
|
||||
let name = rest[..eq_pos].trim();
|
||||
if is_ident(name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// while (...) {
|
||||
if line.starts_with("while ") || line.starts_with("while(") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// fn name(...) {
|
||||
if line.starts_with("fn ") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// lone closing brace
|
||||
if line == "}" || line.starts_with("} ") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(eq_pos) = line.find('=') {
|
||||
if eq_pos > 0 {
|
||||
let before = &line[..eq_pos];
|
||||
|
|
@ -88,16 +115,32 @@ fn is_ident(s: &str) -> bool {
|
|||
|
||||
pub fn classify_document(text: &str) -> Vec<ClassifiedLine> {
|
||||
let mut result = Vec::new();
|
||||
let mut depth: usize = 0;
|
||||
let mut comment_depth: usize = 0;
|
||||
let mut brace_depth: i32 = 0;
|
||||
|
||||
for (i, line) in text.lines().enumerate() {
|
||||
let was_in_comment = depth > 0;
|
||||
depth = scan_comment_depth(line, depth);
|
||||
let was_in_comment = comment_depth > 0;
|
||||
comment_depth = scan_comment_depth(line, comment_depth);
|
||||
|
||||
if was_in_comment || line.trim().starts_with("/*") {
|
||||
result.push(ClassifiedLine { index: i, kind: LineKind::Comment, content: line.to_string() });
|
||||
} else if brace_depth > 0 {
|
||||
let trimmed = line.trim();
|
||||
let opens = trimmed.matches('{').count() as i32;
|
||||
let closes = trimmed.matches('}').count() as i32;
|
||||
brace_depth += opens - closes;
|
||||
if brace_depth < 0 { brace_depth = 0; }
|
||||
result.push(ClassifiedLine { index: i, kind: LineKind::Cordial, content: line.to_string() });
|
||||
} else {
|
||||
result.push(classify_line(i, line));
|
||||
let cl = classify_line(i, line);
|
||||
if cl.kind == LineKind::Cordial {
|
||||
let trimmed = line.trim();
|
||||
let opens = trimmed.matches('{').count() as i32;
|
||||
let closes = trimmed.matches('}').count() as i32;
|
||||
brace_depth += opens - closes;
|
||||
if brace_depth < 0 { brace_depth = 0; }
|
||||
}
|
||||
result.push(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,4 +264,52 @@ mod tests {
|
|||
assert_eq!(lines[3].kind, LineKind::Comment);
|
||||
assert_eq!(lines[4].kind, LineKind::Cordial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn while_line() {
|
||||
let c = classify_line(0, "while (i < 10) {");
|
||||
assert_eq!(c.kind, LineKind::Cordial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_line() {
|
||||
let c = classify_line(0, "fn add(a, b) {");
|
||||
assert_eq!(c.kind, LineKind::Cordial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_brace() {
|
||||
let c = classify_line(0, "}");
|
||||
assert_eq!(c.kind, LineKind::Cordial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn while_block_body_classified() {
|
||||
let doc = "while (x > 0) {\n x = x - 1\n}";
|
||||
let lines = classify_document(doc);
|
||||
assert_eq!(lines[0].kind, LineKind::Cordial);
|
||||
assert_eq!(lines[1].kind, LineKind::Cordial);
|
||||
assert_eq!(lines[2].kind, LineKind::Cordial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_block_body_classified() {
|
||||
let doc = "fn add(a, b) {\n a + b\n}";
|
||||
let lines = classify_document(doc);
|
||||
assert_eq!(lines[0].kind, LineKind::Cordial);
|
||||
assert_eq!(lines[1].kind, LineKind::Cordial);
|
||||
assert_eq!(lines[2].kind, LineKind::Cordial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_with_type_annotation() {
|
||||
let c = classify_line(0, "let x: int = 5");
|
||||
assert_eq!(c.kind, LineKind::Cordial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_with_bool_type() {
|
||||
let c = classify_line(0, "let flag: bool = 1");
|
||||
assert_eq!(c.kind, LineKind::Cordial);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
135
core/src/eval.rs
135
core/src/eval.rs
|
|
@ -1,5 +1,6 @@
|
|||
use serde::Serialize;
|
||||
use crate::doc::{classify_document, LineKind};
|
||||
use crate::interp;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct EvalResult {
|
||||
|
|
@ -21,56 +22,48 @@ pub struct DocumentResult {
|
|||
|
||||
pub fn evaluate_document(text: &str) -> DocumentResult {
|
||||
let classified = classify_document(text);
|
||||
let mut cordial_lines: Vec<String> = Vec::new();
|
||||
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 => {
|
||||
cordial_lines.push(cl.content.clone());
|
||||
}
|
||||
LineKind::Eval => {
|
||||
let expr = cl.content.trim().strip_prefix("/=").unwrap_or("").trim();
|
||||
if expr.is_empty() {
|
||||
errors.push(EvalError {
|
||||
line: cl.index,
|
||||
error: "empty expression".into(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut eval_program = cordial_lines.clone();
|
||||
eval_program.push(expr.to_string());
|
||||
let program = eval_program.join("\n");
|
||||
|
||||
match cord_expr::parse_expr(&program) {
|
||||
Ok(graph) => {
|
||||
let val = cord_trig::eval::evaluate(&graph, 0.0, 0.0, 0.0);
|
||||
results.push(EvalResult {
|
||||
line: cl.index,
|
||||
result: format_value(val),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(EvalError {
|
||||
line: cl.index,
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
match ir.value {
|
||||
Some(interp::Value::Error(e)) => {
|
||||
errors.push(EvalError { line: ir.line, error: e });
|
||||
}
|
||||
Some(v) => {
|
||||
let s = v.display();
|
||||
if !s.is_empty() {
|
||||
results.push(EvalResult { line: ir.line, result: s });
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
DocumentResult { results, errors }
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -152,4 +145,78 @@ mod tests {
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,6 @@
|
|||
pub mod doc;
|
||||
pub mod document;
|
||||
pub mod eval;
|
||||
pub mod interp;
|
||||
pub mod persist;
|
||||
pub mod ffi;
|
||||
|
|
|
|||
Loading…
Reference in New Issue