use crate::lexer::{ScriptTokenType, Span}; use crate::parse::ScriptToken; use crate::utilities::offset_to_line_column; use lalrpop_util::ParseError as BaseLalrError; use std::error::Error; use std::fmt::{Display, Formatter}; use std::process::{ExitCode, Termination}; pub type LalrError<'a> = BaseLalrError<usize, ScriptTokenType, TokenError<'a>>; #[derive(Debug)] pub enum TokenErrorKind<'a> { Incomplete, NomError(nom::error::Error<Span<'a>>), } #[derive(Debug)] pub struct TokenError<'a> { pub kind: TokenErrorKind<'a>, } impl<'a> From<TokenErrorKind<'a>> for TokenError<'a> { fn from(value: TokenErrorKind<'a>) -> Self { TokenError { kind: value } } } impl<'a> Display for TokenError<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match &self.kind { TokenErrorKind::Incomplete => write!(f, "Incomplete Program"), TokenErrorKind::NomError(err) => write!(f, "{}", err), } } } impl<'a> Error for TokenError<'a> {} impl<'a> From<nom::Err<nom::error::Error<Span<'a>>>> for TokenError<'a> { fn from(value: nom::Err<nom::error::Error<Span<'a>>>) -> Self { match value { nom::Err::Error(err) => TokenErrorKind::NomError(err).into(), nom::Err::Failure(err) => TokenErrorKind::NomError(err).into(), nom::Err::Incomplete(_) => TokenErrorKind::Incomplete.into(), } } } #[derive(Clone, Debug)] pub enum ParseErrorKind<'a> { Unexpected { found: ScriptToken<'a>, expected: peg::error::ExpectedSet, }, } #[derive(Clone, Debug)] pub struct ParseError<'a> { pub kind: ParseErrorKind<'a>, } #[derive(Debug, PartialEq)] pub enum ForgeErrorKind<'a> { IncompleteInput, LexerError(nom::error::Error<Span<'a>>), UnexpectedToken { found: ScriptToken<'a>, expected: peg::error::ExpectedSet, }, InvalidToken { location: TokenIndex, }, UnexpectedEof { expected: Vec<String>, }, UnrecognizedToken { token: ScriptTokenType, span: ErrorSpan, expected: Vec<String>, }, ExpectedEof { token: ScriptTokenType, span: ErrorSpan, }, Custom(String), } pub type TokenIndex = usize; pub type ErrorSpan = (TokenIndex, TokenIndex); #[derive(Debug, PartialEq)] pub struct ForgeError<'a> { pub kind: ForgeErrorKind<'a>, } impl<'a> Termination for ForgeError<'a> { fn report(self) -> ExitCode { match self.kind { ForgeErrorKind::IncompleteInput => ExitCode::from(101), ForgeErrorKind::LexerError(_) => ExitCode::from(102), ForgeErrorKind::UnexpectedToken { .. } => ExitCode::from(103), ForgeErrorKind::InvalidToken { .. } => ExitCode::from(104), ForgeErrorKind::UnexpectedEof { .. } => ExitCode::from(105), ForgeErrorKind::UnrecognizedToken { .. } => ExitCode::from(106), ForgeErrorKind::ExpectedEof { .. } => ExitCode::from(107), ForgeErrorKind::Custom(_) => ExitCode::from(1), } } } impl<'a> From<ForgeErrorKind<'a>> for ForgeError<'a> { fn from(value: ForgeErrorKind<'a>) -> Self { Self { kind: value } } } impl<'a> From<ParseError<'a>> for ForgeError<'a> { fn from(value: ParseError<'a>) -> Self { match value.kind { ParseErrorKind::Unexpected { found, expected } => ForgeError { kind: ForgeErrorKind::UnexpectedToken { found, expected }, }, } } } impl<'a> From<TokenError<'a>> for ForgeError<'a> { fn from(value: TokenError<'a>) -> Self { match value.kind { TokenErrorKind::Incomplete => ForgeError { kind: ForgeErrorKind::IncompleteInput, }, TokenErrorKind::NomError(span) => ForgeError { kind: ForgeErrorKind::LexerError(span), }, } } } impl<'a> From<LalrError<'a>> for ForgeError<'a> { fn from(value: LalrError<'a>) -> Self { match value { LalrError::InvalidToken { location } => { ForgeErrorKind::InvalidToken { location }.into() } LalrError::UnrecognizedEof { expected, .. } => { ForgeErrorKind::UnexpectedEof { expected }.into() } LalrError::UnrecognizedToken { token, expected } => ForgeErrorKind::UnrecognizedToken { expected, token: token.1, span: (token.0, token.2), } .into(), LalrError::ExtraToken { token } => ForgeErrorKind::ExpectedEof { token: token.1, span: (token.0, token.2), } .into(), LalrError::User { error } => ForgeErrorKind::Custom(format!("{}", error)).into(), } } } pub type ForgeResult<'a, T> = Result<T, ForgeError<'a>>; pub fn print_unexpected_token<'a>( source: &'a str, token: &'a ScriptToken<'a>, expected: &'a peg::error::ExpectedSet, ) { let line = token.position.location_line() as usize; let column = token.position.get_column(); let previous_line = if line > 1 { source.lines().nth(line - 2) } else { None }; let source_line = source.lines().nth(line - 1).expect("Missing line"); let next_line = source.lines().nth(line); let largest_line_num = line.max(line.saturating_sub(1)).max(line.saturating_add(1)); let number_length = format!("{}", largest_line_num).len(); eprintln!("| Script error on line {} at \"{}\"\n|", line, token); if let Some(prev) = previous_line { eprintln!("| [{:>width$}] {}", line - 1, prev, width = number_length); } eprintln!( "| [{:>width$}] {}", line, source_line, width = number_length ); eprintln!( "| {} {}{}", vec![" "; number_length + 2].join(""), vec![" "; column - 1].join(""), vec!["^"; token.token_type.len()].join(""), ); if let Some(next) = next_line { eprintln!("| [{:>width$}] {}", line + 1, next, width = number_length); } eprintln!("|\n| Failed To Parse: expected {}", expected); } pub fn print_forge_error<'a>(source: &'a str, fe: &'a ForgeError) { eprintln!("{}", format_forge_error(source, fe)); } pub fn format_forge_error<'a>(source: &'a str, fe: &'a ForgeError) -> String { match &fe.kind { ForgeErrorKind::IncompleteInput => String::from("| Unexpected end of file"), ForgeErrorKind::LexerError(err) => format!("| {}", err), ForgeErrorKind::UnexpectedToken { found, expected } => { format!( "{}", SourcePrinter( source, format!("{}", found.token_type), HighlightConfig { line: found.position.location_line() as usize, column: found.position.get_column(), highlight_len: found.token_type.len(), }, Some(format!( "Found {}, expected one of {}", found.token_type, expected .tokens() .collect::<Vec<&str>>() .as_slice() .join(", ") )) ), ) } ForgeErrorKind::InvalidToken { location } => { let (line, column) = offset_to_line_column(source, *location); format!( "{}", SourcePrinter( source, String::from("invalid token"), HighlightConfig { line, column, highlight_len: 1, }, None, ) ) } ForgeErrorKind::UnexpectedEof { expected } => { format!( "| Unexpected end of file, expected one of {}", expected.as_slice().join(", ") ) } ForgeErrorKind::UnrecognizedToken { token, span, expected, } => { let (line, column) = offset_to_line_column(source, span.0); format!( "{}", SourcePrinter( source, format!("{}", token), HighlightConfig { line, column, highlight_len: token.len(), }, Some(format!( "Found {}, expected one of {}", token, expected.as_slice().join(", ") )) ), ) } ForgeErrorKind::ExpectedEof { token, span } => { let (line, column) = offset_to_line_column(source, span.0); format!( "{}", SourcePrinter( source, format!("{}", token), HighlightConfig { line, column, highlight_len: token.len(), }, Some(format!("Expected EOF, found {}", token)) ), ) } ForgeErrorKind::Custom(msg) => format!("| {}", msg), } } #[derive(Clone, Copy)] pub struct HighlightConfig { line: usize, column: usize, highlight_len: usize, } struct SourcePrinter<'a>(&'a str, String, HighlightConfig, Option<String>); impl<'a> Display for SourcePrinter<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { highlight_source_snippet(self.0, &self.1, self.2, self.3.clone(), f) } } pub fn highlight_source_snippet( source: &str, name: impl Display, highlight: HighlightConfig, additional: Option<String>, f: &mut Formatter<'_>, ) -> std::fmt::Result { let HighlightConfig { line, column, highlight_len, } = highlight; let previous_line = if line > 1 { source.lines().nth(line - 2) } else { None }; let source_line = source.lines().nth(line - 1).expect("Missing line"); let next_line = source.lines().nth(line); let largest_line_num = line.max(line.saturating_sub(1)).max(line.saturating_add(1)); let number_length = format!("{}", largest_line_num).len(); writeln!(f, "| Script error on line {} at \"{}\"\n|", line, name)?; if let Some(prev) = previous_line { writeln!( f, "| [{:>width$}] {}", line - 1, prev, width = number_length )?; } writeln!( f, "| [{:>width$}] {}", line, source_line, width = number_length )?; writeln!( f, "| {} {}{}", vec![" "; number_length + 2].join(""), vec![" "; column - 1].join(""), vec!["^"; highlight_len].join(""), )?; if let Some(next) = next_line { writeln!( f, "| [{:>width$}] {}", line + 1, next, width = number_length )?; } if let Some(extra) = additional { writeln!(f, "|\n| {}", extra) } else { writeln!(f, "|") } }