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::segment::{Ctr, Seg};
use crate::sym::{SymTable, ValueType};
use crate::error::{Traceback, start_trace};
fn isnumeric(arg: &Ctr) -> bool {
match arg {
@ -30,7 +31,7 @@ pub const ADD_DOCSTRING: &str =
Adds each arg up to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled.";
pub fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let mut res = Ctr::Integer(0);
let mut culprit: Ctr = Ctr::None;
let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool {
@ -44,7 +45,9 @@ pub fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
});
if !type_consistent {
Err(format!("{} is not a number!", culprit))
Err(start_trace(
("add", format!("{} is not a number!", culprit))
.into()))
} else {
Ok(res)
}
@ -54,9 +57,11 @@ pub const SUB_DOCSTRING: &str = "traverses over N args, which must all evaluate
Subtracts each arg from the first leading to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled.";
pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
if !isnumeric(&*ast.car) {
return Err(format!("{} is not a number", &*ast.car));
pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
if !isnumeric(ast.car.as_ref()) {
return Err(start_trace(
("sub", format!("{} is not a number!", ast.car.as_ref()))
.into()))
}
let mut res = *ast.car.clone();
let mut culprit: Ctr = Ctr::None;
@ -72,12 +77,17 @@ pub fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
});
if !type_consistent {
Err(format!("{} is not a number", culprit))
Err(start_trace(
("sub", format!("{} is not a number!", culprit))
.into()))
} else {
Ok(res)
}
} else {
Err("requires at least two operands".to_string())
Err(start_trace(
("sub", "expected at least two inputs")
.into()))
}
}
@ -85,20 +95,26 @@ pub const DIV_DOCSTRING: &str = "takes two args, which must both evaluate to an
divides arg1 by arg2. WARNING: does not acocunt for under/overflows or float precision.
Consult source code for a better understanding of how extreme values will be handled.";
pub fn div_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn div_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
return Err(start_trace(
("div", format!("{} is not a number!", ast.car.as_ref()))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
return Err(start_trace(
("div", format!("{} is not a number!", second))
.into()))
}
Ok(first / second)
} else {
Err("impossible error: needs two arguments".to_string())
Err(start_trace(
("div", "expected exactly two inputs")
.into()))
}
}
@ -107,7 +123,7 @@ pub const MUL_DOCSTRING: &str =
Multiplies each arg up to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled.";
pub fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let mut res = Ctr::Integer(1);
let mut culprit: Ctr = Ctr::None;
let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool {
@ -121,7 +137,9 @@ pub fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
});
if !type_consistent {
Err(format!("{} is not a number!", culprit))
Err(start_trace(
("mul", format!("{} is not a number!", culprit))
.into()))
} else {
Ok(res)
}
@ -132,19 +150,23 @@ This will work for a float or a potentially a string.
If the cast to Integer fails, it will return Nothing and print an error.
Casting a float to an int will drop its decimal.";
pub fn intcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn intcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
// special case for float
if let Ctr::Float(f) = *ast.car {
Ok(Ctr::Integer(f as i128))
} else if let Ctr::String(ref s) = *ast.car {
let int = str::parse::<i128>(s);
if int.is_err() {
Err(int.err().unwrap().to_string())
Err(start_trace(
("int", int.err().unwrap().to_string())
.into()))
} else {
Ok(Ctr::Integer(int.ok().unwrap()))
}
} else {
Err("int cast only takes a float or a string".to_string())
Err(start_trace(
("int", "expected a float or a string")
.into()))
}
}
@ -153,19 +175,23 @@ This will work for an integer or potentially a string.
If the cast to integer fails, this function will return nothing and print an error.
Casting an integer to a float can result in bad behaviour since float nodes are based on 64bit floats and int nodes are based on 128 bit integers.";
pub fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
// special case for float
if let Ctr::Integer(i) = *ast.car {
Ok(Ctr::Float(i as f64))
} else if let Ctr::String(ref s) = *ast.car {
let int = str::parse::<f64>(&s);
if int.is_err() {
Err(int.err().unwrap().to_string())
let flt = str::parse::<f64>(&s);
if flt.is_err() {
Err(start_trace(
("float", flt.err().unwrap().to_string())
.into()))
} else {
Ok(Ctr::Float(int.ok().unwrap()))
Ok(Ctr::Float(flt.ok().unwrap()))
}
} else {
Err("float cast only takes an integer or a string".to_string())
Err(start_trace(
("float", "expected a string or an integer")
.into()))
}
}
@ -178,34 +204,46 @@ PANIC CASES:
- an integer exceeding the size of a float64 is raised to a float power
- an integer is rased to the power of another integer exceeding the max size of an unsigned 32bit integer";
pub fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
return Err(start_trace(
("exp", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err("impossible error: needs two arguments".to_string());
return Err(start_trace(
("exp", "expected at least two inputs")
.into()))
}
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
return Err(start_trace(
("exp", format!("{} is not a number!", second))
.into()))
}
match first {
Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(lf, rf))),
Ctr::Integer(ri) => Ok(Ctr::Float(f64::powi(lf, ri as i32))),
_ => Err("exp not implemented for these arguments".to_string()),
_ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(li as f64, rf))),
Ctr::Integer(ri) => Ok(Ctr::Integer(li.pow(ri as u32))),
_ => Err("exp not implemented for these arguments".to_string()),
_ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
},
_ => Err("exp not implemented for these arguments".to_string()),
_ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
}
}
@ -218,19 +256,25 @@ PANIC CASES:
- An integer larger than a max f64 is modulo a float
";
pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
return Err(start_trace(
("mod", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err("impossible error: needs two arguments".to_string());
return Err(start_trace(
("mod", "expected at least two inputs")
.into()))
}
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
return Err(start_trace(
("mod", format!("{} is not a number!", second))
.into()))
}
let mut ret = Seg::new();
@ -245,7 +289,9 @@ pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
ret.append(Box::new(Ctr::Integer((lf / ri as f64) as i128)));
ret.append(Box::new(Ctr::Integer((lf % ri as f64) as i128)));
}
_ => return Err("mod not implemented for these arguments".to_string()),
_ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => {
@ -256,10 +302,14 @@ pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
ret.append(Box::new(Ctr::Integer(li / ri)));
ret.append(Box::new(Ctr::Integer(li % ri)));
}
_ => return Err("mod not implemented for these arguments".to_string()),
_ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
},
_ => return Err("mod not implemented for these arguments".to_string()),
_ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
}
Ok(Ctr::Seg(ret))
@ -269,34 +319,45 @@ pub const ISGT_DOCSTRING: &str = "takes two args, which must both evaluate to an
Returns true or false according to whether the first argument is bigger than the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
pub fn isgt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn isgt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
return Err(start_trace(
("gt?", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err("impossible error: needs two arguments".to_string());
}
return Err(start_trace(
("gt?", "expected at least two inputs")
.into())) }
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
return Err(start_trace(
("gt?", format!("{} is not a number!", second))
.into()))
}
match first {
Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(lf > rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(lf > ri as f64)),
_ => Err("gt? not implemented for these arguments".to_string()),
_ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(li as f64 > rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(li > ri)),
_ => Err("gt? not implemented for these arguments".to_string()),
_ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
},
_ => Err("gt? not implemented for these arguments".to_string()),
_ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
}
}
@ -304,34 +365,47 @@ pub const ISLT_DOCSTRING: &str = "takes two args, which must both evaluate to an
Returns true or false according to whether the first argument is smaller than the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
pub fn islt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
pub fn islt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err("first argument must be numeric".to_string());
return Err(start_trace(
("lt?", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err("impossible error: needs two arguments".to_string());
return Err(start_trace(
("lt?", "expected at least two inputs")
.into()))
}
if !isnumeric(&second) {
return Err("second argument must be numeric".to_string());
return Err(start_trace(
("lt?", format!("{} is not a number!", second))
.into()))
}
match first {
Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(lf < rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(lf < ri as f64)),
_ => Err("gt? not implemented for these arguments".to_string()),
_ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool((li as f64) < rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(li < ri)),
_ => Err("gt? not implemented for these arguments".to_string()),
_ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
},
_ => Err("gt? not implemented for these arguments".to_string()),
_ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
}
}
@ -339,11 +413,16 @@ pub const ISGTE_DOCSTRING: &str = "takes two args, which must both evaluate to a
Returns true or false according to whether the first argument is greater than or equal to the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
pub fn isgte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Bool(b) = islt_callback(ast, syms)? {
Ok(Ctr::Bool(!b))
} else {
Err("impossible state: islt returned non-bool".to_string())
pub fn isgte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
match islt_callback(ast, syms) {
Ok(s) => if let Ctr::Bool(b) = s {
Ok(Ctr::Bool(!b))
} else {
Err(start_trace(
("gte?", format!("madness: lt? returned non bool {s}"))
.into()))
},
Err(e) => Err(e.with_trace(("gte?", "error calling lt?").into())),
}
}
@ -351,11 +430,16 @@ pub const ISLTE_DOCSTRING: &str = "takes two args, which must both evaluate to a
Returns true or false according to whether the first argument is less than or equal to the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
pub fn islte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Bool(b) = isgt_callback(ast, syms)? {
Ok(Ctr::Bool(!b))
} else {
Err("impossible state: islt returned non-bool".to_string())
pub fn islte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
match isgt_callback(ast, syms) {
Ok(s) => if let Ctr::Bool(b) = s {
Ok(Ctr::Bool(!b))
} else {
Err(start_trace(
("lte?", format!("madness: gt? returned non bool {s}"))
.into()))
},
Err(e) => Err(e.with_trace(("lte?", "error calling gt?").into())),
}
}
@ -368,28 +452,39 @@ This call is similar to the following:
(def counter '' (add counter 1))
with the caveat that your docstring is preserved.";
pub fn inc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub fn inc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone();
} else {
return Err("argument should be a symbol".to_string());
return Err(start_trace(
("inc", "expected input to be a symbol")
.into()));
}
let mut sym = syms
.remove(&var_name)
.expect(&format!("symbol {var_name} is not defined"));
let sym_ret = syms
.remove(&var_name);
if let None = sym_ret {
return Err(start_trace(
("inc", format!("input ({var_name}) is not defined"))
.into()))
}
let mut sym = sym_ret.unwrap();
if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Integer(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b + 1)));
} else {
syms.insert(var_name, sym);
return Err("can only increment an integer".to_string());
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("inc", format!("expected {var_name} to be an integer"))
.into()));
}
} else {
syms.insert(var_name, sym);
return Err("cannot increment a function".to_string());
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("inc", format!("expected {var_name} to be an integer"))
.into()));
}
syms.insert(var_name, sym);
@ -405,28 +500,39 @@ This call is similar to the following:
(def counter '' (sub counter 1))
with the caveat that your docstring is preserved.";
pub fn dec_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
pub fn dec_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone();
} else {
return Err("argument should be a symbol".to_string());
return Err(start_trace(
("dec", "expected input to be a symbol")
.into()));
}
let mut sym = syms
.remove(&var_name)
.expect(&format!("symbol {var_name} is not defined"));
let sym_ret = syms
.remove(&var_name);
if let None = sym_ret {
return Err(start_trace(
("dec", format!("input ({var_name}) is not defined"))
.into()))
}
let mut sym = sym_ret.unwrap();
if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Integer(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b - 1)));
} else {
syms.insert(var_name, sym);
return Err("can only decrement an integer".to_string());
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("dec", format!("expected {var_name} to be an integer"))
.into()));
}
} else {
syms.insert(var_name, sym);
return Err("cannot decrement a function".to_string());
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("dec", format!("expected {var_name} to be an integer"))
.into()));
}
syms.insert(var_name, sym);