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:
parent
91ad4eed12
commit
789349df48
24 changed files with 837 additions and 374 deletions
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue