improved error messages

- some small bugs are now fixed
- include comments in error messages (if this causes issues, use --hide-comments)
- colors should make more sense now
- error-related things moved to mers_lib/src/errors/
This commit is contained in:
Mark 2023-11-16 14:50:09 +01:00
parent c39e784939
commit 12925fed67
33 changed files with 462 additions and 320 deletions

View File

@ -14,6 +14,9 @@ struct Args {
/// perform checks to avoid runtime crashes /// perform checks to avoid runtime crashes
#[arg(long, default_value_t = Check::Yes)] #[arg(long, default_value_t = Check::Yes)]
check: Check, check: Check,
/// in error messages, hide comments and only show actual code
#[arg(long)]
hide_comments: bool,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
enum Command { enum Command {
@ -81,7 +84,7 @@ fn main() {
let parsed = match parse(&mut source) { let parsed = match parse(&mut source) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
eprintln!("{}", e.display(&source)); eprintln!("{}", e.display(&source).show_comments(!args.hide_comments));
exit(20); exit(20);
} }
}; };
@ -90,7 +93,7 @@ fn main() {
let run = match parsed.compile(&mut info_parsed, Default::default()) { let run = match parsed.compile(&mut info_parsed, Default::default()) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
eprintln!("{}", e.display(&source)); eprintln!("{}", e.display(&source).show_comments(!args.hide_comments));
exit(24); exit(24);
} }
}; };
@ -104,7 +107,7 @@ fn main() {
let return_type = match run.check(&mut info_check, None) { let return_type = match run.check(&mut info_check, None) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
eprint!("{}", e.display(&source)); eprint!("{}", e.display(&source).show_comments(!args.hide_comments));
exit(28); exit(28);
} }
}; };

View File

@ -4,7 +4,10 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use crate::program::run::{CheckError, CheckInfo, Info}; use crate::{
errors::CheckError,
program::run::{CheckInfo, Info},
};
use super::{Data, MersData, MersType, Type}; use super::{Data, MersData, MersType, Type};

308
mers_lib/src/errors/mod.rs Normal file
View File

@ -0,0 +1,308 @@
use std::fmt::Display;
use colored::Colorize;
use line_span::LineSpans;
#[cfg(feature = "parse")]
use crate::parsing::Source;
#[derive(Clone, Copy, Debug)]
pub struct SourcePos(pub(crate) usize);
impl SourcePos {
pub fn pos(&self) -> usize {
self.0
}
}
#[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,
}
}
}
impl SourceRange {
pub fn start(&self) -> SourcePos {
self.start
}
pub fn end(&self) -> SourcePos {
self.end
}
}
#[derive(Clone, Debug)]
pub struct CheckError(Vec<CheckErrorComponent>);
#[allow(non_upper_case_globals)]
pub mod error_colors {
use colored::Color;
pub const UnknownVariable: Color = Color::Red;
pub const WhitespaceAfterHashtag: Color = Color::Red;
pub const HashUnknown: Color = Color::Red;
pub const HashIncludeCantLoadFile: Color = Color::Red;
pub const HashIncludeNotAString: Color = Color::Red;
pub const HashIncludeErrorInIncludedFile: Color = Color::Red;
pub const BackslashEscapeUnknown: Color = Color::Red;
pub const BackslashEscapeEOF: Color = Color::Red;
pub const StringEOF: Color = Color::Red;
pub const IfConditionNotBool: Color = Color::Red;
pub const ChainWithNonFunction: Color = Color::Yellow;
pub const Function: Color = Color::BrightMagenta;
pub const FunctionArgument: Color = Color::BrightBlue;
pub const InitFrom: Color = Color::BrightCyan;
pub const InitTo: Color = Color::Green;
pub const AssignFrom: Color = InitFrom;
pub const AssignTo: Color = InitTo;
pub const AssignTargetNonReference: Color = Color::BrightYellow;
}
#[derive(Clone, Debug)]
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,
/// if true, shows "original" source code, if false, shows source with comments removed (this is what the parser uses internally)
show_comments: bool,
}
#[cfg(feature = "parse")]
pub struct CheckErrorDisplay<'a> {
e: &'a CheckError,
src: Option<&'a Source>,
pub show_comments: bool,
}
#[cfg(feature = "parse")]
impl<'a> CheckErrorDisplay<'a> {
pub fn show_comments(mut self, show_comments: bool) -> Self {
self.show_comments = show_comments;
self
}
}
#[cfg(feature = "parse")]
impl Display for CheckErrorDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.e.human_readable(
f,
self.src,
&CheckErrorHRConfig {
indent_start: String::new(),
indent_default: String::new(),
indent_end: String::new(),
show_comments: self.show_comments,
},
)
}
}
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))
}
#[cfg(feature = "parse")]
pub fn display<'a>(&'a self, src: &'a Source) -> CheckErrorDisplay<'a> {
CheckErrorDisplay {
e: self,
src: Some(src),
show_comments: true,
}
}
#[cfg(feature = "parse")]
pub fn display_no_src<'a>(&'a self) -> CheckErrorDisplay<'a> {
CheckErrorDisplay {
e: self,
src: None,
show_comments: true,
}
}
/// will, unless empty, end in a newline
#[cfg(feature = "parse")]
fn human_readable(
&self,
f: &mut std::fmt::Formatter<'_>,
src: Option<&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) => {
if let Some(src) = src {
let start = highlights.iter().map(|v| v.0.start.pos()).min();
let end = highlights.iter().map(|v| v.0.end.pos()).max();
if let (Some(start_in_line), Some(end_in_line)) = (start, end) {
let start = src.get_line_start(start_in_line);
let end = src.get_line_end(end_in_line);
let (start_with_comments, end_with_comments) = (
src.pos_in_og(start_in_line, true),
src.pos_in_og(end_in_line, false),
);
let (mut start, mut end) = if cfg.show_comments {
(src.pos_in_og(start, true), src.pos_in_og(end, false))
} else {
(start, end)
};
let mut first_line_start = 0;
let first_line_nr = src
.src_og()
.line_spans()
.take_while(|l| {
if l.start() <= start_with_comments {
first_line_start = l.start();
true
} else {
false
}
})
.count();
if cfg.show_comments && first_line_start < start {
start = first_line_start;
}
let mut last_line_start = 0;
let last_line_nr = src
.src_og()
.line_spans()
.take_while(|l| {
if l.start() <= end_with_comments {
last_line_start = l.start();
if cfg.show_comments && l.end() > end {
end = l.end();
}
true
} else {
false
}
})
.count();
if first_line_nr == last_line_nr {
writeln!(
f,
"{}Line {first_line_nr} ({}..{})",
indent!(),
start_with_comments + 1 - first_line_start,
end_with_comments - last_line_start,
)?;
} else {
writeln!(
f,
"{}Lines {first_line_nr}-{last_line_nr} ({}..{})",
indent!(),
start_with_comments + 1 - first_line_start,
end_with_comments - last_line_start,
)?;
}
let lines = if cfg.show_comments {
src.src_og()[start..end].line_spans().collect::<Vec<_>>()
} else {
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, highlight_end) = if cfg.show_comments
{
(
src.pos_in_og(pos.start.pos(), true),
src.pos_in_og(pos.end.pos(), false),
)
} else {
(pos.start.pos(), pos.end.pos())
};
let highlight_start = highlight_start - start;
let highlight_end = highlight_end - start;
if highlight_start < line_end && highlight_end > line_start
{
// where the highlight starts in this line
let hl_start =
highlight_start.saturating_sub(line_start);
// highlight would be further left than cursor, so we need a new line
if hl_start < right {
right = 0;
writeln!(f)?;
}
// length of the highlight
let hl_len = highlight_end
.saturating_sub(line_start)
.saturating_sub(hl_start);
let hl_space = hl_start - right;
let print_indent = right == 0;
let hl_len = hl_len.min(line.len() - right);
right += hl_space + hl_len;
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,5 +1,7 @@
/// data and types in mers /// data and types in mers
pub mod data; pub mod data;
/// struct to represent errors the user may face
pub mod errors;
/// shared code handling scopes to guarantee that compiler and runtime scopes match /// shared code handling scopes to guarantee that compiler and runtime scopes match
pub mod info; pub mod info;
/// parser implementation. /// parser implementation.

View File

@ -1 +0,0 @@

View File

@ -1,8 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use crate::program::{self, parsed::block::Block, run::CheckError}; use crate::{
errors::{CheckError, SourcePos},
program::{self, parsed::block::Block},
};
pub mod errors;
pub mod statements; pub mod statements;
pub mod types; pub mod types;
@ -20,7 +22,7 @@ pub struct Source {
src_raw_len: usize, src_raw_len: usize,
src_og: String, src_og: String,
src: String, src: String,
/// (start, content) of each comment, including start/end (//, \n, /* and */) /// (start, content) of each comment, including start/end (//, /* and */), but NOT newline after //
comments: Vec<(usize, String)>, comments: Vec<(usize, String)>,
i: usize, i: usize,
sections: Vec<SectionMarker>, sections: Vec<SectionMarker>,
@ -36,13 +38,15 @@ impl Source {
if let Some((i, ch)) = chars.next() { if let Some((i, ch)) = chars.next() {
match in_comment { match in_comment {
Some(false) => { Some(false) => {
comment.1.push(ch);
if ch == '\n' { if ch == '\n' {
src.push('\n');
in_comment = None; in_comment = None;
comments.push(( comments.push((
comment.0, comment.0,
std::mem::replace(&mut comment.1, String::new()), std::mem::replace(&mut comment.1, String::new()),
)); ));
} else {
comment.1.push(ch);
} }
} }
Some(true) => { Some(true) => {
@ -223,6 +227,20 @@ impl Source {
} }
o o
} }
pub fn pos_in_og(&self, mut pos: usize, inclusive: bool) -> usize {
for (start, comment) in &self.comments {
if *start < pos || (inclusive && *start == pos) {
pos += comment.len();
} else {
break;
}
}
pos
}
pub fn src_og(&self) -> &String {
&self.src_og
}
} }
impl Drop for Source { impl Drop for Source {
@ -261,14 +279,3 @@ impl SectionMarker {
} }
} }
} }
#[derive(Clone, Copy, Debug)]
pub struct SourcePos(usize);
impl SourcePos {
pub fn pos(&self) -> usize {
self.0
}
fn diff(&self, rhs: &Self) -> usize {
rhs.0 - self.0
}
}

View File

@ -3,11 +3,8 @@ use std::fs;
use super::{Source, SourcePos}; use super::{Source, SourcePos};
use crate::{ use crate::{
data::Data, data::Data,
program::{ errors::{error_colors, CheckError},
self, program::{self, parsed::MersStatement},
parsed::MersStatement,
run::{error_colors, CheckError},
},
}; };
pub fn parse( pub fn parse(
@ -19,6 +16,7 @@ pub fn parse(
} else { } else {
return Ok(None); return Ok(None);
}; };
let mut pos_after_first = src.get_pos();
src.skip_whitespace(); src.skip_whitespace();
match src.peek_word() { match src.peek_word() {
":=" => { ":=" => {
@ -68,15 +66,15 @@ pub fn parse(
}); });
} }
_ => loop { _ => loop {
let pos_in_src = src.get_pos();
src.skip_whitespace(); src.skip_whitespace();
let dot_in_src = src.get_pos();
if let Some('.') = src.peek_char() { if let Some('.') = src.peek_char() {
src.next_char(); src.next_char();
let chained = match parse_no_chain(src) { let chained = match parse_no_chain(src) {
Ok(Some(v)) => v, Ok(Some(v)) => v,
Ok(None) => { Ok(None) => {
return Err(CheckError::new() return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos()).into(), None)]) .src(vec![((dot_in_src, src.get_pos()).into(), None)])
.msg(format!("EOF after `.`"))) .msg(format!("EOF after `.`")))
} }
Err(e) => return Err(e), Err(e) => return Err(e),
@ -91,11 +89,13 @@ pub fn parse(
}); });
} }
first = Box::new(program::parsed::chain::Chain { first = Box::new(program::parsed::chain::Chain {
pos_in_src: (pos_in_src, src.get_pos()).into(), pos_in_src: (first.source_range().start(), src.get_pos()).into(),
first, first,
chained, chained,
}); });
pos_after_first = src.get_pos();
} else { } else {
src.set_pos(pos_after_first);
break; break;
} }
}, },

