From 688e28c1719e7d77a641bb87d2c7f99780bd30a1 Mon Sep 17 00:00:00 2001 From: Mark <> Date: Fri, 21 Jun 2024 15:50:41 +0200 Subject: [PATCH] nicer errors --- TODO.md | 14 + learn_mers/Cargo.toml | 12 + learn_mers/src/main.rs | 65 +++ learn_mers/src/tasks.rs | 202 +++++++ main.mers | 4 + mers/Cargo.toml | 6 +- mers/RuntimeErrors.md | 19 + mers/src/main.rs | 4 +- mers_lib/Cargo.toml | 2 +- mers_lib/src/data/mod.rs | 6 +- mers_lib/src/errors/mod.rs | 250 +++++++-- mers_lib/src/program/configs/with_math.rs | 613 +++++++++++----------- mers_lib/src/program/run/chain.rs | 33 +- 13 files changed, 833 insertions(+), 397 deletions(-) create mode 100644 TODO.md create mode 100644 learn_mers/Cargo.toml create mode 100644 learn_mers/src/main.rs create mode 100644 learn_mers/src/tasks.rs create mode 100644 main.mers create mode 100644 mers/RuntimeErrors.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..dbcc730 --- /dev/null +++ b/TODO.md @@ -0,0 +1,14 @@ +# Objects + +``` +o := { + c: 12 + x: 5.0 + y: 3.2 + coords: o -> o.c + println: o -> "aaa".println +} + +{ println: println } := o +o.println +``` diff --git a/learn_mers/Cargo.toml b/learn_mers/Cargo.toml new file mode 100644 index 0000000..864ac91 --- /dev/null +++ b/learn_mers/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "learn_mers" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.4.8", features = ["derive"] } +colored = "2.0.4" +mers_lib = { path = "../mers_lib" } +notify = "6.1.1" diff --git a/learn_mers/src/main.rs b/learn_mers/src/main.rs new file mode 100644 index 0000000..94cbb76 --- /dev/null +++ b/learn_mers/src/main.rs @@ -0,0 +1,65 @@ +mod tasks; + +use colored::Colorize; + +use std::{fs, path::PathBuf, time::Duration}; + +use clap::Parser; +use notify::Watcher; + +use mers_lib::prelude_compile::*; + +#[derive(Parser)] +pub(crate) struct Args { + file: PathBuf, + /// Skip this many tasks, i.e. start at task #, where 0 is `Hello, World`. + #[arg(long, default_value_t = 0)] + skip: usize, +} + +fn main() { + let args = Args::parse(); + let (s, r) = std::sync::mpsc::channel(); + let mut file_watcher = notify::recommended_watcher(move |e: Result| { + let e = e.unwrap(); + match e.kind { + notify::EventKind::Modify(k) => match k { + notify::event::ModifyKind::Data(_) => { + s.send(()).unwrap(); + } + _ => {} + }, + _ => {} + } + }) + .unwrap(); + fs::write(&args.file, "\"Hello, World!\".println\n").unwrap(); + file_watcher + .watch(&args.file, notify::RecursiveMode::NonRecursive) + .unwrap(); + for (task_i, (init_msg, cfg, mut task)) in + tasks::tasks(&args).into_iter().enumerate().skip(args.skip) + { + eprintln!("\n\n\n\n\n{}", format!(" -= {task_i} =-").bright_black()); + eprintln!("{}", init_msg); + loop { + // wait for file change + r.recv().unwrap(); + std::thread::sleep(Duration::from_millis(50)); + _ = r.try_iter().count(); + let code = fs::read_to_string(&args.file).unwrap(); + let mut src = Source::new(code); + let v = parse(&mut src).and_then(|v| { + let config = cfg(); + let (mut i1, i2, mut i3) = config.infos(); + v.compile(&mut i1, CompInfo::default()) + .and_then(|v| v.check(&mut i3, None).map(|t| (t, v, i1, i2, i3))) + }); + eprintln!("\n{}\n", "- - - - - - - - - -".bright_black()); + if task(v, src) { + break; + } + } + } + eprintln!("\nThere are no more tasks here. Feel free to suggest new tasks or changes to existing ones, and thanks for taking the time to check out mers :)"); +} diff --git a/learn_mers/src/tasks.rs b/learn_mers/src/tasks.rs new file mode 100644 index 0000000..d5f3613 --- /dev/null +++ b/learn_mers/src/tasks.rs @@ -0,0 +1,202 @@ +use std::sync::{Arc, Mutex}; + +use mers_lib::{ + data::{self, Data, MersType, Type}, + errors::CheckError, + prelude_compile::{RunMersStatement, Source}, + prelude_extend_config::Config, +}; + +use crate::Args; + +pub(crate) fn tasks( + args: &Args, +) -> Vec<( + String, + Box Config>, + Box< + dyn FnMut( + Result< + ( + Type, + Box, + mers_lib::program::parsed::Info, + mers_lib::program::run::Info, + mers_lib::program::run::CheckInfo, + ), + CheckError, + >, + Source, + ) -> bool, + >, +)> { + let file = args.file.to_string_lossy(); + vec![ + ( + format!( + " Hello, World! +--------------- +Hello! I've saved a file at {file}. +You can always use `mers run '{file}'` to actually run it, but for this introduction, just open the file in the editor of your choice. + +You should see mers' version of the \"Hello, World!\" program. Your task is to cause an error - to write code that doesn't work. + +When done, save the file and you should get feedback right here." + ), + Box::new(|| Config::new().with_stdio()), + Box::new(|v, s| match v { + Ok(..) => { + eprintln!("nope, that still compiles."); + false + } + Err(e) => { + eprintln!("{}", e.display(&s)); + eprintln!("Nice! Just for fun, the error you created can be seen above."); + true + } + }), + ), + ( + format!( + " Hello, Function! +------------------ +Functions in mers are created using `->`: +`arg -> arg` +This is a function that does nothing, it just returns its argument. +`n -> (n, 1).sum` +This function returns `n + 1`. + +Your task is to create any function." + ), + Box::new(|| Config::new().with_math()), + Box::new(|v, s| match v { + Ok((t, _, _, _, _)) => { + if t.types + .iter() + .all(|t| t.as_any().is::()) + { + eprintln!( + "Nice! Note that, even though you didn't see an error, your function may not have been correct. This will be explained later." + ); + true + } else { + eprintln!("This expression has type {t}, which isn't a function"); + false + } + } + Err(e) => { + eprintln!("{}", e.display(&s)); + false + } + }), + ), + ( + format!( + " Using Functions +----------------- +Mers functions require exactly one argument. +To use a function, use the `.` syntax: +`\"Hi\".println` +(`println` is a function, `\"Hi\"` is the argument) + +In this task, `func` will be a function. Your task is to use it. +`func` works with any argument, so this should be quite easy." + ), + Box::new(|| { + Config::new().with_stdio().add_var( + "func".to_string(), + Data::new(data::function::Function { + info: Arc::new(mers_lib::info::Info::neverused()), + info_check: Arc::new(Mutex::new(mers_lib::info::Info::neverused())), + out: Arc::new(|_, _| Ok(Type::empty())), + run: Arc::new(|_, _| Data::empty_tuple()), + }), + ) + }), + Box::new(|v, s| match v { + Ok((t, _, _, _, _)) => { + if t.types.is_empty() { + eprintln!( + "Nice! You successfully achieved nothing by using a function that does nothing." + ); + true + } else { + eprintln!("Hm, doesn't look like using `func` is the last thing your program does..."); + false + } + } + Err(e) => { + eprintln!("{}", e.display(&s)); + false + } + }), + ),( + format!( + " Hello, Variables! +------------------ +To create a new variable in mers, use `:=`: +`greeting := \"Hello\"` +You can use variables by just writing their name: +`greeting.println` + +In this task, I'll add the variable `im_done`. +To move on to the next task, return the value stored in it by writing `im_done` at the end of your file. +You can also store the value in another variable first: +`value := im_done` +`value`" + ), + Box::new(|| Config::new().add_var("im_done".to_string(), Data::one_tuple(Data::empty_tuple()))), + Box::new(|v, s| match v { + Ok((t, _, _, _, _)) => { + if t.one_tuple_content().is_some_and(|c| c.is_zero_tuple()) + { + true + } else { + eprintln!("You returned a value of type {t}, which isn't the type of `im_done`."); + false + } + } + Err(e) => { + eprintln!("{}", e.display(&s)); + false + } + }), + ), + ( + format!( + " Functions with multiple arguments +----------------------------------- +Mers functions only have one argument. To give a function multiple values, we have to use tuples: +`(\"Hello, \", \"World!\").concat` +(`concat` joins the two strings together, creating `\"Hello, World!\"`.) + +When writing your own functions, you can destructure these tuples: +`(a, b) -> (b, a).concat` +This creates a function which can only be called with a 2-long tuple. + +Your task is to assign this function to a variable `swapped`: +`swapped := (a, b) -> (b, a).concat` +Then, try to call the function with a wrong argument: +`\"hi\".swapped` +`().swapped` +`(\"a\", \"b\", \"c\").swapped` +To complete the task, use the function in a way that won't cause any errors." + ), + Box::new(|| Config::new().with_string()), + Box::new(|v, s| match v { + Ok((t, _, _, _, _)) => { + if t.is_included_in(&data::string::StringT) { + true + } else { + eprintln!("Whatever you did compiles, but doesn't seem to use the `swapped` function..."); + false + } + } + Err(e) => { + eprintln!("{}", e.display(&s)); + false + } + }), + ), + ] +} diff --git a/main.mers b/main.mers new file mode 100644 index 0000000..55e89b8 --- /dev/null +++ b/main.mers @@ -0,0 +1,4 @@ +add_one := x -> x.sum(1) +do_twice := func -> x -> x.func.func +add_two := [(Int -> Int, Float -> Float)] add_one.do_twice +"not a number".add_two diff --git a/mers/Cargo.toml b/mers/Cargo.toml index 3fd2263..faab7bd 100644 --- a/mers/Cargo.toml +++ b/mers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mers" -version = "0.8.11" +version = "0.8.12" edition = "2021" license = "MIT OR Apache-2.0" description = "dynamically typed but type-checked programming language" @@ -11,7 +11,7 @@ repository = "https://github.com/Dummi26/mers" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -mers_lib = "0.8.11" -# mers_lib = { path = "../mers_lib" } +# mers_lib = "0.8.12" +mers_lib = { path = "../mers_lib" } clap = { version = "4.3.19", features = ["derive"] } colored = "2.1.0" diff --git a/mers/RuntimeErrors.md b/mers/RuntimeErrors.md new file mode 100644 index 0000000..40a0b60 --- /dev/null +++ b/mers/RuntimeErrors.md @@ -0,0 +1,19 @@ +# Runtime Errors in Mers + +This is a list of (hopefully) +all runtime errors that can occur when running a mers program. + +## Explicit + +- Calling `panic` with a `String`-type argument will cause a runtime error, using the argument as the error message. +- Calling `exit` will exit with the given (`Int`) exit code. Usually, a nonzero exit code is considered a program failure. While this isn't really a runtime error, `exit` terminate the program just like an error would. + +## Integer-Integer math + +Some math functions fail under certain conditions when called with two integer arguments: + +- `x.div(0)` will fail because you cannot divide by zero. If at least one argument is a `Float`, this will return Infinity or Not A Number. +- `x.modulo(0)` will fail +- ... + +## ... (TODO) diff --git a/mers/src/main.rs b/mers/src/main.rs index 7af83a1..a0e4bda 100755 --- a/mers/src/main.rs +++ b/mers/src/main.rs @@ -123,7 +123,7 @@ fn main() { } Ok(_) => { if let Err(e) = compiled.run(&mut i2) { - eprintln!("Error while running: {}", e); + eprintln!("Error while running:\n{e}"); std::process::exit(1); } } @@ -149,7 +149,7 @@ fn main() { } Ok(compiled) => { if let Err(e) = compiled.run(&mut i2) { - eprintln!("Error while running: {}", e); + eprintln!("Error while running:\n{e}"); std::process::exit(1); } } diff --git a/mers_lib/Cargo.toml b/mers_lib/Cargo.toml index e13e0ea..fad9db3 100755 --- a/mers_lib/Cargo.toml +++ b/mers_lib/Cargo.toml @@ -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" diff --git a/mers_lib/src/data/mod.rs b/mers_lib/src/data/mod.rs index 45ca992..7917954 100755 --- a/mers_lib/src/data/mod.rs +++ b/mers_lib/src/data/mod.rs @@ -352,11 +352,7 @@ impl Type { pub fn iterable(&self) -> Option { 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) } diff --git a/mers_lib/src/errors/mod.rs b/mers_lib/src/errors/mod.rs index 9b98e8f..ffaa53c 100644 --- a/mers_lib/src/errors/mod.rs +++ b/mers_lib/src/errors/mod.rs @@ -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)>), } -#[derive(Clone)] pub struct CheckErrorHRConfig { - color_index: Rc, - indent_start: String, - indent_default: String, - indent_end: String, + color_index_ptr: Rc, + 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::>(); 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 for CheckError { fn from(value: String) -> Self { diff --git a/mers_lib/src/program/configs/with_math.rs b/mers_lib/src/program/configs/with_math.rs index 3a31eb4..cf3c3ea 100755 --- a/mers_lib/src/program/configs/with_math.rs +++ b/mers_lib/src/program/configs/with_math.rs @@ -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::().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::().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::() { - n.0.signum() - } else - if let Some(n) = a.get().as_any().downcast_ref::() { - 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::() { - 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::(), left.downcast_ref::(), - right.downcast_ref::(), right.downcast_ref::() - ) { - (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::() + .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::() { - 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::(), left.downcast_ref::(), - right.downcast_ref::(), right.downcast_ref::() - ) { - (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::() + .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::() { + n.0.signum() + } else if let Some(n) = + a.get().as_any().downcast_ref::() + { + 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::().is_some()) - { - ints = true; - } else if i.types.iter().all(|t| { - t.as_any().downcast_ref::().is_some() - || t.as_any().downcast_ref::().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::() { - sumi += i.0; - } else if let Some(i) = - val.get().as_any().downcast_ref::() - { - 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::().is_some()) - { - ints = true; - } else if i.types.iter().all(|t| { - t.as_any().downcast_ref::().is_some() - || t.as_any().downcast_ref::().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::() { - if first { - sumi = i.0; - } else { - sumi -= i.0; - } - } else if let Some(i) = - val.get().as_any().downcast_ref::() - { - 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::().is_some()) - { - ints = true; - } else if i.types.iter().all(|t| { - t.as_any().downcast_ref::().is_some() - || t.as_any().downcast_ref::().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::() { - prodi *= i.0; - } else if let Some(i) = - val.get().as_any().downcast_ref::() - { - 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, + func: impl Fn(Result, Result) -> Result + 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::() + .map(|v| Ok(v.0)) + .unwrap_or_else(|| { + Err(v + .downcast_ref::() + .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 { +fn two_num_tuple_to_num( + func_name: &'static str, + func_ii: impl Fn(isize, isize) -> Result + Send + Sync + 'static, + func_if: impl Fn(isize, f64) -> Result + Send + Sync + 'static, + func_fi: impl Fn(f64, isize) -> Result + Send + Sync + 'static, + func_ff: impl Fn(f64, f64) -> Result + 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 { let mut float = false; for t in &a.types { if let Some(t) = t.as_any().downcast_ref::() { @@ -403,6 +357,49 @@ fn two_tuple_to_num(a: &Type, func_name: &str) -> Result { 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 + Send + Sync), + func_if: &(impl Fn(isize, f64) -> Result + Send + Sync), + func_fi: &(impl Fn(f64, isize) -> Result + Send + Sync), + func_ff: &(impl Fn(f64, f64) -> Result + Send + Sync), +) -> Result { + if let Some(t) = a.get().as_any().downcast_ref::() { + 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::(), + left.downcast_ref::(), + right.downcast_ref::(), + right.downcast_ref::(), + ) { + (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, diff --git a/mers_lib/src/program/run/chain.rs b/mers_lib/src/program/run/chain.rs index f6b3c45..1fad44f 100755 --- a/mers_lib/src/program/run/chain.rs +++ b/mers_lib/src/program/run/chain.rs @@ -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 {