add support for custom theming in mers errors

this also includes support for the NoTheme,
a theme which doesn't add any color to mers'
output.
If you compile mers with --no-default-features,
the `colored` dependency will disappear and
mers_lib will fall back to NoTheme.
This commit is contained in:
Mark
2024-06-26 01:02:19 +02:00
parent f055d2089f
commit a78367f27c
24 changed files with 517 additions and 336 deletions

View File

@@ -4,7 +4,7 @@ use line_span::{LineSpan, LineSpanExt};
use crate::{
data::Type,
errors::{error_colors, CheckError, SourcePos},
errors::{CheckError, EColor, SourcePos},
program::{
self,
parsed::{block::Block, CompInfo},
@@ -53,7 +53,7 @@ pub fn check_mut(
for (try_stmt, used) in info.global.unused_try_statements.lock().unwrap().iter() {
if used.iter().any(|v| v.is_some()) {
let err = err.get_or_insert_with(|| {
CheckError::new().msg(format!(
CheckError::from(format!(
"There are `.try` statements with unused functions!"
))
});
@@ -62,7 +62,7 @@ pub fn check_mut(
.enumerate()
.filter_map(|v| Some((v.0, v.1.clone()?)))
.collect::<Vec<_>>();
err.msg_mut(format!(
err.msg_mut_str(format!(
"Here, {}function{} {} {} unused:",
if unused.len() == 1 { "the " } else { "" },
if unused.len() == 1 { "" } else { "s" },
@@ -87,9 +87,9 @@ pub fn check_mut(
src.push((
src_range,
Some(if i % 2 == 0 {
error_colors::TryUnusedFunction1
EColor::TryUnusedFunction1
} else {
error_colors::TryUnusedFunction2
EColor::TryUnusedFunction2
}),
));
}

View File

@@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc};
use super::{Source, SourceFrom, SourcePos};
use crate::{
data::Data,
errors::{error_colors, CheckError},
errors::{CheckError, EColor},
program::{
self,
parsed::{as_type::AsType, MersStatement},
@@ -30,9 +30,9 @@ pub fn parse(
let name = name.trim().to_owned();
src.skip_whitespace();
if !matches!(src.next_char(), Some(']')) {
return Err(
CheckError::new().msg(format!("Expected ']' after type name in [[type_name]]"))
);
return Err(CheckError::from(format!(
"Expected ']' after type name in [[type_name]]"
)));
}
src.skip_whitespace();
if src.peek_word_allow_colon() == ":=" {
@@ -43,12 +43,12 @@ pub fn parse(
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `[[...] := ...]` type definition")))
.msg_str(format!("EOF after `[[...] := ...]` type definition")))
}
Err(e) => return Err(e),
};
if !matches!(src.next_char(), Some(']')) {
return Err(CheckError::new().msg(format!(
return Err(CheckError::new().msg_str(format!(
"Expected ']' after statement in [[type_name] := statement]"
)));
}
@@ -63,7 +63,7 @@ pub fn parse(
let as_type = super::types::parse_type(src, srca)?;
src.skip_whitespace();
if !matches!(src.next_char(), Some(']')) {
return Err(CheckError::new().msg(format!(
return Err(CheckError::new().msg_str(format!(
"Expected ']' after type definition in [[type_name] type_definition]"
)));
}
@@ -84,16 +84,16 @@ pub fn parse(
return Err(CheckError::new()
.src(vec![(
(pos_in_src, src.get_pos(), srca).into(),
Some(error_colors::TypeAnnotationNoClosingBracket),
Some(EColor::TypeAnnotationNoClosingBracket),
)])
.msg(format!("Missing closing bracket ']' after type annotation")));
.msg_str(format!("Missing closing bracket ']' after type annotation")));
}
let statement = match parse(src, srca) {
Ok(Some(v)) => v,
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `[...]` type annotation")))
.msg_str(format!("EOF after `[...]` type annotation")))
}
Err(e) => return Err(e),
};
@@ -120,7 +120,7 @@ pub fn parse(
let source = parse(src, srca)?.ok_or_else(|| {
CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `:=`"))
.msg_str(format!("EOF after `:=`"))
})?;
first = Box::new(program::parsed::init_to::InitTo {
pos_in_src: (first.source_range().start(), src.get_pos(), srca).into(),
@@ -137,7 +137,7 @@ pub fn parse(
(first.source_range().start(), src.get_pos(), srca).into(),
None,
)])
.msg(format!("EOF after `=`"))
.msg_str(format!("EOF after `=`"))
})?;
first = Box::new(program::parsed::assign_to::AssignTo {
pos_in_src: (pos_in_src, src.get_pos(), srca).into(),
@@ -153,7 +153,7 @@ pub fn parse(
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `->`")))
.msg_str(format!("EOF after `->`")))
}
Err(e) => return Err(e),
};
@@ -182,10 +182,10 @@ pub fn parse(
pos_after_first = src.get_pos();
} else {
return Err(CheckError::new()
.msg(format!("Expected `(` after `.try`"))
.msg_str(format!("Expected `(` after `.try`"))
.src(vec![(
(dot_in_src, src.get_pos(), srca).into(),
Some(error_colors::TryBadSyntax),
Some(EColor::TryBadSyntax),
)]));
}
} else {
@@ -194,7 +194,7 @@ pub fn parse(
Ok(None) => {
return Err(CheckError::new()
.src(vec![((dot_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `.`")))
.msg_str(format!("EOF after `.`")))
}
Err(e) => return Err(e),
};
@@ -265,16 +265,16 @@ pub fn parse_no_chain(
if src.peek_char().is_none() {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after #")));
.msg_str(format!("EOF after #")));
}
if src.peek_char().is_some_and(|ch| ch.is_whitespace()) {
src.skip_whitespace();
return Err(CheckError::new()
.src(vec![(
(pos_in_src, src.get_pos(), srca).into(),
Some(error_colors::WhitespaceAfterHashtag),
Some(EColor::WhitespaceAfterHashtag),
)])
.msg(format!("Whitespace after #")));
.msg_str(format!("Whitespace after #")));
}
match src.next_word() {
"include" => {
@@ -313,19 +313,19 @@ pub fn parse_no_chain(
((pos_in_src, end_in_src, srca).into(), None),
(
(string_in_src, src.get_pos(), srca).into(),
Some(error_colors::HashIncludeCantLoadFile),
Some(EColor::HashIncludeCantLoadFile),
),
])
.msg(format!("Can't load file '{file_path_str}': {e}")));
.msg_str(format!("Can't load file '{file_path_str}': {e}")));
}
}
} else {
return Err(CheckError::new()
.src(vec![
((pos_in_src, end_in_src, srca).into(), None),
((string_in_src, src.get_pos(), srca).into(), Some(error_colors::HashIncludeNotAString)),
((string_in_src, src.get_pos(), srca).into(), Some(EColor::HashIncludeNotAString)),
])
.msg(format!(
.msg_str(format!(
"#include must be followed by a string literal like \"file.mers\" (\" expected)."
)));
}
@@ -335,9 +335,9 @@ pub fn parse_no_chain(
return Err(CheckError::new()
.src(vec![(
(pos_in_src, src.get_pos(), srca).into(),
Some(error_colors::HashUnknown),
Some(EColor::HashUnknown),
)])
.msg(msg));
.msg_str(msg));
}
}
}
@@ -371,7 +371,7 @@ pub fn parse_no_chain(
(pos_in_src, src.get_pos(), srca).into(),
None,
)])
.msg(format!("EOF after `:` in object")))
.msg_str(format!("EOF after `:` in object")))
}
Err(e) => {
return Err(CheckError::new()
@@ -379,7 +379,9 @@ pub fn parse_no_chain(
(pos_in_src, src.get_pos(), srca).into(),
None,
)])
.msg(format!("Error in statement after `:` in object"))
.msg_str(format!(
"Error in statement after `:` in object"
))
.err(e))
}
},
@@ -430,7 +432,7 @@ pub fn parse_no_chain(
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF in `if`")))
.msg_str(format!("EOF in `if`")))
}
Err(e) => return Err(e),
};
@@ -439,7 +441,7 @@ pub fn parse_no_chain(
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `if <condition>`")))
.msg_str(format!("EOF after `if <condition>`")))
}
Err(e) => return Err(e),
};
@@ -453,7 +455,7 @@ pub fn parse_no_chain(
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `else`")))
.msg_str(format!("EOF after `else`")))
}
Err(e) => return Err(e),
})
@@ -476,7 +478,7 @@ pub fn parse_no_chain(
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("EOF after `loop`")))
.msg_str(format!("EOF after `loop`")))
}
Err(e) => return Err(e),
};
@@ -578,17 +580,17 @@ pub fn parse_string_custom_end(
return Err(CheckError::new()
.src(vec![(
(backslash_in_src, src.get_pos(), srca).into(),
Some(error_colors::BackslashEscapeUnknown),
Some(EColor::BackslashEscapeUnknown),
)])
.msg(format!("unknown backslash escape '\\{o}'")));
.msg_str(format!("unknown backslash escape '\\{o}'")));
}
None => {
return Err(CheckError::new()
.src(vec![(
(backslash_in_src, src.get_pos(), srca).into(),
Some(error_colors::BackslashEscapeEOF),
Some(EColor::BackslashEscapeEOF),
)])
.msg(format!("EOF in backslash escape")));
.msg_str(format!("EOF in backslash escape")));
}
});
} else if ch == closing_char {
@@ -600,9 +602,9 @@ pub fn parse_string_custom_end(
return Err(CheckError::new()
.src(vec![(
(opening, src.get_pos(), srca).into(),
Some(error_colors::StringEOF),
Some(EColor::StringEOF),
)])
.msg(format!(
.msg_str(format!(
"EOF in string literal{}",
if closing_char != '"' {
format!(

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::{
data::{self, Type},
errors::{error_colors, CheckError},
errors::{CheckError, EColor},
};
use super::Source;
@@ -35,7 +35,7 @@ pub fn parse_single_type(src: &mut Source, srca: &Arc<Source>) -> Result<ParsedT
} else {
format!("EOF")
};
return Err(CheckError::new().msg(format!(
return Err(CheckError::new().msg_str(format!(
"No closing ] in reference type with opening [! Found {nc} instead"
)));
}
@@ -75,10 +75,10 @@ pub fn parse_single_type(src: &mut Source, srca: &Arc<Source>) -> Result<ParsedT
((pos_in_src, src.get_pos(), srca).into(), None),
(
(pos1, src.get_pos(), srca).into(),
Some(error_colors::BadCharInFunctionType),
Some(EColor::BadCharInFunctionType),
),
]).msg(format!("Unexpected character in function type, expected arrow `->` but found `,`."))
.msg(format!("If you wanted this to be a tuple type instead, you may have used `Input -> Output` instead of `(Input -> Output)` for a function type somewhere.")));
]).msg_str(format!("Unexpected character in function type, expected arrow `->` but found `,`."))
.msg_str(format!("If you wanted this to be a tuple type instead, you may have used `Input -> Output` instead of `(Input -> Output)` for a function type somewhere.")));
}
}
Some('-') if src.peek_word() == "->" => {
@@ -92,8 +92,8 @@ pub fn parse_single_type(src: &mut Source, srca: &Arc<Source>) -> Result<ParsedT
Some(')') => break,
_ => return Err(CheckError::new().src(vec![
((pos_in_src, src.get_pos(), srca).into(), None),
((pos2, src.get_pos(), srca).into(), Some(error_colors::BadCharInFunctionType)),
]).msg(format!("Expected comma `,` after `In -> Out` part of function type")))
((pos2, src.get_pos(), srca).into(), Some(EColor::BadCharInFunctionType)),
]).msg_str(format!("Expected comma `,` after `In -> Out` part of function type")))
}
} else {
let pos1 = src.get_pos();
@@ -102,10 +102,10 @@ pub fn parse_single_type(src: &mut Source, srca: &Arc<Source>) -> Result<ParsedT
((pos_in_src, src.get_pos(), srca).into(), None),
(
(pos1, src.get_pos(), srca).into(),
Some(error_colors::BadCharInTupleType),
Some(EColor::BadCharInTupleType),
),
]).msg(format!("Unexpected character in tuple type, expected comma `,` but found arrow `->`."))
.msg(format!("If you wanted to write a function type, use `(Input -> Output)` instead of `Input -> Output`.")));
]).msg_str(format!("Unexpected character in tuple type, expected comma `,` but found arrow `->`."))
.msg_str(format!("If you wanted to write a function type, use `(Input -> Output)` instead of `Input -> Output`.")));
}
}
_ => {
@@ -116,10 +116,10 @@ pub fn parse_single_type(src: &mut Source, srca: &Arc<Source>) -> Result<ParsedT
((pos_in_src, src.get_pos(), srca).into(), None),
(
(ppos, src.get_pos(), srca).into(),
Some(error_colors::BadCharInTupleType),
Some(EColor::BadCharInTupleType),
),
])
.msg(format!(
.msg_str(format!(
"Unexpected character in tuple type, expected comma ',' or ')'."
)));
}
@@ -150,7 +150,7 @@ pub fn parse_single_type(src: &mut Source, srca: &Arc<Source>) -> Result<ParsedT
if src.next_char() != Some(':') {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos(), srca).into(), None)])
.msg(format!("Expected colon ':' in object type")));
.msg_str(format!("Expected colon ':' in object type")));
}
src.skip_whitespace();
inner.push((field, parse_type(src, srca)?));
@@ -236,11 +236,11 @@ pub fn type_from_parsed(
{
Some(Ok(t)) => as_type.add_all(&*t),
Some(Err(_)) => {
return Err(CheckError::new().msg(format!(
return Err(CheckError::new().msg_str(format!(
"Type: specified type without info, but type needs additional info"
)))
}
None => return Err(CheckError::new().msg(format!("Unknown type '{name}'"))),
None => return Err(CheckError::new().msg_str(format!("Unknown type '{name}'"))),
},
ParsedType::TypeWithInfo(name, additional_info) => match info
.scopes
@@ -248,12 +248,12 @@ pub fn type_from_parsed(
.find_map(|scope| scope.types.iter().find(|v| v.0 == name).map(|(_, v)| v))
{
Some(Ok(t)) => {
return Err(CheckError::new().msg(format!(
return Err(CheckError::new().msg_str(format!(
"Type: specified type with info, but type {t} doesn't need it"
)))
}
Some(Err(f)) => as_type.add_all(&*f(&additional_info, info)?),
None => return Err(CheckError::new().msg(format!("Unknown type '{name}'"))),
None => return Err(CheckError::new().msg_str(format!("Unknown type '{name}'"))),
},
}
}