View File

@ -5,7 +5,8 @@ use std::{
use crate::{ use crate::{
data::{self, Data, MersType, Type}, data::{self, Data, MersType, Type},
program::run::{CheckError, CheckInfo, Info}, errors::CheckError,
program::run::{CheckInfo, Info},
}; };
use super::Config; use super::Config;

View File

@ -9,10 +9,8 @@ use crate::{
function::{Function, FunctionT}, function::{Function, FunctionT},
Data, MersData, MersType, Type, Data, MersData, MersType, Type,
}, },
program::{ errors::CheckError,
self, program::{self, run::CheckInfo},
run::{CheckError, CheckInfo},
},
}; };
use super::Config; use super::Config;
@ -140,13 +138,6 @@ impl Config {
} }
} }
fn iter_out(
a: &Type,
name: &str,
func: impl Fn(&FunctionT) -> ItersT + Sync + Send,
) -> Result<Type, CheckError> {
iter_out_arg(a, name, func)
}
fn iter_out_arg<T: MersType>( fn iter_out_arg<T: MersType>(
a: &Type, a: &Type,
name: &str, name: &str,

View File

@ -135,7 +135,7 @@ impl Config {
Data::new(data::function::Function { Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()), info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())), info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, i| { out: Arc::new(|a, _i| {
if let Some(v) = a.iterable() { if let Some(v) = a.iterable() {
Ok(Type::new(ListT(v))) Ok(Type::new(ListT(v)))
} else { } else {

View File

@ -2,10 +2,8 @@ use std::sync::{Arc, Mutex};
use crate::{ use crate::{
data::{self, Data, MersType, Type}, data::{self, Data, MersType, Type},
program::{ errors::CheckError,
self, program::{self, run::CheckInfo},
run::{CheckError, CheckInfo},
},
}; };
use super::Config; use super::Config;

View File

@ -6,10 +6,8 @@ use std::{
use crate::{ use crate::{
data::{self, Data, MersData, MersType, Type}, data::{self, Data, MersData, MersType, Type},
program::{ errors::CheckError,
self, program::{self, run::CheckInfo},
run::{CheckError, CheckInfo},
},
}; };
use super::Config; use super::Config;

View File

@ -3,8 +3,11 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use colored::Colorize;
use crate::{ use crate::{
data::{self, Data, Type}, data::{self, Data, Type},
errors::error_colors,
program::{self, run::CheckInfo}, program::{self, run::CheckInfo},
}; };
@ -23,7 +26,17 @@ impl Config {
Data::new(data::function::Function { Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()), info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())), info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, i| Ok(Type::new(data::string::StringT))), out: Arc::new(|a, _i| {
if a.is_zero_tuple() {
Ok(Type::new(data::string::StringT))
} else {
Err(format!(
"expected (), got {}",
a.to_string().color(error_colors::FunctionArgument)
)
.into())
}
}),
run: Arc::new(|_a, _i| { run: Arc::new(|_a, _i| {
let mut line = String::new(); let mut line = String::new();
_ = std::io::stdin().read_line(&mut line); _ = std::io::stdin().read_line(&mut line);
@ -36,7 +49,7 @@ impl Config {
Data::new(data::function::Function { Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()), info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())), info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, i| Ok(Type::empty_tuple())), out: Arc::new(|_a, _i| Ok(Type::empty_tuple())),
run: Arc::new(|a, _i| { run: Arc::new(|a, _i| {
eprintln!("{:#?}", a.get()); eprintln!("{:#?}", a.get());
Data::empty_tuple() Data::empty_tuple()
@ -48,10 +61,10 @@ impl Config {
Data::new(data::function::Function { Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()), info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())), info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, i| Ok(Type::empty_tuple())), out: Arc::new(|_a, _i| Ok(Type::empty_tuple())),
run: Arc::new(|a, _i| { run: Arc::new(|a, _i| {
eprint!("{}", a.get()); eprint!("{}", a.get());
std::io::stderr().lock().flush(); _ = std::io::stderr().lock().flush();
Data::empty_tuple() Data::empty_tuple()
}), }),
}), }),
@ -61,7 +74,7 @@ impl Config {
Data::new(data::function::Function { Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()), info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())), info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, i| Ok(Type::empty_tuple())), out: Arc::new(|_a, _i| Ok(Type::empty_tuple())),
run: Arc::new(|a, _i| { run: Arc::new(|a, _i| {
eprintln!("{}", a.get()); eprintln!("{}", a.get());
Data::empty_tuple() Data::empty_tuple()
@ -73,10 +86,10 @@ impl Config {
Data::new(data::function::Function { Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()), info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())), info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, i| Ok(Type::empty_tuple())), out: Arc::new(|_a, _i| Ok(Type::empty_tuple())),
run: Arc::new(|a, _i| { run: Arc::new(|a, _i| {
print!("{}", a.get()); print!("{}", a.get());
std::io::stdout().lock().flush(); _ = std::io::stdout().lock().flush();
Data::empty_tuple() Data::empty_tuple()
}), }),
}), }),
@ -86,7 +99,7 @@ impl Config {
Data::new(data::function::Function { Data::new(data::function::Function {
info: Arc::new(program::run::Info::neverused()), info: Arc::new(program::run::Info::neverused()),
info_check: Arc::new(Mutex::new(CheckInfo::neverused())), info_check: Arc::new(Mutex::new(CheckInfo::neverused())),
out: Arc::new(|a, i| Ok(Type::empty_tuple())), out: Arc::new(|_a, _i| Ok(Type::empty_tuple())),
run: Arc::new(|a, _i| { run: Arc::new(|a, _i| {
println!("{}", a.get()); println!("{}", a.get());
Data::empty_tuple() Data::empty_tuple()

View File

@ -1,6 +1,6 @@
use crate::program::{ use crate::{
self, errors::{CheckError, SourceRange},
run::{CheckError, SourceRange}, program::{self},
}; };
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -1,9 +1,7 @@
use crate::{ use crate::{
errors::{CheckError, SourceRange},
info, info,
program::{ program::{self},
self,
run::{CheckError, SourceRange},
},
}; };
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -1,5 +1,7 @@
use crate::program::run::{CheckError, SourceRange}; use crate::{
use crate::{info, program}; errors::{CheckError, SourceRange},
info, program,
};
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -1,8 +1,8 @@
use crate::program::run::{CheckError, SourceRange};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::{ use crate::{
data, data,
errors::{CheckError, SourceRange},
program::{self, run::CheckInfo}, program::{self, run::CheckInfo},
}; };
@ -29,8 +29,8 @@ impl MersStatement for Function {
let arg_target = Arc::new(self.arg.compile(info, comp)?); let arg_target = Arc::new(self.arg.compile(info, comp)?);
comp.is_init = false; comp.is_init = false;
let run = Arc::new(self.run.compile(info, comp)?); let run = Arc::new(self.run.compile(info, comp)?);
let arg2 = Arc::clone(&arg_target); let arg2: Arc<Box<dyn crate::program::run::MersStatement>> = Arc::clone(&arg_target);
let run2 = Arc::clone(&run); let run2: Arc<Box<dyn crate::program::run::MersStatement>> = Arc::clone(&run);
Ok(Box::new(program::run::function::Function { Ok(Box::new(program::run::function::Function {
pos_in_src: self.pos_in_src, pos_in_src: self.pos_in_src,
func_no_info: data::function::Function { func_no_info: data::function::Function {

View File

@ -1,6 +1,6 @@
use crate::program::{ use crate::{
self, errors::{CheckError, SourceRange},
run::{CheckError, SourceRange}, program::{self},
}; };
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -4,11 +4,9 @@ use colored::Colorize;
use crate::{ use crate::{
data::{self, Data}, data::{self, Data},
errors::{error_colors, CheckError, SourceRange},
info::{self, Local}, info::{self, Local},
program::{ program::{self},
self,
run::{error_colors, CheckError, SourceRange},
},
}; };
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};
@ -27,7 +25,7 @@ impl MersStatement for IncludeMers {
info: &mut info::Info<super::Local>, info: &mut info::Info<super::Local>,
comp: CompInfo, comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, CheckError> { ) -> Result<Box<dyn program::run::MersStatement>, CheckError> {
let compiled = match self.include.compile(info, comp) { let compiled: Arc<Box<dyn crate::program::run::MersStatement>> = match self.include.compile(info, comp) {
Ok(v) => Arc::new(v), Ok(v) => Arc::new(v),
Err(e) => { Err(e) => {
return Err(CheckError::new() return Err(CheckError::new()

View File

@ -1,5 +1,7 @@
use crate::program::run::SourceRange; use crate::{
use crate::program::{self, run::CheckError}; errors::{CheckError, SourceRange},
program::{self},
};
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -1,8 +1,9 @@
use std::{collections::HashMap, fmt::Debug}; use std::{collections::HashMap, fmt::Debug};
use crate::info; use crate::{
errors::{CheckError, SourceRange},
use super::run::{CheckError, SourceRange}; info,
};
#[cfg(feature = "parse")] #[cfg(feature = "parse")]
pub mod assign_to; pub mod assign_to;

View File

@ -1,9 +1,7 @@
use crate::{ use crate::{
errors::{CheckError, SourceRange},
info, info,
program::{ program::{self},
self,
run::{CheckError, SourceRange},
},
}; };
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -1,5 +1,8 @@
use crate::program::run::{CheckError, SourceRange}; use crate::{
use crate::{data::Data, program}; data::Data,
errors::{CheckError, SourceRange},
program,
};
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -1,9 +1,7 @@
use crate::{ use crate::{
errors::{error_colors, CheckError, SourceRange},
info::Local, info::Local,
program::{ program::{self},
self,
run::{error_colors, CheckError, SourceRange},
},
}; };
use super::{CompInfo, MersStatement}; use super::{CompInfo, MersStatement};

View File

@ -1,8 +1,11 @@
use colored::Colorize; use colored::Colorize;
use crate::data::{self, Data, MersType, Type}; use crate::{
data::{self, Data, MersType, Type},
errors::{error_colors, CheckError, SourceRange},
};
use super::{error_colors, CheckError, CheckInfo, MersStatement, SourceRange}; use super::{CheckInfo, MersStatement};
#[derive(Debug)] #[derive(Debug)]
pub struct AssignTo { pub struct AssignTo {

View File

@ -1,6 +1,9 @@
use crate::data::Type; use crate::{
data::Type,
errors::{CheckError, SourceRange},
};
use super::{MersStatement, SourceRange}; use super::{CheckInfo, MersStatement};
#[derive(Debug)] #[derive(Debug)]
pub struct Block { pub struct Block {
@ -10,9 +13,9 @@ pub struct Block {
impl MersStatement for Block { impl MersStatement for Block {
fn check_custom( fn check_custom(
&self, &self,
info: &mut super::CheckInfo, info: &mut CheckInfo,
init_to: Option<&Type>, init_to: Option<&Type>,
) -> Result<crate::data::Type, super::CheckError> { ) -> Result<crate::data::Type, CheckError> {
if init_to.is_some() { if init_to.is_some() {
return Err("can't init to statement type Block".to_string().into()); return Err("can't init to statement type Block".to_string().into());
} }

View File

@ -2,9 +2,12 @@ use std::sync::Arc;
use colored::Colorize; use colored::Colorize;
use crate::data::{Data, Type}; use crate::{
data::{Data, Type},
errors::{error_colors, CheckError, SourceRange},
};
use super::{error_colors, CheckError, MersStatement, SourceRange}; use super::MersStatement;
#[derive(Debug)] #[derive(Debug)]
pub struct Chain { pub struct Chain {

View File

@ -1,8 +1,11 @@
use std::sync::Arc; use std::sync::Arc;
use crate::data::{self, Data, MersData, Type}; use crate::{
data::{self, Data, MersData, Type},
errors::{CheckError, SourceRange},
};
use super::{MersStatement, SourceRange}; use super::MersStatement;
#[derive(Debug)] #[derive(Debug)]
pub struct Function { pub struct Function {
@ -15,7 +18,7 @@ impl MersStatement for Function {
&self, &self,
info: &mut super::CheckInfo, info: &mut super::CheckInfo,
init_to: Option<&Type>, init_to: Option<&Type>,
) -> Result<data::Type, super::CheckError> { ) -> Result<data::Type, CheckError> {
if init_to.is_some() { if init_to.is_some() {
return Err("can't init to statement type Function".to_string().into()); return Err("can't init to statement type Function".to_string().into());
} }

View File

@ -2,9 +2,12 @@ use std::sync::Arc;
use colored::Colorize; use colored::Colorize;
use crate::data::{self, Data, MersType, Type}; use crate::{
data::{self, Data, MersType, Type},
errors::{error_colors, CheckError, SourceRange},
};
use super::{error_colors, CheckError, MersStatement, SourceRange}; use super::MersStatement;
#[derive(Debug)] #[derive(Debug)]
pub struct If { pub struct If {
@ -19,7 +22,7 @@ impl MersStatement for If {
&self, &self,
info: &mut super::CheckInfo, info: &mut super::CheckInfo,
init_to: Option<&Type>, init_to: Option<&Type>,
) -> Result<data::Type, super::CheckError> { ) -> Result<data::Type, CheckError> {
if init_to.is_some() { if init_to.is_some() {
return Err("can't init to statement type If".to_string().into()); return Err("can't init to statement type If".to_string().into());
} }

View File

@ -1,15 +1,12 @@
use std::{ use std::{
fmt::{Debug, Display}, fmt::Debug,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use colored::Colorize;
use line_span::LineSpanExt;
use crate::{ use crate::{
data::{self, Data, Type}, data::{self, Data, Type},
errors::{CheckError, SourceRange},
info, info,
parsing::{Source, SourcePos},
}; };
#[cfg(feature = "run")] #[cfg(feature = "run")]
@ -61,208 +58,6 @@ pub trait MersStatement: Debug + Send + Sync {
fn source_range(&self) -> SourceRange; fn source_range(&self) -> SourceRange;
} }
#[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,
}
}
}
impl SourceRange {
pub fn start(&self) -> SourcePos {
self.start
}
pub fn end(&self) -> SourcePos {
self.end
}
}
#[derive(Clone, Debug)]
pub struct CheckError(Vec<CheckErrorComponent>);
#[allow(non_upper_case_globals)]
pub mod error_colors {
use colored::Color;
pub const UnknownVariable: Color = Color::Red;
pub const WhitespaceAfterHashtag: Color = Color::Red;
pub const HashUnknown: Color = Color::Red;
pub const HashIncludeCantLoadFile: Color = Color::Red;
pub const HashIncludeNotAString: Color = Color::Red;
pub const HashIncludeErrorInIncludedFile: Color = Color::Red;
pub const BackslashEscapeUnknown: Color = Color::Red;
pub const BackslashEscapeEOF: Color = Color::Red;
pub const StringEOF: Color = Color::Red;
pub const IfConditionNotBool: Color = Color::Red;
pub const ChainWithNonFunction: Color = Color::Yellow;
pub const Function: Color = Color::BrightMagenta;
pub const FunctionArgument: Color = Color::BrightBlue;
pub const InitFrom: Color = Color::BrightCyan;
pub const InitTo: Color = Color::Green;
pub const AssignFrom: Color = InitFrom;
pub const AssignTo: Color = InitTo;
pub const AssignTargetNonReference: Color = Color::BrightYellow;
}
#[derive(Clone, Debug)]
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: Option<&'a Source>,
}
impl Display for CheckErrorDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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: Some(src),
}
}
pub fn display_no_src<'a>(&'a self) -> CheckErrorDisplay<'a> {
CheckErrorDisplay { e: self, src: None }
}
// will, unless empty, end in a newline
fn human_readable(
&self,
f: &mut std::fmt::Formatter<'_>,
src: Option<&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) => {
if let Some(src) = src {
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
{
// where the highlight starts in this line
let hl_start =
highlight_start.saturating_sub(line_start);
// highlight would be further left than cursor, so we need a new line
if hl_start < right {
right = 0;
writeln!(f)?;
}
// length of the highlight
let hl_len = highlight_end
.saturating_sub(line_start)
.saturating_sub(hl_start);
let hl_space = hl_start - right;
let print_indent = right == 0;
let hl_len = hl_len.min(line.len() - right);
right += hl_space + hl_len;
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)
}
}
pub type Info = info::Info<Local>; pub type Info = info::Info<Local>;
pub type CheckInfo = info::Info<CheckLocal>; pub type CheckInfo = info::Info<CheckLocal>;

View File

@ -2,9 +2,12 @@ use std::{collections::VecDeque, sync::Arc};
use colored::Colorize; use colored::Colorize;
use crate::data::{self, tuple::TupleT, Data, Type}; use crate::{
data::{self, tuple::TupleT, Data, Type},
errors::{error_colors, SourceRange},
};
use super::{error_colors, MersStatement, SourceRange}; use super::MersStatement;
#[derive(Debug)] #[derive(Debug)]
pub struct Tuple { pub struct Tuple {

View File

@ -1,6 +1,9 @@
use crate::data::{Data, Type}; use crate::{
data::{Data, Type},
errors::SourceRange,
};
use super::{MersStatement, SourceRange}; use super::{CheckInfo, MersStatement};
#[derive(Debug)] #[derive(Debug)]
pub struct Value { pub struct Value {
@ -14,7 +17,7 @@ impl MersStatement for Value {
} }
fn check_custom( fn check_custom(
&self, &self,
info: &mut super::CheckInfo, _info: &mut CheckInfo,
init_to: Option<&Type>, init_to: Option<&Type>,
) -> Result<crate::data::Type, super::CheckError> { ) -> Result<crate::data::Type, super::CheckError> {
if init_to.is_some() { if init_to.is_some() {

View File

@ -1,8 +1,11 @@
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crate::data::{self, Data, Type}; use crate::{
data::{self, Data, Type},
errors::SourceRange,
};
use super::{MersStatement, SourceRange}; use super::MersStatement;
#[derive(Debug)] #[derive(Debug)]
pub struct Variable { pub struct Variable {