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