added better error messages inspired by rustc/cargo

but not as good
This commit is contained in:
Mark
2023-10-23 21:48:15 +02:00
parent 62ed8fc2bd
commit ea95a16c30
30 changed files with 557 additions and 260 deletions

View File

@@ -24,7 +24,7 @@ impl Config {
for t in a.types.iter() {
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
if t.0.len() != 2 {
return Err(CheckError(format!("cannot use try with tuple argument where len != 2 (got len {})", t.0.len())));
return Err(format!("cannot use try with tuple argument where len != 2 (got len {})", t.0.len()).into());
}
let arg_type = &t.0[0];
let functions = &t.0[1];
@@ -56,7 +56,7 @@ impl Config {
},
});
} else {
return Err(CheckError(format!("try: arguments f1-fn must be functions")));
return Err(format!("try: arguments f1-fn must be functions").into());
}
}
// found a function that won't fail for this arg_type!
@@ -68,18 +68,26 @@ impl Config {
}
}
if tuple_fallible || !tuple_possible {
return Err(CheckError(format!("try: if the argument is {arg_type}, there is no infallible function. add a fallback function to handle this case! Errors for all functions: {}", func_errors.iter().enumerate().map(|(i, v)| match v {
Some(e) => format!("\n{i}: {}", e.0),
None => "\n({i}: no error)".to_owned(),
}).collect::<String>())));
// if the argument is {arg_type}, there is no infallible function. add a fallback function to handle this case!
let mut e = CheckError::new()
.msg(format!("if the argument is {arg_type}, there is no infallible function."))
.msg(format!("Add a fallback function to handle this case!"));
for (i, err) in func_errors.into_iter().enumerate() {
if let Some(err) = err {
e = e
.msg(format!("Error for function #{i}:"))
.err(err);
}
}
return Err(e);
}
} else {
return Err(CheckError(format!("try: argument must be (arg, (f1, f2, f3, ..., fn))")));
return Err(format!("try: argument must be (arg, (f1, f2, f3, ..., fn))").into());
}
}
}
} else {
return Err(CheckError(format!("cannot use try with non-tuple argument")));
return Err(format!("cannot use try with non-tuple argument").into());
}
}
Ok(out)
@@ -106,7 +114,7 @@ impl Config {
out: Arc::new(|a, _i| if a.is_included_in(&data::int::IntT) {
Ok(Type::empty())
} else {
Err(CheckError(format!("cannot call exit with non-int argument")))
Err(format!("cannot call exit with non-int argument").into())
}),
run: Arc::new(|a, _i| {
std::process::exit(a.get().as_any().downcast_ref::<data::int::Int>().map(|i| i.0 as _).unwrap_or(1));
@@ -120,7 +128,7 @@ impl Config {
out: Arc::new(|a, _i| {
for t in &a.types {
if t.as_any().downcast_ref::<data::string::StringT>().is_none() && t.as_any().downcast_ref::<data::tuple::TupleT>().is_none() {
return Err(crate::program::run::CheckError(format!("cannot get length of {t} (must be a tuple or a string)")));
return Err(format!("cannot get length of {t} (must be a tuple or a string)").into());
}
}
Ok(Type::new(data::int::IntT))
@@ -147,16 +155,16 @@ impl Config {
for t in (t.0)(&Type::empty_tuple())?.types {
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
if t.0.len() > 1 {
return Err(crate::program::run::CheckError(format!("called loop with funcion that might return a tuple of length > 1")));
return Err(format!("called loop with funcion that might return a tuple of length > 1").into());
} else if let Some(v) = t.0.first() {
o.add(Arc::new(v.clone()))
}
} else {
return Err(crate::program::run::CheckError(format!("called loop with funcion that might return something other than a tuple")));
return Err(format!("called loop with funcion that might return something other than a tuple").into());
}
}
} else {
return Err(crate::program::run::CheckError(format!("called loop on a non-function")));
return Err(format!("called loop on a non-function").into());
}
}
Ok(o)
@@ -182,7 +190,7 @@ impl Config {
out: Arc::new(|a, _i| {
for t in &a.types {
if t.iterable().is_none() {
return Err(crate::program::run::CheckError(format!("called eq on non-iterable")))
return Err(format!("called eq on non-iterable").into())
}
}
Ok(Type::new(data::bool::BoolT))
@@ -212,7 +220,8 @@ impl Config {
Data::new(data::function::Function {
info: Arc::new(Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, _i| if let Some(v) = a.dereference() { Ok(v) } else { Err(crate::program::run::CheckError(format!("cannot dereference type {a}")))}),
out: Arc::new(|a, _i| if let Some(v) = a.dereference() { Ok(v) } else { Err(format!("cannot dereference type {a}").into())
}),
run: Arc::new(|a, _i| {
if let Some(r) = a
.get()

View File

@@ -6,10 +6,7 @@ use std::{
use crate::{
data::{self, Data, MersData, MersType, Type},
program::{
self,
run::{CheckError, CheckInfo},
},
program::{self, run::CheckInfo},
};
use super::Config;
@@ -37,7 +34,7 @@ impl Config {
Arc::new(RunCommandErrorT)
]))
} else {
return Err(CheckError(format!("run_command called with invalid arguments (must be (String, Iter<String>))")));
return Err(format!("run_command called with invalid arguments (must be (String, Iter<String>))").into());
}
}),
run: Arc::new(|a, _i| {

View File

@@ -19,9 +19,7 @@ impl Config {
if let Some(v) = a.get() {
Ok(v)
} else {
Err(program::run::CheckError(format!(
"called get on non-gettable type {a}"
)))
Err(format!("called get on non-gettable type {a}").into())
}
}),
run: Arc::new(|a, _i| {

View File

@@ -5,10 +5,7 @@ use std::{
use crate::{
data::{self, Data, MersData, MersType, Type},
program::{
self,
run::{CheckError, CheckInfo},
},
program::{self, run::CheckInfo},
};
use super::Config;
@@ -42,24 +39,22 @@ impl Config {
for f in f {
let ret = f.0(&iter)?;
if !ret.is_zero_tuple() {
return Err(CheckError(format!(
"for_each function must return (), not {ret}"
)));
return Err(format!("for_each function must return (), not {ret}").into());
}
}
} else {
return Err(CheckError(format!(
return Err(format!(
"for_each called on tuple not containing iterable and function: {v} is {}",
if v.iterable().is_some() { "iterable" } else { "not iterable" },
)));
).into());
}
} else {
return Err(CheckError(format!(
return Err(format!(
"for_each called on tuple with len < 2"
)));
).into());
}
} else {
return Err(CheckError(format!("for_each called on non-tuple")));
return Err(format!("for_each called on non-tuple").into());
}
}
Ok(Type::empty_tuple())

View File

@@ -5,10 +5,7 @@ use std::{
use crate::{
data::{self, Data, MersData, MersType, Type},
program::{
self,
run::{CheckError, CheckInfo},
},
program::{self, run::CheckInfo},
};
use super::Config;
@@ -36,14 +33,14 @@ impl Config {
if let Some(t) = t.as_any().downcast_ref::<ListT>() {
out.add(Arc::new(t.0.clone()));
} else {
return Err(CheckError(format!(
return Err(format!(
"pop: found a reference to {t}, which is not a list"
)));
).into());
}
}
Ok(out)
} else {
return Err(CheckError(format!("pop: not a reference: {a}")));
return Err(format!("pop: not a reference: {a}").into());
}
}),
run: Arc::new(|a, _i| {
@@ -77,9 +74,9 @@ impl Config {
for t in a.types.iter() {
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
if t.0.len() != 2 {
return Err(CheckError(format!(
return Err(format!(
"push: tuple must have length 2"
)));
).into());
}
let a = &t.0[0];
let new = &t.0[1];
@@ -87,23 +84,24 @@ impl Config {
for t in a.types.iter() {
if let Some(t) = t.as_any().downcast_ref::<ListT>() {
if !new.is_included_in(&t.0) {
return Err(CheckError(format!(
return Err(format!(
"push: found a reference to {t}, which is a list which can't contain elements of type {new}"
)));
).into());
}
} else {
return Err(CheckError(format!(
"push: found a reference to {t}, which is not a list"
)));
return Err(format!(
"push: found a reference to {t}, which is not a list"
).into());
}
}
} else {
return Err(CheckError(format!(
return Err(format!(
"push: first element in tuple not a reference: {a}"
)));
).into());
}
} else {
return Err(CheckError(format!("push: not a tuple: {t}")));
return Err(format!("push: not a tuple: {t}")
.into());
}
}
Ok(Type::empty_tuple())
@@ -138,9 +136,9 @@ impl Config {
if let Some(v) = a.iterable() {
Ok(Type::new(ListT(v)))
} else {
Err(program::run::CheckError(format!(
Err(format!(
"cannot iterate over type {a}"
)))
).into())
}
}),
run: Arc::new(|a, _i| {

View File

@@ -14,6 +14,8 @@ impl Config {
/// `sum: fn` returns the sum of all the numbers in the tuple
/// `diff: fn` returns b - a
/// `product: fn` returns the product of all the numbers in the tuple
/// `div: fn` returns a / b. Performs integer division if a and b are both integers.
/// `modulo: fn` returns a % b
/// `signum: fn` returns 1 for positive numbers, -1 for negative ones and 0 for 0 (always returns an Int, even when input is Float)
/// `parse_int: fn` parses a string to an int, returns () on failure
/// `parse_float: fn` parses a string to an int, returns () on failure
@@ -22,8 +24,6 @@ impl Config {
/// `round: fn` rounds the float and returns an int
/// `ceil: fn` rounds the float [?] and returns an int
/// `floor: fn` rounds the float [?] and returns an int
/// `div: fn` returns a / b. Performs integer division if a and b are both integers.
/// `modulo: fn` returns a % b
pub fn with_math(self) -> Self {
self.add_var("parse_float".to_string(), Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()),
@@ -35,7 +35,7 @@ impl Config {
Arc::new(data::tuple::TupleT(vec![])),
]))
} else {
Err(CheckError(format!("parse_float called on non-string type")))
Err(format!("parse_float called on non-string type").into())
}
}),
run: Arc::new(|a, _i| {
@@ -55,7 +55,7 @@ impl Config {
Arc::new(data::tuple::TupleT(vec![])),
]))
} else {
Err(CheckError(format!("parse_float called on non-string type")))
Err(format!("parse_float called on non-string type").into())
}
}),
run: Arc::new(|a, _i| {
@@ -72,7 +72,7 @@ impl Config {
if a.is_included_in(&Type::newm(vec![Arc::new(data::int::IntT), Arc::new(data::float::FloatT)])) {
Ok(Type::new(data::int::IntT))
} else {
Err(CheckError(format!("signum called on non-number type")))
Err(format!("signum called on non-number type").into())
}
}),
run: Arc::new(|a, _i| {
@@ -88,38 +88,47 @@ impl Config {
}
} else { unreachable!("called signum on non-number type")}))
})
}))
})) .add_var("div".to_string(), Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, _i| two_tuple_to_num(a, "div")),
run: Arc::new(|a, _i| if let Some(t) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
let left = t.0[0].get();
let right = t.0[1].get();
let (left, right) = (left.as_any(), right.as_any());
match (left.downcast_ref::<data::int::Int>(), left.downcast_ref::<data::float::Float>(),
right.downcast_ref::<data::int::Int>(), right.downcast_ref::<data::float::Float>()
) {
(Some(data::int::Int(l)), None, Some(data::int::Int(r)), None) => Data::new(data::int::Int(l / r)),
(Some(data::int::Int(l)), None, None, Some(data::float::Float(r))) => Data::new(data::float::Float(*l as f64 / r)),
(None, Some(data::float::Float(l)), Some(data::int::Int(r)), None) => Data::new(data::float::Float(l / *r as f64)),
(None, Some(data::float::Float(l)), None, Some(data::float::Float(r))) => Data::new(data::float::Float(l / r)),
_ => unreachable!(),
}
} else { unreachable!() }),
})).add_var("modulo".to_string(), Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, _i| two_tuple_to_num(a, "modulo")),
run: Arc::new(|a, _i| if let Some(t) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
let left = t.0[0].get();
let right = t.0[1].get();
let (left, right) = (left.as_any(), right.as_any());
match (left.downcast_ref::<data::int::Int>(), left.downcast_ref::<data::float::Float>(),
right.downcast_ref::<data::int::Int>(), right.downcast_ref::<data::float::Float>()
) {
(Some(data::int::Int(l)), None, Some(data::int::Int(r)), None) => Data::new(data::int::Int(l % r)),
(Some(data::int::Int(l)), None, None, Some(data::float::Float(r))) => Data::new(data::float::Float(*l as f64 % r)),
(None, Some(data::float::Float(l)), Some(data::int::Int(r)), None) => Data::new(data::float::Float(l % *r as f64)),
(None, Some(data::float::Float(l)), None, Some(data::float::Float(r))) => Data::new(data::float::Float(l % r)),
_ => unreachable!(),
}
} else { unreachable!() }),
}))
.add_var("diff".to_string(), Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, _i| {
let mut float = false;
for t in &a.types {
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
if t.0.len() != 2 {
return Err(CheckError(format!("Called diff with a tuple where len != 2")));
}
for (t, side) in [(&t.0[0], "left"), (&t.0[1], "right")] {
for t in t.types.iter() {
if t.as_any().is::<data::float::FloatT>() {
float = true;
} else if !t.as_any().is::<data::int::IntT>() {
return Err(CheckError(format!("Called diff, but the {side} side of the tuple had type {t}, which isn't Int/Float.")));
}
}
}
} else {
return Err(CheckError(format!("Called diff on a non-tuple")));
}
}
Ok(if a.types.is_empty() {
Type::empty()
} else if float {
Type::new(data::float::FloatT)
} else {
Type::new(data::int::IntT)
})
}),
out: Arc::new(|a, _i| two_tuple_to_num(a, "diff")),
run: Arc::new(|a, _i| if let Some(t) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
let left = t.0[0].get();
let right = t.0[1].get();
@@ -156,12 +165,12 @@ impl Config {
}) {
floats = true;
} else {
return Err(CheckError(format!("cannot get sum of iterator over type {i} because it contains types that aren't int/float")))
return Err(format!("cannot get sum of iterator over type {i} because it contains types that aren't int/float").into())
}
} else {
return Err(CheckError(format!(
return Err(format!(
"cannot get sum of non-iterable type {a}"
)));
).into());
}
}
Ok(match (ints, floats) {
@@ -217,12 +226,12 @@ impl Config {
}) {
floats = true;
} else {
return Err(CheckError(format!("cannot get product of iterator over type {i} because it contains types that aren't int/float")))
return Err(format!("cannot get product of iterator over type {i} because it contains types that aren't int/float").into())
}
} else {
return Err(CheckError(format!(
return Err(format!(
"cannot get product of non-iterable type {a}"
)));
).into());
}
}
Ok(match (ints, floats) {
@@ -259,3 +268,36 @@ impl Config {
)
}
}
/// (int, int) -> int
/// (int, float) -> float
/// (float, int) -> float
/// (float, float) -> float
fn two_tuple_to_num(a: &Type, func_name: &str) -> Result<Type, CheckError> {
let mut float = false;
for t in &a.types {
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
if t.0.len() != 2 {
return Err(format!("Called {func_name} with a tuple where len != 2").into());
}
for (t, side) in [(&t.0[0], "left"), (&t.0[1], "right")] {
for t in t.types.iter() {
if t.as_any().is::<data::float::FloatT>() {
float = true;
} else if !t.as_any().is::<data::int::IntT>() {
return Err(format!("Called {func_name}, but the {side} side of the tuple had type {t}, which isn't Int/Float.").into());
}
}
}
} else {
return Err(format!("Called {func_name} on a non-tuple").into());
}
}
Ok(if a.types.is_empty() {
Type::empty()
} else if float {
Type::new(data::float::FloatT)
} else {
Type::new(data::int::IntT)
})
}

