Error Messaging Redesign

This commit contains the following:

* New data types to support full tracebacks
* New traceback data type used across stl and ast
* Updates to tests
* fixes for error messaging in sym and some stl functions
This commit is contained in:
Ava Apples Affine 2023-05-23 22:06:11 +00:00
parent 91ad4eed12
commit 789349df48
24 changed files with 837 additions and 374 deletions

View file

@ -16,6 +16,7 @@
*/
use crate::eval::eval;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg};
use crate::sym::{SymTable, Symbol};
@ -29,47 +30,63 @@ example: (if my-state-switch
(do-my-thing)
(else-an-other-thing))";
pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let cond: bool;
match *ast.car {
Ctr::Seg(ref cond_form) => {
if let Ctr::Bool(cond_from_eval) = *eval(cond_form, syms)? {
let intermediate = eval(cond_form, syms);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
cond = cond_from_eval;
} else {
return Err("first argument to if must evaluate to be a boolean".to_string());
return Err(start_trace(("if", "first arg must be a bool").into()));
}
}
Ctr::Symbol(ref cond_name) => {
if let Ctr::Bool(cond_from_eval) = *syms.call_symbol(cond_name, &Seg::new(), false)? {
let intermediate = syms.call_symbol(cond_name, &Seg::new(), false);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
cond = cond_from_eval;
} else {
return Err("first argument to if must evaluate to be a boolean".to_string());
return Err(start_trace(("if", "first arg must be a bool").into()));
}
}
Ctr::Bool(cond_from_car) => cond = cond_from_car,
_ => return Err("first argument to if must evaluate to be a boolean".to_string()),
_ => return Err(start_trace(("if", "first arg must be a bool").into())),
}
let then_form: &Seg;
if let Ctr::Seg(ref s) = *ast.cdr {
then_form = s;
} else {
return Err("impossible condition: not enough args to if".to_string());
return Err(start_trace(("if", "not enough args").into()));
}
if cond {
// then
match *then_form.car {
Ctr::Seg(ref first_arg) => Ok(*eval(first_arg, syms)?),
Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating then form").into())),
Ok(val) => Ok(*val)
},
_ => {
let eval_tree = &Seg::from_mono(then_form.car.clone());
let eval_res = *eval(eval_tree, syms)?;
let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating then form").into()))
}
let eval_res = *eval_intermediate?;
if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone())
} else {
Err("impossible condition: list evals to non list".to_string())
Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
}
}
}
@ -77,19 +94,27 @@ pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
// else
if let Ctr::Seg(ref else_form) = *then_form.cdr {
match *else_form.car {
Ctr::Seg(ref second_arg) => Ok(*eval(second_arg, syms)?),
Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating else form").into())),
Ok(val) => Ok(*val)
},
_ => {
let eval_tree = &Seg::from_mono(else_form.car.clone());
let eval_res = *eval(eval_tree, syms)?;
let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating else form").into()))
}
let eval_res = *eval_intermediate?;
if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone())
} else {
Err("impossible condition: list evals to non list".to_string())
Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
}
}
}
} else {
Err("impossible condition: args not in standard form".to_string())
Err(start_trace(("if", "impossible condition: args not in standard form").into()))
}
}
}
@ -108,7 +133,7 @@ Then, the echo form is evaluated, printing 'hello-world'.
Finally, the some-func form is evaluated.
Since the call to some-func is the final form, its value is returned.";
pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut localsyms = syms.clone();
let mut locals = vec![];
let locals_form: &Seg;
@ -116,21 +141,24 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(ref locals_form_list) = *ast.car {
locals_form = locals_form_list;
} else {
return Err("first element of let form must contain local variables".to_string());
return Err(start_trace(("let", "first form does not contain list of local declarations")
.into()));
}
if let Ctr::Seg(ref eval_forms_head) = *ast.cdr {
eval_forms = eval_forms_head;
} else {
return Err("let form should contain one or more elements to evaluate".to_string());
return Err(start_trace(("let", "missing one or more forms to evaluate").into()));
}
let mut err_trace: Traceback = Traceback::new();
// process locals forms
if !locals_form.circuit(&mut |var_decl: &Ctr| -> bool {
if let Ctr::Seg(ref var_form) = *var_decl {
if let Ctr::Symbol(ref name) = *var_form.car {
if let Ctr::Seg(ref var_val_form) = *var_form.cdr {
let var_val_res: Result<Box<Ctr>, String>;
let var_val_res: Result<Box<Ctr>, Traceback>;
if let Ctr::Seg(ref val_form) = *var_val_form.car {
var_val_res = eval(val_form, &mut localsyms);
} else {
@ -145,7 +173,10 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
}
}
if let Err(e) = var_val_res {
eprintln!("failed to evaluate definition of {}: {}", name, e);
err_trace = e
.with_trace(
("let", format!("failed to evaluate definition of {}", name))
.into());
return false;
}
@ -162,21 +193,27 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
// nothing to declare
return true;
} else {
eprintln!("improper declaration of {}: not a list", var_decl);
err_trace = start_trace(
("let", format!("improper declaration of {}: not a list", var_form))
.into());
return false;
}
} else {
eprintln!("improper declaration form: {}", var_decl);
err_trace = start_trace(
("let", format!("improper declaration form: {}", var_decl))
.into());
return false;
}
true
}) {
return Err("local variable declaration failure".to_string());
assert!(err_trace.depth() > 0);
return Err(err_trace);
}
assert!(err_trace.depth() < 1);
let mut result: Box<Ctr> = Box::new(Ctr::None);
if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool {
let res: Result<Box<Ctr>, String>;
let res: Result<Box<Ctr>, Traceback>;
if let Ctr::Seg(ref eval_tree) = eval_form {
res = eval(&eval_tree, &mut localsyms);
} else {
@ -192,15 +229,19 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
}
if let Err(e) = res {
eprintln!("{}", e);
err_trace = e.with_trace(
("let", "evaluation failure")
.into());
return false;
}
result = res.unwrap().clone();
true
}) {
return Err("evaluation failure".to_string());
assert!(err_trace.depth() > 0);
return Err(err_trace);
}
assert!(err_trace.depth() < 1);
for i in locals {
localsyms.remove(&i);
@ -217,12 +258,12 @@ example: (while (check-my-state)
(do-thing-2 args)
(edit-state my-state))";
pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let eval_cond: &Seg;
let outer_maybe: Seg;
let eval_bodies_head: &Seg;
let mut unwrap = false;
let mut result: Result<Box<Ctr>, String> = Ok(Box::new(Ctr::None));
let mut result: Result<Box<Ctr>, Traceback> = Ok(Box::new(Ctr::None));
if let Ctr::Seg(ref cond) = *ast.car {
eval_cond = cond;
@ -235,7 +276,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(ref eval) = *ast.cdr {
eval_bodies_head = eval;
} else {
return Err("expected N more bodies in while form".to_string());
return Err(start_trace(("while", "expected one or more forms to evaluate").into()));
}
loop {
@ -255,7 +296,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
break;
}
} else {
return Err("first body of while form should evaluate to a bool".to_string());
return Err(start_trace(("while", "expected first form to evaluate to a boolean").into()));
}
if !eval_bodies_head.circuit(&mut |body: &Ctr| -> bool {
@ -271,7 +312,7 @@ pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
result = eval(eval_arg, syms);
result.is_ok()
}) {
return Err(result.err().unwrap());
return Err(result.err().unwrap().with_trace(("while", "evaluation failure").into()));
}
}
@ -289,9 +330,9 @@ example: (circuit (eq? (do-operation) myresult)
in this example, do-another-operation will not be called";
pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut cursor = 0;
let mut error: String = String::new();
let mut err_trace = Traceback::new();
let result = ast.circuit(&mut |form: &Ctr| -> bool {
cursor += 1;
let operand: &Seg;
@ -307,7 +348,9 @@ pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
let eval_result = eval(operand, syms);
match eval_result {
Err(s) => error = format!("eval failed at form {cursor}: {s}"),
Err(s) => err_trace = s.with_trace(
("circuit", format!("failed at form {cursor}"))
.into()),
Ok(s) => match *s {
Ctr::Bool(b) => return b,
@ -317,25 +360,26 @@ pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
} else if let Ctr::Integer(i) = *s.car {
return i==0;
} else {
error = "impossible condition in circuit form".to_string();
err_trace = err_trace.with_trace(
("circuit", "impossible condition")
.into());
}
},
Ctr::Integer(i) => return i == 0,
_ => error = format!("{cursor} form did not evaluate to a boolean"),
_ => err_trace = err_trace.with_trace(
("circuit", format!("form {cursor} did not evaluate to a boolean"))
.into()),
},
}
false
});
if !result && !error.is_empty() {
Err(format!("circuit stopped at form {cursor}: {error}"))
if !result && err_trace.depth() > 0 {
Err(err_trace)
} else {
if !result {
eprintln!("circuit stopped at form {cursor}");
}
Ok(Ctr::Bool(result))
}
}