nicer errors

This commit is contained in:
Mark
2024-06-21 15:50:41 +02:00
parent b11e4017ed
commit 688e28c171
13 changed files with 833 additions and 397 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "mers_lib"
version = "0.8.11"
version = "0.8.12"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "library to use the mers language in other projects"

View File

@@ -352,11 +352,7 @@ impl Type {
pub fn iterable(&self) -> Option<Type> {
let mut o = Self::empty();
for t in self.types.iter() {
if let Some(t) = t.iterable() {
o.add_all(&t);
} else {
return None;
}
o.add_all(&t.iterable()?);
}
Some(o)
}

View File

@@ -87,6 +87,9 @@ pub mod error_colors {
pub const TryNotAFunction: Color = Color::Red;
pub const TryUnusedFunction1: Color = Color::Red;
pub const TryUnusedFunction2: Color = Color::BrightRed;
pub const StacktraceDescend: Color = Color::Yellow;
pub const StacktraceDescendHashInclude: Color = Color::Red;
}
#[derive(Clone)]
pub enum CheckErrorComponent {
@@ -95,15 +98,134 @@ pub enum CheckErrorComponent {
ErrorWithDifferentSource(CheckError),
Source(Vec<(SourceRange, Option<colored::Color>)>),
}
#[derive(Clone)]
pub struct CheckErrorHRConfig {
color_index: Rc<AtomicUsize>,
indent_start: String,
indent_default: String,
indent_end: String,
color_index_ptr: Rc<AtomicUsize>,
color_index: usize,
is_inner: bool,
style: u8,
idt_start: String,
idt_default: String,
idt_end: String,
idt_single: String,
/// if true, shows "original" source code, if false, shows source with comments removed (this is what the parser uses internally)
show_comments: bool,
}
type BorderCharsSet = [[&'static str; 4]; 3];
pub struct IndentStr<'a>(&'a str, ColoredString);
impl Display for IndentStr<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.0, self.1)
}
}
impl CheckErrorHRConfig {
pub const BORDER_STYLE_THIN: u8 = 0;
pub const BORDER_STYLE_THICK: u8 = 1;
pub const BORDER_STYLE_DOUBLE: u8 = 2;
pub const STYLE_DEFAULT: u8 = Self::BORDER_STYLE_THIN;
pub const STYLE_DIFFSRC: u8 = Self::BORDER_STYLE_THICK;
const CHARS_HORIZONTAL: [&'static str; 3] = ["", "", ""];
const CHARS: [BorderCharsSet; 3] = [
[
["", "", "", ""],
["", "", "", ""],
["", "", "", ""],
],
[
["", "", "", ""],
["", "", "", ""],
["", "", "", ""],
],
[
["", "", "", ""],
["", "", "", ""],
["", "", "", ""],
],
];
fn color(&self, s: &str) -> ColoredString {
match self.color_index % 8 {
0 => s.bright_white(),
1 => s.bright_green(),
2 => s.bright_purple(),
3 => s.bright_cyan(),
4 => s.bright_red(),
5 => s.bright_yellow(),
6 => s.bright_magenta(),
_ => s.bright_blue(),
}
}
pub fn indent_start(&self, right: bool) -> IndentStr {
IndentStr(
&self.idt_start,
self.color(
Self::CHARS[self.style as usize][0][self.is_inner as usize * 2 + right as usize],
),
)
}
pub fn indent_default(&self, right: bool) -> IndentStr {
IndentStr(
&self.idt_default,
self.color(Self::CHARS[self.style as usize][1][right as usize]),
)
}
pub fn indent_end(&self, right: bool) -> IndentStr {
IndentStr(
&self.idt_end,
self.color(
Self::CHARS[self.style as usize][2][self.is_inner as usize * 2 + right as usize],
),
)
}
pub fn indent_single(&self, right: bool) -> IndentStr {
IndentStr(
&self.idt_single,
self.color(if self.is_inner {
if right {
Self::CHARS_HORIZONTAL[2] // left+right
} else {
Self::CHARS_HORIZONTAL[1] // left only
}
} else {
if right {
Self::CHARS_HORIZONTAL[0] // right only
} else {
Self::CHARS_HORIZONTAL[1] // left only (so that there is something)
}
}),
)
}
pub fn for_inner(&self, is_first: bool, is_last: bool, style: u8) -> Self {
let color_index = self
.color_index_ptr
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Self {
color_index_ptr: self.color_index_ptr.clone(),
color_index,
is_inner: true,
style,
idt_start: if is_first {
self.indent_start(true)
} else {
self.indent_default(true)
}
.to_string(),
idt_default: self.indent_default(false).to_string(),
idt_end: if is_last {
self.indent_end(true)
} else {
self.indent_default(true)
}
.to_string(),
idt_single: match (is_first, is_last) {
(false, false) => self.indent_default(true),
(false, true) => self.indent_end(true),
(true, false) => self.indent_start(true),
(true, true) => self.indent_single(true),
}
.to_string(),
show_comments: self.show_comments,
}
}
}
#[cfg(feature = "parse")]
pub struct CheckErrorDisplay<'a> {
e: &'a CheckError,
@@ -122,10 +244,14 @@ impl Display for CheckErrorDisplay<'_> {
self.e.human_readable(
f,
&CheckErrorHRConfig {
color_index: Rc::new(AtomicUsize::new(0)),
indent_start: String::new(),
indent_default: String::new(),
indent_end: String::new(),
color_index: 0,
color_index_ptr: Rc::new(AtomicUsize::new(1)),
is_inner: false,
style: CheckErrorHRConfig::STYLE_DEFAULT,
idt_start: String::new(),
idt_default: String::new(),
idt_end: String::new(),
idt_single: String::new(),
show_comments: self.show_comments,
},
)
@@ -182,18 +308,21 @@ impl CheckError {
f: &mut std::fmt::Formatter<'_>,
cfg: &CheckErrorHRConfig,
) -> std::fmt::Result {
const ADD_RIGHT_BITS: bool = false;
use crate::parsing::SourceFrom;
let len = self.0.len();
for (i, component) in self.0.iter().enumerate() {
let is_first = i == 0;
let is_last = i + 1 == len;
let i = (); // to see if we use `i` anywhere else
macro_rules! indent {
($s:expr, $e:expr) => {
if $e && i + 1 == len {
&cfg.indent_end
} else if $s && i == 0 {
&cfg.indent_start
} else {
&cfg.indent_default
($s:expr, $e:expr, $right:expr) => {
match ($s && is_first, $e && is_last) {
(false, false) => cfg.indent_default($right),
(false, true) => cfg.indent_end($right),
(true, false) => cfg.indent_start($right),
(true, true) => cfg.indent_single($right),
}
};
}
@@ -202,23 +331,17 @@ impl CheckError {
let lines = msg.lines().collect::<Vec<_>>();
let lc = lines.len();
for (i, line) in lines.into_iter().enumerate() {
writeln!(f, "{}{line}", indent!(i == 0, i + 1 == lc))?
let s = i == 0;
let e = i + 1 == lc;
writeln!(f, "{}{line}", indent!(s, e, s && ADD_RIGHT_BITS))?
}
}
CheckErrorComponent::Error(err) => {
let clr = Self::get_color(&cfg.color_index);
let mut cfg = cfg.clone();
cfg.indent_start.push_str(&clr("").to_string());
cfg.indent_default.push_str(&clr("").to_string());
cfg.indent_end.push_str(&clr("").to_string());
let cfg = cfg.for_inner(is_first, is_last, CheckErrorHRConfig::STYLE_DEFAULT);
err.human_readable(f, &cfg)?;
}
CheckErrorComponent::ErrorWithDifferentSource(err) => {
let clr = Self::get_color(&cfg.color_index);
let mut cfg = cfg.clone();
cfg.indent_start.push_str(&clr("").bold().to_string());
cfg.indent_default.push_str(&clr("").bold().to_string());
cfg.indent_end.push_str(&clr("").bold().to_string());
let cfg = cfg.for_inner(is_first, is_last, CheckErrorHRConfig::STYLE_DIFFSRC);
err.human_readable(f, &cfg)?;
}
CheckErrorComponent::Source(highlights) => {
@@ -276,10 +399,10 @@ impl CheckError {
if first_line_nr == last_line_nr {
writeln!(
f,
"{}",
"{}{}",
indent!(true, false, ADD_RIGHT_BITS),
format!(
"{}Line {first_line_nr} ({}..{}){}",
indent!(true, false),
"Line {first_line_nr} ({}..{}){}",
start_with_comments + 1 - first_line_start,
end_with_comments - last_line_start,
src_from,
@@ -289,10 +412,10 @@ impl CheckError {
} else {
writeln!(
f,
"{}",
"{}{}",
indent!(true, false, ADD_RIGHT_BITS),
format!(
"{}Lines {first_line_nr}-{last_line_nr} ({}..{}){}",
indent!(true, false),
"Lines {first_line_nr}-{last_line_nr} ({}..{}){}",
start_with_comments + 1 - first_line_start,
end_with_comments - last_line_start,
src_from,
@@ -313,16 +436,18 @@ impl CheckError {
let line = line.as_str();
let mut line_printed = false;
let mut right = 0;
for (pos, color) in highlights {
for (highlight_index, (highlight_pos, color)) in
highlights.iter().enumerate()
{
if let Some(color) = color {
let (highlight_start, highlight_end) = if cfg.show_comments
{
(
src.pos_in_og(pos.start.pos(), true),
src.pos_in_og(pos.end.pos(), false),
src.pos_in_og(highlight_pos.start.pos(), true),
src.pos_in_og(highlight_pos.end.pos(), false),
)
} else {
(pos.start.pos(), pos.end.pos())
(highlight_pos.start.pos(), highlight_pos.end.pos())
};
let highlight_start = highlight_start - start;
let highlight_end = highlight_end - start;
@@ -330,7 +455,11 @@ impl CheckError {
{
if !line_printed {
// this isn't the last line (important for indent)
writeln!(f, "{} {line}", indent!(false, false))?;
writeln!(
f,
"{} {line}",
indent!(false, false, false)
)?;
line_printed = true;
}
// where the highlight starts in this line
@@ -350,7 +479,35 @@ impl CheckError {
let hl_len = hl_len.min(line.len() - right);
right += hl_space + hl_len;
if print_indent && right != 0 {
write!(f, "{} ", indent!(false, false))?;
write!(
f,
"{} ",
indent!(
false,
// is end if last_line and
// all following highlights can be put on this line
last_line
&& highlights
.iter()
.skip(highlight_index + 1)
.try_fold(
// accumulator = end position of previous highlight
highlight_pos.end().pos(),
// success if all highlights start only after the previous highlight ended: a < hl.start
|a, hl| if a < hl
.0
.start()
.pos()
{
Some(hl.0.end().pos())
} else {
None
}
)
.is_some(),
false
)
)?;
}
write!(
f,
@@ -363,7 +520,7 @@ impl CheckError {
}
if !line_printed {
// may be last line (important for indent)
writeln!(f, "{} {line}", indent!(false, last_line))?;
writeln!(f, "{} {line}", indent!(false, last_line, false))?;
}
if right != 0 {
writeln!(f)?;
@@ -376,19 +533,6 @@ impl CheckError {
}
Ok(())
}
fn get_color(i: &AtomicUsize) -> impl Fn(&str) -> ColoredString {
let i = i.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
move |s| match i % 8 {
0 => s.bright_white(),
1 => s.bright_green(),
2 => s.bright_purple(),
3 => s.bright_cyan(),
4 => s.bright_red(),
5 => s.bright_yellow(),
6 => s.bright_magenta(),
_ => s.bright_blue(),
}
}
}
impl From<String> for CheckError {
fn from(value: String) -> Self {

View File

@@ -13,6 +13,7 @@ impl Config {
/// `minus: fn` returns the first number minus all the others
/// `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.
/// `pow: fn` returns a^b or a**b.
/// `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)
/// `lt: fn` returns true if the input keeps increasing, that is, for (a, b), a < b, for (a, b, c), a < b < c, and so on.
@@ -27,355 +28,308 @@ impl Config {
/// `ceil: fn` rounds the float [?] and returns an int
/// `floor: fn` rounds the float [?] and returns an int
pub fn with_math(self) -> Self {
self
.add_var("lt".to_string(), Data::new(ltgtoe_function("lt".to_string(), |l, r| match (l, r) {
self.add_var(
"lt".to_string(),
Data::new(ltgtoe_function("lt".to_string(), |l, r| match (l, r) {
(IntOrFloatOrNothing::Nothing, _) | (_, IntOrFloatOrNothing::Nothing) => true,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Int(r)) => l < r,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Float(r)) => (l as f64) < r,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Int(r)) => l < r as f64,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Float(r)) => l < r,
})))
.add_var("gt".to_string(), Data::new(ltgtoe_function("gt".to_string(), |l, r| match (l, r) {
})),
)
.add_var(
"gt".to_string(),
Data::new(ltgtoe_function("gt".to_string(), |l, r| match (l, r) {
(IntOrFloatOrNothing::Nothing, _) | (_, IntOrFloatOrNothing::Nothing) => true,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Int(r)) => l > r,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Float(r)) => (l as f64) > r,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Int(r)) => l > r as f64,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Float(r)) => l > r,
})))
.add_var("ltoe".to_string(), Data::new(ltgtoe_function("ltoe".to_string(), |l, r| match (l, r) {
})),
)
.add_var(
"ltoe".to_string(),
Data::new(ltgtoe_function("ltoe".to_string(), |l, r| match (l, r) {
(IntOrFloatOrNothing::Nothing, _) | (_, IntOrFloatOrNothing::Nothing) => true,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Int(r)) => l <= r,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Float(r)) => (l as f64) <= r,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Int(r)) => l <= r as f64,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Float(r)) => l <= r,
})))
.add_var("gtoe".to_string(), Data::new(ltgtoe_function("gtoe".to_string(), |l, r| match (l, r) {
})),
)
.add_var(
"gtoe".to_string(),
Data::new(ltgtoe_function("gtoe".to_string(), |l, r| match (l, r) {
(IntOrFloatOrNothing::Nothing, _) | (_, IntOrFloatOrNothing::Nothing) => true,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Int(r)) => l >= r,
(IntOrFloatOrNothing::Int(l), IntOrFloatOrNothing::Float(r)) => (l as f64) >= r,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Int(r)) => l >= r as f64,
(IntOrFloatOrNothing::Float(l), IntOrFloatOrNothing::Float(r)) => l >= r,
})))
.add_var("parse_float".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| {
if a.is_included_in(&Type::new(data::string::StringT)) {
Ok(Type::newm(vec![
Arc::new(data::tuple::TupleT(vec![
Type::new(data::float::FloatT),
])),
Arc::new(data::tuple::TupleT(vec![])),
]))
} else {
Err(format!("parse_float called on non-string type").into())
}
}),
run: Arc::new(|a, _i| {
Ok(if let Ok(n) = a.get().as_any().downcast_ref::<data::string::String>().unwrap().0.parse() {
Data::one_tuple(Data::new(data::float::Float(n)))
} else {
Data::empty_tuple()
})
}),
inner_statements: None,
})).add_var("parse_int".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| {
if a.is_included_in(&Type::new(data::string::StringT)) {
Ok(Type::newm(vec![
Arc::new(data::tuple::TupleT(vec![
Type::new(data::int::IntT),
])),
Arc::new(data::tuple::TupleT(vec![])),
]))
} else {
Err(format!("parse_float called on non-string type").into())
}
}),
run: Arc::new(|a, _i| {
Ok(if let Ok(n) = a.get().as_any().downcast_ref::<data::string::String>().unwrap().0.parse() {
Data::one_tuple(Data::new(data::int::Int(n)))
} else {
Data::empty_tuple()
})
}),
inner_statements: None,
})).add_var("signum".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| {
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(format!("signum called on non-number type").into())
}
}),
run: Arc::new(|a, _i| {
Ok(Data::new(data::int::Int(if let Some(n) = a.get().as_any().downcast_ref::<data::int::Int>() {
n.0.signum()
} else
if let Some(n) = a.get().as_any().downcast_ref::<data::float::Float>() {
if n.0 > 0.0 {
1
} else if n.0 < 0.0 {
-1
} else { 0
}
} else { return Err("called signum on non-number type".into()); })))
}),
inner_statements: None,
})) .add_var("div".to_string(), Data::new(data::function::Function {
})),
)
.add_var(
"parse_float".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) => Ok(Data::new(data::int::Int(l.checked_div(*r).ok_or_else(|| CheckError::from("attempted to divide by zero"))?))),
(Some(data::int::Int(l)), None, None, Some(data::float::Float(r))) => Ok(Data::new(data::float::Float(*l as f64 / r))),
(None, Some(data::float::Float(l)), Some(data::int::Int(r)), None) => Ok(Data::new(data::float::Float(l / *r as f64))),
(None, Some(data::float::Float(l)), None, Some(data::float::Float(r))) => Ok(Data::new(data::float::Float(l / r))),
_ => return Err("at least one of the arguments to div were neither an int nor a float".into()),
out: Arc::new(|a, _i| {
if a.is_included_in(&Type::new(data::string::StringT)) {
Ok(Type::newm(vec![
Arc::new(data::tuple::TupleT(vec![Type::new(data::float::FloatT)])),
Arc::new(data::tuple::TupleT(vec![])),
]))
} else {
Err(format!("parse_float called on non-string type").into())
}
} else { return Err("argument to div was not a tuple".into()); }),
}),
run: Arc::new(|a, _i| {
Ok(
if let Ok(n) = a
.get()
.as_any()
.downcast_ref::<data::string::String>()
.unwrap()
.0
.parse()
{
Data::one_tuple(Data::new(data::float::Float(n)))
} else {
Data::empty_tuple()
},
)
}),
inner_statements: None,
})).add_var("modulo".to_string(), Data::new(data::function::Function {
}),
)
.add_var(
"parse_int".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) => Ok(Data::new(data::int::Int(l % r))),
(Some(data::int::Int(l)), None, None, Some(data::float::Float(r))) => Ok(Data::new(data::float::Float(*l as f64 % r))),
(None, Some(data::float::Float(l)), Some(data::int::Int(r)), None) => Ok(Data::new(data::float::Float(l % *r as f64))),
(None, Some(data::float::Float(l)), None, Some(data::float::Float(r))) => Ok(Data::new(data::float::Float(l % r))),
_ => return Err("at least one of the arguments to modulo were neither an int nor a float".into()),
out: Arc::new(|a, _i| {
if a.is_included_in(&Type::new(data::string::StringT)) {
Ok(Type::newm(vec![
Arc::new(data::tuple::TupleT(vec![Type::new(data::int::IntT)])),
Arc::new(data::tuple::TupleT(vec![])),
]))
} else {
Err(format!("parse_float called on non-string type").into())
}
} else { return Err("argument to modulo was not a tuple".into()) }),
}),
run: Arc::new(|a, _i| {
Ok(
if let Ok(n) = a
.get()
.as_any()
.downcast_ref::<data::string::String>()
.unwrap()
.0
.parse()
{
Data::one_tuple(Data::new(data::int::Int(n)))
} else {
Data::empty_tuple()
},
)
}),
inner_statements: None,
}))
.add_var(
}),
)
.add_var(
"signum".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| {
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(format!("signum called on non-number type").into())
}
}),
run: Arc::new(|a, _i| {
Ok(Data::new(data::int::Int(
if let Some(n) = a.get().as_any().downcast_ref::<data::int::Int>() {
n.0.signum()
} else if let Some(n) =
a.get().as_any().downcast_ref::<data::float::Float>()
{
if n.0 > 0.0 {
1
} else if n.0 < 0.0 {
-1
} else {
0
}
} else {
return Err("called signum on non-number type".into());
},
)))
}),
inner_statements: None,
}),
)
.add_var(
"div".to_string(),
Data::new(two_num_tuple_to_num(
"div",
|l, r| {
l.checked_div(r)
.ok_or_else(|| CheckError::from("attempted to divide by zero"))
},
|l, r| Ok(l as f64 / r),
|l, r| Ok(l / r as f64),
|l, r| Ok(l / r),
)),
)
.add_var(
"pow".to_string(),
Data::new(two_num_tuple_to_num(
"pow",
|l, r| Ok(l.pow(r.try_into().unwrap_or(u32::MAX))),
|l, r| Ok((l as f64).powf(r)),
|l, r| {
Ok(if let Ok(r) = r.try_into() {
l.powi(r)
} else {
l.powf(r as f64)
})
},
|l, r| Ok(l.powf(r)),
)),
)
.add_var(
"modulo".to_string(),
Data::new(two_num_tuple_to_num(
"modulo",
|l, r| {
l.checked_rem(r).ok_or_else(|| {
CheckError::from(
"called modulo on two integers, and the second one was zero",
)
})
},
|l, r| Ok(l as f64 % r),
|l, r| Ok(l % r as f64),
|l, r| Ok(l % r),
)),
)
.add_var(
"sum".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 ints = false;
let mut floats = false;
for a in &a.types {
if let Some(i) = a.iterable() {
if i.types
.iter()
.all(|t| t.as_any().downcast_ref::<data::int::IntT>().is_some())
{
ints = true;
} else if i.types.iter().all(|t| {
t.as_any().downcast_ref::<data::int::IntT>().is_some()
|| t.as_any().downcast_ref::<data::float::FloatT>().is_some()
}) {
floats = true;
} else {
return Err(format!("cannot get sum of iterator over type {i} because it contains types that aren't int/float").into())
}
} else {
return Err(format!(
"cannot get sum of non-iterable type {a}"
).into());
}
}
Ok(match (ints, floats) {
(_, true) => Type::new(data::float::FloatT),
(true, false) => Type::new(data::int::IntT),
(false, false) => Type::empty(),
})
}),
run: Arc::new(|a, _i| {
if let Some(i) = a.get().iterable() {
let mut sumi = 0;
let mut sumf = 0.0;
let mut usef = false;
for val in i {
let val = val?;
let o = if let Some(i) = val.get().as_any().downcast_ref::<data::int::Int>() {
sumi += i.0;
} else if let Some(i) =
val.get().as_any().downcast_ref::<data::float::Float>()
{
sumf += i.0;
usef = true;
};
o
}
Ok(if usef {
Data::new(data::float::Float(sumi as f64 + sumf))
} else {
Data::new(data::int::Int(sumi))
})
} else {
return Err("sum called on non-tuple".into());
}
}),
inner_statements: None,
}),
Data::new(num_iter_to_num("sum", Ok(0), |a, v| match (a, v) {
(Ok(a), Ok(v)) => Ok(a + v),
(Ok(a), Err(v)) => Err(a as f64 + v),
(Err(a), Ok(v)) => Err(a + v as f64),
(Err(a), Err(v)) => Err(a + v),
})),
)
.add_var(
.add_var(
"subtract".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 ints = false;
let mut floats = false;
for a in &a.types {
if let Some(i) = a.iterable() {
if i.types
.iter()
.all(|t| t.as_any().downcast_ref::<data::int::IntT>().is_some())
{
ints = true;
} else if i.types.iter().all(|t| {
t.as_any().downcast_ref::<data::int::IntT>().is_some()
|| t.as_any().downcast_ref::<data::float::FloatT>().is_some()
}) {
floats = true;
} else {
return Err(format!("cannot subtract on iterator over type {i} because it contains types that aren't int/float").into())
}
} else {
return Err(format!(
"cannot subtract over non-iterable type {a}"
).into());
}
}
Ok(match (ints, floats) {
(_, true) => Type::new(data::float::FloatT),
(true, false) => Type::new(data::int::IntT),
(false, false) => Type::empty(),
})
}),
run: Arc::new(|a, _i| {
if let Some(i) = a.get().iterable() {
let mut first = true;
let mut sumi = 0;
let mut sumf = 0.0;
let mut usef = false;
for val in i {
let val = val?;
if let Some(i) = val.get().as_any().downcast_ref::<data::int::Int>() {
if first {
sumi = i.0;
} else {
sumi -= i.0;
}
} else if let Some(i) =
val.get().as_any().downcast_ref::<data::float::Float>()
{
if first {
sumf = i.0;
} else {
sumf -= i.0;
}
usef = true;
}
if first {
first = false;
}
}
Ok(if usef {
Data::new(data::float::Float(sumi as f64 + sumf))
} else {
Data::new(data::int::Int(sumi))
})
} else {
return Err("sum called on non-tuple".into());
}
}),
inner_statements: None,
}),
Data::new(two_num_tuple_to_num(
"subtract",
|l, r| Ok(l - r),
|l, r| Ok(l as f64 - r),
|l, r| Ok(l - r as f64),
|l, r| Ok(l - r),
)),
)
.add_var(
.add_var(
"product".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 ints = false;
let mut floats = false;
for a in &a.types {
if let Some(i) = a.iterable() {
if i.types
.iter()
.all(|t| t.as_any().downcast_ref::<data::int::IntT>().is_some())
{
ints = true;
} else if i.types.iter().all(|t| {
t.as_any().downcast_ref::<data::int::IntT>().is_some()
|| t.as_any().downcast_ref::<data::float::FloatT>().is_some()
}) {
floats = true;
} else {
return Err(format!("cannot get product of iterator over type {i} because it contains types that aren't int/float").into())
}
} else {
return Err(format!(
"cannot get product of non-iterable type {a}"
).into());
}
}
Ok(match (ints, floats) {
(_, true) => Type::new(data::float::FloatT),
(true, false) => Type::new(data::int::IntT),
(false, false) => Type::empty(),
})
}),
run: Arc::new(|a, _i| {
if let Some(i) = a.get().iterable() {
let mut prodi = 1;
let mut prodf = 1.0;
let mut usef = false;
for val in i {
let val = val?;
let o = if let Some(i) = val.get().as_any().downcast_ref::<data::int::Int>() {
prodi *= i.0;
} else if let Some(i) =
val.get().as_any().downcast_ref::<data::float::Float>()
{
prodf *= i.0;
usef = true;
};
o
}
Ok(if usef {
Data::new(data::float::Float(prodi as f64 * prodf))
} else {
Data::new(data::int::Int(prodi))
})
} else {
return Err("product called on non-tuple".into());
}
}),
inner_statements: None,
}),
Data::new(num_iter_to_num("sum", Ok(1), |a, v| match (a, v) {
(Ok(a), Ok(v)) => Ok(a * v),
(Ok(a), Err(v)) => Err(a as f64 * v),
(Err(a), Ok(v)) => Err(a * v as f64),
(Err(a), Err(v)) => Err(a * v),
})),
)
}
}
fn num_iter_to_num(
func_name: &'static str,
init: Result<isize, f64>,
func: impl Fn(Result<isize, f64>, Result<isize, f64>) -> Result<isize, f64> + Send + Sync + 'static,
) -> data::function::Function {
data::function::Function {
info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(move |a, _i| {
if let Some(a) = a.iterable() {
let int_type = Type::new(data::int::IntT);
if a.is_included_in(&int_type) {
Ok(int_type)
} else {
let float_type = Type::new(data::float::FloatT);
if a.is_included_in(&float_type) {
Ok(float_type)
} else {
let int_float_type = Type::newm(vec![
Arc::new(data::int::IntT),
Arc::new(data::float::FloatT),
]);
if a.is_included_in(&int_float_type) {
Ok(int_float_type)
} else {
Err(format!("argument passed to {func_name} must be an iterator over values of type Int/String, but was an iterator over values of type {a}.").into())
}
}
}
} else {
Err(format!("argument passed to {func_name} must be an iterator").into())
}
}),
run: Arc::new(move |a, _i| {
let mut out = init;
for v in a.get().iterable().unwrap() {
let v = v?;
let v = v.get();
let v = v.as_any();
let v = v
.downcast_ref::<data::int::Int>()
.map(|v| Ok(v.0))
.unwrap_or_else(|| {
Err(v
.downcast_ref::<data::float::Float>()
.expect("value used in num-iterator function was not a number")
.0)
});
out = func(out, v);
}
Ok(match out {
Ok(v) => Data::new(data::int::Int(v)),
Err(v) => Data::new(data::float::Float(v)),
})
}),
inner_statements: None,
}
}
/// (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> {
fn two_num_tuple_to_num(
func_name: &'static str,
func_ii: impl Fn(isize, isize) -> Result<isize, CheckError> + Send + Sync + 'static,
func_if: impl Fn(isize, f64) -> Result<f64, CheckError> + Send + Sync + 'static,
func_fi: impl Fn(f64, isize) -> Result<f64, CheckError> + Send + Sync + 'static,
func_ff: impl Fn(f64, f64) -> Result<f64, CheckError> + Send + Sync + 'static,
) -> data::function::Function {
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_impl_check(a, func_name)),
run: Arc::new(move |a, _i| {
two_tuple_to_num_impl_run(a, func_name, &func_ii, &func_if, &func_fi, &func_ff)
}),
inner_statements: None,
}
}
fn two_tuple_to_num_impl_check(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>() {
@@ -403,6 +357,49 @@ fn two_tuple_to_num(a: &Type, func_name: &str) -> Result<Type, CheckError> {
Type::new(data::int::IntT)
})
}
fn two_tuple_to_num_impl_run(
a: Data,
func_name: &'static str,
func_ii: &(impl Fn(isize, isize) -> Result<isize, CheckError> + Send + Sync),
func_if: &(impl Fn(isize, f64) -> Result<f64, CheckError> + Send + Sync),
func_fi: &(impl Fn(f64, isize) -> Result<f64, CheckError> + Send + Sync),
func_ff: &(impl Fn(f64, f64) -> Result<f64, CheckError> + Send + Sync),
) -> Result<Data, CheckError> {
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());
Ok(
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(func_ii(*l, *r)?))
}
(Some(data::int::Int(l)), None, None, Some(data::float::Float(r))) => {
Data::new(data::float::Float(func_if(*l, *r)?))
}
(None, Some(data::float::Float(l)), Some(data::int::Int(r)), None) => {
Data::new(data::float::Float(func_fi(*l, *r)?))
}
(None, Some(data::float::Float(l)), None, Some(data::float::Float(r))) => {
Data::new(data::float::Float(func_ff(*l, *r)?))
}
_ => {
return Err(format!(
"at least one of the arguments to {func_name} were neither an int nor a float"
)
.into())
}
},
)
} else {
return Err(format!("argument to {func_name} was not a tuple").into());
}
}
fn ltgtoe_function(
func_name: String,

View File

@@ -96,32 +96,15 @@ impl MersStatement for Chain {
match func.run(f) {
Ok(v) => Ok(v),
Err(e) => Err(if let Some(_) = &self.as_part_of_include {
CheckError::new()
.src(vec![(
self.pos_in_src.clone(),
Some(error_colors::HashIncludeErrorInIncludedFile),
)])
.msg(
"Error in #include:"
.color(error_colors::HashIncludeErrorInIncludedFile)
.to_string(),
)
.err_with_diff_src(e)
CheckError::new().err_with_diff_src(e).src(vec![(
self.pos_in_src.clone(),
Some(error_colors::StacktraceDescendHashInclude),
)])
} else {
CheckError::new()
.src(vec![
(self.pos_in_src.clone(), None),
(
self.first.source_range(),
Some(error_colors::FunctionArgument),
),
(self.chained.source_range(), Some(error_colors::Function)),
])
.msg(format!(
"Error in {}:",
"this function".color(error_colors::Function)
))
.err(e)
CheckError::new().err(e).src(vec![
(self.pos_in_src.clone(), None),
(self.source_range(), Some(error_colors::StacktraceDescend)),
])
}),
}
} else {