View File

@@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex};
use crate::{
data::{self, Data, MersType, Type},
program::run::{CheckError, CheckInfo, Info},
program::run::{CheckInfo, Info},
};
use super::Config;
@@ -21,7 +21,7 @@ impl Config {
out: Arc::new(|a, _i| if a.is_included_in(&data::string::StringT) {
Ok(Type::new(data::string::StringT))
} else {
Err(CheckError(format!("cannot call trim on non-strings")))
Err(format!("cannot call trim on non-strings").into())
}),
run: Arc::new(|a, _i| {
Data::new(data::string::String(a.get().as_any().downcast_ref::<data::string::String>().unwrap().0.trim().to_owned()))
@@ -32,7 +32,7 @@ impl Config {
out: Arc::new(|a, _i| if a.iterable().is_some() {
Ok(Type::new(data::string::StringT))
} else {
Err(CheckError(format!("concat called on non-iterable type {a}")))
Err(format!("concat called on non-iterable type {a}").into())
}),
run: Arc::new(|a, _i| Data::new(data::string::String(a.get().iterable().unwrap().map(|v| v.get().to_string()).collect()))),
})).add_var("to_string".to_string(), Data::new(data::function::Function {
@@ -49,7 +49,7 @@ impl Config {
Arc::new(data::int::IntT),
]))
} else {
Err(CheckError(format!("wrong args for index_of: must be (string, string)")))
Err(format!("wrong args for index_of: must be (string, string)").into())
}),
run: Arc::new(|a, _i| index_of(a, false)),
})).add_var("index_of_rev".to_string(), Data::new(data::function::Function {
@@ -61,7 +61,7 @@ impl Config {
Arc::new(data::int::IntT),
]))
} else {
Err(CheckError(format!("wrong args for index_of: must be (string, string)")))
Err(format!("wrong args for index_of: must be (string, string)").into())
}),
run: Arc::new(|a, _i| index_of(a, true)),
})).add_var("substring".to_string(), Data::new(data::function::Function {
@@ -71,19 +71,19 @@ impl Config {
for t in a.types.iter() {
if let Some(t) = t.as_any().downcast_ref::<data::tuple::TupleT>() {
if t.0.len() != 2 && t.0.len() != 3 {
return Err(CheckError(format!("cannot call substring with tuple argument of len != 3")));
return Err(format!("cannot call substring with tuple argument of len != 3").into());
}
if !t.0[0].is_included_in(&data::string::StringT) {
return Err(CheckError(format!("cannot call substring with tuple argument that isn't (*string*, int, int)")));
return Err(format!("cannot call substring with tuple argument that isn't (*string*, int, int)").into());
}
if !t.0[1].is_included_in(&data::int::IntT) {
return Err(CheckError(format!("cannot call substring with tuple argument that isn't (string, *int*, int)")));
return Err(format!("cannot call substring with tuple argument that isn't (string, *int*, int)").into());
}
if t.0.len() > 2 && !t.0[2].is_included_in(&data::int::IntT) {
return Err(CheckError(format!("cannot call substring with tuple argument that isn't (string, int, *int*)")));
return Err(format!("cannot call substring with tuple argument that isn't (string, int, *int*)").into());
}
} else {
return Err(CheckError(format!("cannot call substring with non-tuple argument.")));
return Err(format!("cannot call substring with non-tuple argument.").into());
}
}
Ok(if a.types.is_empty() {