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() {

View File

@@ -1,10 +1,10 @@
use crate::{parsing::SourcePos, program};
use crate::program::{self, run::SourceRange};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct AssignTo {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub target: Box<dyn MersStatement>,
pub source: Box<dyn MersStatement>,
}

View File

@@ -1,10 +1,13 @@
use crate::{info, parsing::SourcePos, program};
use crate::{
info,
program::{self, run::SourceRange},
};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Block {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub statements: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Block {

View File

@@ -1,11 +1,11 @@
use crate::parsing::SourcePos;
use crate::program::run::SourceRange;
use crate::{info, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Chain {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub first: Box<dyn MersStatement>,
pub chained: Box<dyn MersStatement>,
}

View File

@@ -1,4 +1,4 @@
use crate::parsing::SourcePos;
use crate::program::run::SourceRange;
use std::sync::{Arc, Mutex};
use crate::{
@@ -10,7 +10,7 @@ use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Function {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub arg: Box<dyn MersStatement>,
pub run: Box<dyn MersStatement>,
}

View File

@@ -1,10 +1,10 @@
use crate::{parsing::SourcePos, program};
use crate::program::{self, run::SourceRange};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct If {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub condition: Box<dyn MersStatement>,
pub on_true: Box<dyn MersStatement>,
pub on_false: Option<Box<dyn MersStatement>>,

View File

@@ -1,11 +1,11 @@
use crate::parsing::SourcePos;
use crate::program;
use crate::program::run::SourceRange;
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct InitTo {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub target: Box<dyn MersStatement>,
pub source: Box<dyn MersStatement>,
}

View File

@@ -1,10 +1,13 @@
use crate::{info, parsing::SourcePos, program};
use crate::{
info,
program::{self, run::SourceRange},
};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Tuple {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub elems: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Tuple {

View File

@@ -1,11 +1,11 @@
use crate::parsing::SourcePos;
use crate::program::run::SourceRange;
use crate::{data::Data, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Value {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub data: Data,
}

View File

@@ -1,10 +1,13 @@
use crate::{info::Local, parsing::SourcePos, program};
use crate::{
info::Local,
program::{self, run::SourceRange},
};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Variable {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub is_ref: bool,
pub var: String,
}

View File

@@ -1,13 +1,12 @@
use crate::{
data::{self, Data, MersType, Type},
parsing::SourcePos,
};
use colored::Colorize;
use super::{CheckError, CheckInfo, MersStatement};
use crate::data::{self, Data, MersType, Type};
use super::{CheckError, CheckInfo, MersStatement, SourceRange};
#[derive(Debug)]
pub struct AssignTo {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub is_init: bool,
pub target: Box<dyn MersStatement>,
pub source: Box<dyn MersStatement>,
@@ -20,21 +19,51 @@ impl MersStatement for AssignTo {
init_to: Option<&Type>,
) -> Result<Type, CheckError> {
if init_to.is_some() {
return Err(CheckError(
"can't init to statement type AssignTo".to_string(),
));
return Err("can't init to statement type AssignTo".to_string().into());
}
let source = self.source.check(info, None)?;
let target = self.target.check(info, Some(&source))?;
let target = match self.target.check(info, Some(&source)) {
Ok(v) => v,
Err(e) => {
return Err(CheckError::new()
.src(vec![
(self.pos_in_src, None),
(
self.target.source_range(),
Some(colored::Color::BrightYellow),
),
(self.source.source_range(), Some(colored::Color::BrightCyan)),
])
.msg(format!("Cannot initialize:"))
.err(e))
}
};
if !self.is_init {
if let Some(t) = target.dereference() {
if !source.is_included_in(&t) {
return Err(CheckError(format!(
"can't assign {source} to {target} because it isn't included in {t}!"
)));
return Err(CheckError::new()
.src(vec![
(self.pos_in_src, None),
(
self.target.source_range(),
Some(colored::Color::BrightYellow),
),
(self.source.source_range(), Some(colored::Color::BrightCyan)),
])
.msg(format!(
"can't assign {} to {} because it isn't included in {}",
source.to_string().bright_cyan(),
target.to_string().bright_yellow(),
t
)));
}
} else {
return Err(CheckError(format!("can't assign to non-reference!")));
return Err(CheckError::new()
.src(vec![
(self.pos_in_src, None),
(self.target.source_range(), Some(colored::Color::Red)),
])
.msg(format!("can't assign to non-reference!")));
}
}
Ok(Type::empty_tuple())
@@ -48,7 +77,7 @@ impl MersStatement for AssignTo {
fn has_scope(&self) -> bool {
false
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}

View File

@@ -1,10 +1,10 @@
use crate::{data::Type, parsing::SourcePos};
use crate::data::Type;
use super::{CheckError, MersStatement};
use super::{MersStatement, SourceRange};
#[derive(Debug)]
pub struct Block {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub statements: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Block {
@@ -14,7 +14,7 @@ impl MersStatement for Block {
init_to: Option<&Type>,
) -> Result<crate::data::Type, super::CheckError> {
if init_to.is_some() {
return Err(CheckError("can't init to statement type Block".to_string()));
return Err("can't init to statement type Block".to_string().into());
}
let mut o = Type::empty_tuple();
for s in &self.statements {
@@ -32,7 +32,7 @@ impl MersStatement for Block {
fn has_scope(&self) -> bool {
true
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}

View File

@@ -1,15 +1,14 @@
use std::sync::Arc;
use crate::{
data::{Data, Type},
parsing::SourcePos,
};
use colored::Colorize;
use super::{CheckError, MersStatement};
use crate::data::{Data, Type};
use super::{CheckError, MersStatement, SourceRange};
#[derive(Debug)]
pub struct Chain {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub first: Box<dyn MersStatement>,
pub chained: Box<dyn MersStatement>,
}
@@ -20,7 +19,7 @@ impl MersStatement for Chain {
init_to: Option<&Type>,
) -> Result<Type, CheckError> {
if init_to.is_some() {
return Err(CheckError("can't init to statement type Chain".to_string()));
return Err("can't init to statement type Chain".to_string().into());
}
let arg = self.first.check(info, None)?;
let func = self.chained.check(info, None)?;
@@ -32,15 +31,34 @@ impl MersStatement for Chain {
{
match (func.0)(&arg) {
Ok(t) => o.add(Arc::new(t)),
Err(e) =>
return Err(CheckError(format!(
"cannot run this function with this argument (type: {arg}), because it would cause the following error:\n{e}"
))),
Err(e) => {
return Err(CheckError::new()
.src(vec![
(self.pos_in_src, None),
(self.first.source_range(), Some(colored::Color::BrightCyan)),
(
self.chained.source_range(),
Some(colored::Color::BrightMagenta),
),
])
.msg(format!(
"Can't call {} with an argument of type {}:",
"this function".bright_magenta(),
arg.to_string().bright_cyan()
))
.err(e))
}
}
} else {
return Err(CheckError(format!(
"cannot chain with a non-function ({func})"
)));
return Err(CheckError::new()
.src(vec![
(self.pos_in_src, None),
(self.chained.source_range(), Some(colored::Color::BrightRed)),
])
.msg(format!(
"cannot chain with a non-function ({})",
func.to_string().bright_red()
)));
}
}
Ok(o)
@@ -58,7 +76,7 @@ impl MersStatement for Chain {
fn has_scope(&self) -> bool {
false
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}

View File

@@ -1,15 +1,12 @@
use std::sync::Arc;
use crate::{
data::{self, Data, MersData, Type},
parsing::SourcePos,
};
use crate::data::{self, Data, MersData, Type};
use super::{CheckError, MersStatement};
use super::{MersStatement, SourceRange};
#[derive(Debug)]
pub struct Function {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub func_no_info: data::function::Function,
}
@@ -20,9 +17,7 @@ impl MersStatement for Function {
init_to: Option<&Type>,
) -> Result<data::Type, super::CheckError> {
if init_to.is_some() {
return Err(CheckError(
"can't init to statement type Function".to_string(),
));
return Err("can't init to statement type Function".to_string().into());
}
self.func_no_info.with_info_check(info.clone());
Ok(self.func_no_info.as_type())
@@ -33,7 +28,7 @@ impl MersStatement for Function {
fn has_scope(&self) -> bool {
true
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}

View File

@@ -1,15 +1,14 @@
use std::sync::Arc;
use crate::{
data::{self, Data, MersType, Type},
parsing::SourcePos,
};
use colored::Colorize;
use super::{CheckError, MersStatement};
use crate::data::{self, Data, MersType, Type};
use super::{CheckError, MersStatement, SourceRange};
#[derive(Debug)]
pub struct If {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub condition: Box<dyn MersStatement>,
pub on_true: Box<dyn MersStatement>,
pub on_false: Option<Box<dyn MersStatement>>,
@@ -22,16 +21,23 @@ impl MersStatement for If {
init_to: Option<&Type>,
) -> Result<data::Type, super::CheckError> {
if init_to.is_some() {
return Err(CheckError("can't init to statement type If".to_string()));
return Err("can't init to statement type If".to_string().into());
}
if !self
.condition
.check(info, None)?
.is_included_in(&data::bool::BoolT)
{
return Err(CheckError(format!(
"condition in an if-statement must return bool"
)));
let cond_return_type = self.condition.check(info, None)?;
if !cond_return_type.is_included_in(&data::bool::BoolT) {
return Err(CheckError::new()
.src(vec![
(self.pos_in_src, None),
(
self.condition.source_range(),
Some(colored::Color::BrightRed),
),
])
.msg(format!(
"The {} in an if-statement must return bool, not {}",
"condition".red(),
cond_return_type.to_string().bright_red(),
)));
}
let mut t = self.on_true.check(info, None)?;
if let Some(f) = &self.on_false {
@@ -59,7 +65,7 @@ impl MersStatement for If {
fn has_scope(&self) -> bool {
true
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}

View File

@@ -3,10 +3,13 @@ use std::{
sync::{Arc, Mutex},
};
use colored::Colorize;
use line_span::LineSpanExt;
use crate::{
data::{self, Data, Type},
info,
parsing::SourcePos,
parsing::{Source, SourcePos},
};
#[cfg(feature = "run")]
@@ -35,7 +38,6 @@ pub trait MersStatement: Debug + Send + Sync {
fn run_custom(&self, info: &mut Info) -> Data;
/// if true, local variables etc. will be contained inside their own scope.
fn has_scope(&self) -> bool;
fn pos_in_src(&self) -> &SourcePos;
fn check(&self, info: &mut CheckInfo, assign: Option<&Type>) -> Result<Type, CheckError> {
if self.has_scope() {
info.create_scope();
@@ -56,13 +58,159 @@ pub trait MersStatement: Debug + Send + Sync {
}
o
}
fn source_range(&self) -> SourceRange;
}
#[derive(Clone, Debug)]
pub struct CheckError(pub String);
impl Display for CheckError {
#[derive(Clone, Copy, Debug)]
pub struct SourceRange {
start: SourcePos,
end: SourcePos,
}
impl From<(SourcePos, SourcePos)> for SourceRange {
fn from(value: (SourcePos, SourcePos)) -> Self {
SourceRange {
start: value.0,
end: value.1,
}
}
}
#[derive(Clone)]
pub struct CheckError(Vec<CheckErrorComponent>);
#[derive(Clone)]
enum CheckErrorComponent {
Message(String),
Error(CheckError),
Source(Vec<(SourceRange, Option<colored::Color>)>),
}
#[derive(Clone)]
pub struct CheckErrorHRConfig {
indent_start: String,
indent_default: String,
indent_end: String,
}
pub struct CheckErrorDisplay<'a> {
e: &'a CheckError,
src: &'a Source,
}
impl Display for CheckErrorDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
self.e.human_readable(
f,
self.src,
&CheckErrorHRConfig {
indent_start: String::new(),
indent_default: String::new(),
indent_end: String::new(),
},
)
}
}
impl CheckError {
pub fn new() -> Self {
CheckError(vec![])
}
fn add(mut self, v: CheckErrorComponent) -> Self {
self.0.push(v);
self
}
pub(crate) fn msg(self, s: String) -> Self {
self.add(CheckErrorComponent::Message(s))
}
pub(crate) fn err(self, e: Self) -> Self {
self.add(CheckErrorComponent::Error(e))
}
pub(crate) fn src(self, s: Vec<(SourceRange, Option<colored::Color>)>) -> Self {
self.add(CheckErrorComponent::Source(s))
}
pub fn display<'a>(&'a self, src: &'a Source) -> CheckErrorDisplay<'a> {
CheckErrorDisplay { e: self, src }
}
// will, unless empty, end in a newline
fn human_readable(
&self,
f: &mut std::fmt::Formatter<'_>,
src: &Source,
cfg: &CheckErrorHRConfig,
) -> std::fmt::Result {
let len = self.0.len();
for (i, component) in self.0.iter().enumerate() {
macro_rules! indent {
() => {
if i + 1 == len {
&cfg.indent_end
} else if i == 0 {
&cfg.indent_start
} else {
&cfg.indent_default
}
};
}
match component {
CheckErrorComponent::Message(msg) => writeln!(f, "{}{msg}", indent!())?,
CheckErrorComponent::Error(err) => {
let mut cfg = cfg.clone();
cfg.indent_start.push_str("");
cfg.indent_default.push_str("");
cfg.indent_end.push_str("");
err.human_readable(f, src, &cfg)?;
}
CheckErrorComponent::Source(highlights) => {
let start = highlights.iter().map(|v| v.0.start.pos()).min();
let end = highlights.iter().map(|v| v.0.start.pos()).max();
if let (Some(start), Some(end)) = (start, end) {
writeln!(f, "{}Line(s) [?] ({start}..{end})", indent!())?;
let start = src.get_line_start(start);
let end = src.get_line_end(end);
let lines = src.src()[start..end].line_spans().collect::<Vec<_>>();
for line in lines {
let line_start = line.start();
let line_end = line.end();
let line = line.as_str();
writeln!(f, "{} {line}", indent!())?;
let mut right = 0;
for (pos, color) in highlights {
if let Some(color) = color {
let highlight_start = pos.start.pos() - start;
let highlight_end = pos.end.pos() - start;
if highlight_start < line_end && highlight_end > line_start {
let hl_start = highlight_start.saturating_sub(line_start);
if hl_start < right {
right = 0;
writeln!(f)?;
}
let hl_len = highlight_end
.saturating_sub(line_start)
.saturating_sub(hl_start);
let hl_space = hl_start - right;
let print_indent = right == 0;
right += hl_space + hl_len;
let hl_len = hl_len.min(highlight_end - highlight_start);
if print_indent && right != 0 {
write!(f, "{} ", indent!())?;
}
write!(
f,
"{}{}",
" ".repeat(hl_space),
"^".repeat(hl_len).color(*color)
)?;
}
}
}
if right != 0 {
writeln!(f)?;
}
}
}
}
}
}
Ok(())
}
}
impl From<String> for CheckError {
fn from(value: String) -> Self {
Self::new().msg(value)
}
}

View File

@@ -1,15 +1,14 @@
use std::sync::Arc;
use crate::{
data::{self, tuple::TupleT, Data, Type},
parsing::SourcePos,
};
use colored::Colorize;
use super::{CheckError, MersStatement};
use crate::data::{self, tuple::TupleT, Data, Type};
use super::{MersStatement, SourceRange};
#[derive(Debug)]
pub struct Tuple {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub elems: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Tuple {
@@ -29,14 +28,11 @@ impl MersStatement for Tuple {
vec[i].add(Arc::new(e.clone()));
}
} else {
return Err(CheckError(
format!("can't init to statement type Tuple with value type {t}, which is part of {init_to} - only tuples with the same length ({}) can be assigned to tuples", self.elems.len()),
));
return Err(
format!("can't init statement type Tuple with value type {t}, which is part of {init_to} - only tuples with the same length ({}) can be assigned to tuples", self.elems.len()).into());
}
} else {
return Err(CheckError(
format!("can't init to statement type Tuple with value type {t}, which is part of {init_to} - only tuples can be assigned to tuples"),
));
return Err(format!("can't init a {} with a value of type {}, which is part of {} - only tuples can be assigned to tuples", "tuple".bright_yellow(), t.to_string().bright_cyan(), init_to.to_string().bright_cyan()).into());
}
}
Some(vec)
@@ -68,7 +64,7 @@ impl MersStatement for Tuple {
fn has_scope(&self) -> bool {
false
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}

View File

@@ -1,13 +1,10 @@
use crate::{
data::{Data, Type},
parsing::SourcePos,
};
use crate::data::{Data, Type};
use super::{CheckError, MersStatement};
use super::{MersStatement, SourceRange};
#[derive(Debug)]
pub struct Value {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub val: Data,
}
@@ -21,14 +18,14 @@ impl MersStatement for Value {
init_to: Option<&Type>,
) -> Result<crate::data::Type, super::CheckError> {
if init_to.is_some() {
return Err(CheckError("can't init to statement type Value".to_string()));
return Err("can't init to statement type Value".to_string().into());
}
Ok(self.val.get().as_type())
}
fn run_custom(&self, _info: &mut super::Info) -> Data {
self.val.clone()
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}

View File

@@ -1,15 +1,12 @@
use std::sync::{Arc, Mutex};
use crate::{
data::{self, Data, Type},
parsing::SourcePos,
};
use crate::data::{self, Data, Type};
use super::MersStatement;
use super::{MersStatement, SourceRange};
#[derive(Debug)]
pub struct Variable {
pub pos_in_src: SourcePos,
pub pos_in_src: SourceRange,
pub is_init: bool,
pub is_ref: bool,
pub var: (usize, usize),
@@ -60,7 +57,7 @@ impl MersStatement for Variable {
.clone()
}
}
fn pos_in_src(&self) -> &SourcePos {
&self.pos_in_src
fn source_range(&self) -> SourceRange {
self.pos_in_src
}
}