Skip to content
Snippets Groups Projects
mod.rs 9.06 KiB
Newer Older
use crate::lexer::{ScriptTokenType, Span};
Louis's avatar
Louis committed
use crate::parse::ScriptToken;
use crate::utilities::offset_to_line_column;
use lalrpop_util::ParseError as BaseLalrError;
Louis's avatar
Louis committed
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::process::{ExitCode, Termination};

Louis's avatar
Louis committed
pub type LalrError<'a> = BaseLalrError<usize, ScriptTokenType, TokenError<'a>>;
Louis's avatar
Louis committed

#[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)]
Louis's avatar
Louis committed
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 {
Louis's avatar
Louis committed
		token: ScriptTokenType,
		span: ErrorSpan,
		expected: Vec<String>,
	},
	ExpectedEof {
Louis's avatar
Louis committed
		token: ScriptTokenType,
		span: ErrorSpan,
	},
	Custom(String),
pub type TokenIndex = usize;
pub type ErrorSpan = (TokenIndex, TokenIndex);

#[derive(Debug, PartialEq)]
Louis's avatar
Louis committed
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 }
	}
}

Louis's avatar
Louis committed
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(),
		}
	}
}

Louis's avatar
Louis committed
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!(
Louis's avatar
Louis committed
						"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!(
Louis's avatar
Louis committed
						"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(),
					},
Louis's avatar
Louis committed
					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$}] {}",
			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 {
			"| [{:>width$}] {}",
			line + 1,
			next,
			width = number_length
Louis's avatar
Louis committed
	}
	if let Some(extra) = additional {
		writeln!(f, "|\n| {}", extra)
	} else {
		writeln!(f, "|")