Add formatting of the printed lines to errors
This commit is contained in:
parent
9d1b18f083
commit
df4febf1ef
@ -5,9 +5,9 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
ast,
|
||||
lexer::{self, FullToken},
|
||||
lexer::{self, FullToken, Position},
|
||||
mir::{self, pass, Metadata, SourceModuleId},
|
||||
token_stream,
|
||||
token_stream::{self, TokenRange},
|
||||
};
|
||||
|
||||
impl<T: std::error::Error + std::fmt::Display> pass::Error<T> {
|
||||
@ -16,17 +16,26 @@ impl<T: std::error::Error + std::fmt::Display> pass::Error<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn label(text: &str) -> &str {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
return text;
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
""
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
#[error("Lexing: {}", .0.kind)]
|
||||
#[error("{}{}", label("(Lexing) "), .0.kind)]
|
||||
LexerError(#[from] mir::pass::Error<lexer::Error>),
|
||||
#[error("Parsing: {}", .0.kind)]
|
||||
#[error("{}{}", label("(Parsing) "), .0.kind)]
|
||||
ParserError(#[from] mir::pass::Error<token_stream::Error>),
|
||||
#[error("Typechecking: {}", .0.kind)]
|
||||
#[error("{}{}", label("(TypeCheck) "), .0.kind)]
|
||||
TypeCheckError(#[source] mir::pass::Error<mir::typecheck::ErrorKind>),
|
||||
#[error("Type Inference: {}", .0.kind)]
|
||||
#[error("{}{}", label("(TypeInference) "), .0.kind)]
|
||||
TypeInferenceError(#[source] mir::pass::Error<mir::typecheck::ErrorKind>),
|
||||
#[error("Linking: {}", .0.kind)]
|
||||
#[error("{}{}", label("(Linker) "), .0.kind)]
|
||||
LinkerError(#[from] mir::pass::Error<mir::linker::ErrorKind>),
|
||||
}
|
||||
|
||||
@ -102,70 +111,12 @@ impl ModuleMap {
|
||||
}
|
||||
}
|
||||
|
||||
// impl TryFrom<&mir::Context> for ModuleMap {
|
||||
// type Error = ();
|
||||
|
||||
// fn try_from(value: &mir::Context) -> Result<Self, Self::Error> {
|
||||
// let mut map = HashMap::new();
|
||||
// for module in &value.modules {
|
||||
// if let Some(_) = map.insert(
|
||||
// module.module_id,
|
||||
// ErrModule {
|
||||
// name: module.name.clone(),
|
||||
// tokens: Some(module.clone()),
|
||||
// },
|
||||
// ) {
|
||||
// return Err(());
|
||||
// }
|
||||
// }
|
||||
// let module_counter = value.modules.iter().map(|m| m.module_id).max().ok_or(())?;
|
||||
// Ok(ModuleMap {
|
||||
// module_map: map,
|
||||
// module_counter,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ReidError {
|
||||
map: ModuleMap,
|
||||
errors: Vec<ErrorKind>,
|
||||
}
|
||||
|
||||
impl std::error::Error for ReidError {}
|
||||
|
||||
impl std::fmt::Display for ReidError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut sorted_errors = self.errors.clone();
|
||||
sorted_errors.sort_by(|a, b| a.cmp(&b));
|
||||
sorted_errors.dedup();
|
||||
|
||||
let mut curr_module = None;
|
||||
for error in sorted_errors {
|
||||
let meta = error.get_meta();
|
||||
if curr_module != Some(meta.source_module_id) {
|
||||
curr_module = Some(meta.source_module_id);
|
||||
writeln!(
|
||||
f,
|
||||
"Errors in module {}:",
|
||||
color_err(format!(
|
||||
"{}",
|
||||
self.map
|
||||
.module_map
|
||||
.get(&meta.source_module_id)
|
||||
.unwrap()
|
||||
.name
|
||||
))?
|
||||
)?;
|
||||
}
|
||||
write!(f, " {}: ", color_err("Error")?)?;
|
||||
writeln!(f, "{}", error)?;
|
||||
writeln!(f, " {}: {}", color_warn("At")?, meta)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ReidError {
|
||||
pub fn from_lexer<U>(
|
||||
result: Result<U, lexer::Error>,
|
||||
@ -214,6 +165,176 @@ impl ReidError {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ReidError {}
|
||||
|
||||
impl std::fmt::Display for ReidError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut sorted_errors = self.errors.clone();
|
||||
sorted_errors.sort_by(|a, b| a.cmp(&b));
|
||||
sorted_errors.dedup();
|
||||
|
||||
let mut curr_module = None;
|
||||
for error in sorted_errors {
|
||||
let meta = error.get_meta();
|
||||
let module = self.map.get_module(&meta.source_module_id).unwrap();
|
||||
let (position_fmt, line_fmt) = if let Some(tokens) = &module.tokens {
|
||||
let range_tokens = meta.range.into_tokens(&tokens);
|
||||
let position = get_position(&range_tokens).unwrap();
|
||||
let full_lines = get_full_lines(&tokens, position);
|
||||
(
|
||||
fmt_positions(get_position(&full_lines).unwrap()),
|
||||
Some(fmt_tokens(&full_lines, &range_tokens)),
|
||||
)
|
||||
} else if let Some(position) = meta.position {
|
||||
(fmt_positions((position, position)), None)
|
||||
} else {
|
||||
("unknown".to_owned(), None)
|
||||
};
|
||||
|
||||
if curr_module != Some(meta.source_module_id) {
|
||||
curr_module = Some(meta.source_module_id);
|
||||
writeln!(
|
||||
f,
|
||||
"Errors in module {}:",
|
||||
color_err(format!(
|
||||
"{}",
|
||||
self.map
|
||||
.module_map
|
||||
.get(&meta.source_module_id)
|
||||
.unwrap()
|
||||
.name
|
||||
))?
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
write!(f, " Error: ")?;
|
||||
writeln!(f, "{}", color_err(format!("{}", error))?)?;
|
||||
writeln!(f, "{:>20}{}", color_warn("At: ")?, position_fmt)?;
|
||||
if let Some(line_fmt) = line_fmt {
|
||||
writeln!(f, "{:>20}{}", color_warn("")?, line_fmt)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenRange {
|
||||
pub fn into_tokens<'v>(&self, tokens: &'v Vec<FullToken>) -> Vec<&'v FullToken> {
|
||||
tokens
|
||||
.iter()
|
||||
.skip(self.start)
|
||||
.by_ref()
|
||||
.take(self.end - self.start)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_position(tokens: &Vec<&FullToken>) -> Option<(Position, Position)> {
|
||||
if let Some(first) = tokens.first() {
|
||||
let last = tokens.last().unwrap();
|
||||
Some((first.position, last.position.add(last.token.len() as u32)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_full_lines<'v>(
|
||||
tokens: &'v Vec<FullToken>,
|
||||
(start, end): (Position, Position),
|
||||
) -> Vec<&'v FullToken> {
|
||||
let (first_token_pos, _) = tokens
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, token)| token.position.1 == start.1)
|
||||
.unwrap();
|
||||
tokens
|
||||
.iter()
|
||||
.skip(first_token_pos)
|
||||
.by_ref()
|
||||
.take_while(|token| token.position.1 <= end.1)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn fmt_tokens(tokens: &Vec<&FullToken>, highlighted: &Vec<&FullToken>) -> String {
|
||||
let mut text = String::new();
|
||||
let mut last_likes_space = false;
|
||||
for (i, token) in tokens.iter().enumerate() {
|
||||
if token.token.needs_space() || (token.token.likes_space() && last_likes_space) {
|
||||
text += " ";
|
||||
}
|
||||
last_likes_space = token.token.likes_space();
|
||||
|
||||
let mut token_fmt = format!("{}", token.token.to_string());
|
||||
if highlighted.contains(token) {
|
||||
token_fmt = color_underline(token_fmt).unwrap();
|
||||
}
|
||||
text += &token_fmt;
|
||||
if token.token.is_newline() && i > (tokens.len() - 1) {
|
||||
text += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
text
|
||||
}
|
||||
|
||||
fn fmt_positions((start, end): (Position, Position)) -> String {
|
||||
if start == end {
|
||||
format!("ln {}, col {}", start.1, start.0)
|
||||
} else if start.1 == end.1 {
|
||||
format!("ln {}, col {}-{}", start.1, start.0, end.0)
|
||||
} else {
|
||||
format!("{}:{} - {}:{}", start.1, start.0, end.1, end.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl lexer::Token {
|
||||
fn likes_space(&self) -> bool {
|
||||
match self {
|
||||
lexer::Token::Identifier(_) => true,
|
||||
lexer::Token::DecimalValue(_) => true,
|
||||
lexer::Token::StringLit(_) => true,
|
||||
lexer::Token::LetKeyword => true,
|
||||
lexer::Token::MutKeyword => true,
|
||||
lexer::Token::ImportKeyword => true,
|
||||
lexer::Token::ReturnKeyword => true,
|
||||
lexer::Token::FnKeyword => true,
|
||||
lexer::Token::PubKeyword => true,
|
||||
lexer::Token::Arrow => true,
|
||||
lexer::Token::If => true,
|
||||
lexer::Token::Else => true,
|
||||
lexer::Token::True => true,
|
||||
lexer::Token::False => true,
|
||||
lexer::Token::Extern => true,
|
||||
lexer::Token::Struct => true,
|
||||
lexer::Token::Equals => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn needs_space(&self) -> bool {
|
||||
match self {
|
||||
lexer::Token::LetKeyword => true,
|
||||
lexer::Token::MutKeyword => true,
|
||||
lexer::Token::ImportKeyword => true,
|
||||
lexer::Token::ReturnKeyword => true,
|
||||
lexer::Token::FnKeyword => true,
|
||||
lexer::Token::PubKeyword => true,
|
||||
lexer::Token::Arrow => true,
|
||||
lexer::Token::Equals => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_newline(&self) -> bool {
|
||||
match self {
|
||||
lexer::Token::Semi => true,
|
||||
lexer::Token::BraceOpen => true,
|
||||
lexer::Token::BraceClose => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn color_err(elem: impl std::fmt::Display) -> Result<String, std::fmt::Error> {
|
||||
let mut text = format!("{}", elem);
|
||||
|
||||
@ -237,3 +358,15 @@ fn color_warn(elem: impl std::fmt::Display) -> Result<String, std::fmt::Error> {
|
||||
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
fn color_underline(elem: impl std::fmt::Display) -> Result<String, std::fmt::Error> {
|
||||
let mut text = format!("{}", elem);
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
{
|
||||
use colored::Colorize;
|
||||
text = format!("{}", text.bright_yellow().underline())
|
||||
}
|
||||
|
||||
Ok(text)
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use std::{fmt::Debug, str::Chars};
|
||||
use std::{
|
||||
fmt::{Debug, Write},
|
||||
str::Chars,
|
||||
};
|
||||
|
||||
static DECIMAL_NUMERICS: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
|
||||
@ -99,6 +102,54 @@ impl From<Token> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn len(&self) -> usize {
|
||||
self.to_string().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Token {
|
||||
fn to_string(&self) -> String {
|
||||
match &self {
|
||||
Token::Identifier(ident) => ident.clone(),
|
||||
Token::DecimalValue(val) => val.to_string(),
|
||||
Token::StringLit(lit) => format!("\"{}\"", lit),
|
||||
Token::LetKeyword => String::from("let"),
|
||||
Token::MutKeyword => String::from("mut"),
|
||||
Token::ImportKeyword => String::from("import"),
|
||||
Token::ReturnKeyword => String::from("return"),
|
||||
Token::FnKeyword => String::from("fn"),
|
||||
Token::PubKeyword => String::from("pub"),
|
||||
Token::Arrow => String::from("=>"),
|
||||
Token::If => String::from("if"),
|
||||
Token::Else => String::from("else"),
|
||||
Token::True => String::from("true"),
|
||||
Token::False => String::from("false"),
|
||||
Token::Extern => String::from("extern"),
|
||||
Token::Struct => String::from("struct"),
|
||||
Token::Semi => String::from(';'),
|
||||
Token::Equals => String::from('='),
|
||||
Token::Colon => String::from(':'),
|
||||
Token::Plus => String::from('+'),
|
||||
Token::Times => String::from('*'),
|
||||
Token::Minus => String::from('-'),
|
||||
Token::GreaterThan => String::from('>'),
|
||||
Token::LessThan => String::from('<'),
|
||||
Token::Et => String::from('&'),
|
||||
Token::Exclamation => String::from('!'),
|
||||
Token::ParenOpen => String::from('('),
|
||||
Token::ParenClose => String::from(')'),
|
||||
Token::BraceOpen => String::from('{'),
|
||||
Token::BraceClose => String::from('}'),
|
||||
Token::BracketOpen => String::from('['),
|
||||
Token::BracketClose => String::from(']'),
|
||||
Token::Comma => String::from(','),
|
||||
Token::Dot => String::from('.'),
|
||||
Token::Eof => String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A token with a position
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FullToken {
|
||||
@ -115,7 +166,19 @@ impl Debug for FullToken {
|
||||
}
|
||||
}
|
||||
|
||||
pub type Position = (u32, u32);
|
||||
/// (Column, Line)
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Position(pub u32, pub u32);
|
||||
|
||||
impl Position {
|
||||
pub fn add(&self, num: u32) -> Position {
|
||||
Position(self.0 + num, self.1)
|
||||
}
|
||||
|
||||
pub fn sub(&self, num: u32) -> Position {
|
||||
Position(self.0 - num, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
struct Cursor<'a> {
|
||||
pub position: Position,
|
||||
@ -153,14 +216,14 @@ pub fn tokenize<T: Into<String>>(to_tokenize: T) -> Result<Vec<FullToken>, Error
|
||||
let to_tokenize = to_tokenize.into();
|
||||
let mut cursor = Cursor {
|
||||
char_stream: to_tokenize.chars(),
|
||||
position: (0, 1),
|
||||
position: Position(0, 1),
|
||||
};
|
||||
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
while let Some(character) = &cursor.next() {
|
||||
// Save "current" token first character position
|
||||
let position = (cursor.position.0 - 1, cursor.position.1);
|
||||
let position = cursor.position.sub(1);
|
||||
|
||||
let variant = match character {
|
||||
// Whitespace
|
||||
@ -275,9 +338,9 @@ pub fn tokenize<T: Into<String>>(to_tokenize: T) -> Result<Vec<FullToken>, Error
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Error {
|
||||
#[error("Invalid token '{}' at Ln {}, Col {}", .0, (.1).1, (.1).0)]
|
||||
#[error("Invalid token '{}' ", .0)]
|
||||
InvalidToken(char, Position),
|
||||
#[error("String literal that starts at Ln {}, Col {} is never finished!", (.0).1, (.0).0)]
|
||||
#[error("String literal is never finished!")]
|
||||
MissingQuotation(Position),
|
||||
}
|
||||
|
||||
|
@ -209,7 +209,7 @@ impl std::iter::Sum for TokenRange {
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Error {
|
||||
#[error("Expected {} at Ln {}, Col {}, got {:?}", .0, (.2).1, (.2).0, .1)]
|
||||
#[error("Expected {} got {:?}", .0, .1)]
|
||||
Expected(String, Token, Position),
|
||||
#[error("Source file contains no tokens")]
|
||||
FileEmpty,
|
||||
|
Loading…
Reference in New Issue
Block a user