ezno-parser 0.1.7

Parser and AST definitions for Ezno
Documentation
/// Contains lexing and parser errors
use std::fmt::{self, Display};

use crate::TSXToken;
use source_map::Span;
use tokenizer_lib::{sized_tokens::TokenStart, Token};

#[allow(missing_docs)]
pub enum ParseErrors<'a> {
	UnexpectedToken { expected: &'a [TSXToken], found: TSXToken },
	UnexpectedSymbol(derive_finite_automaton::InvalidCharacter),
	ClosingTagDoesNotMatch { expected: &'a str, found: &'a str },
	ExpectedStringLiteral { found: TSXToken },
	TypeArgumentsNotValidOnReference,
	UnmatchedBrackets,
	FunctionParameterOptionalAndDefaultValue,
	ExpectedIdent { found: TSXToken, at_location: &'a str },
	ParameterCannotHaveDefaultValueHere,
	InvalidLHSAssignment,
	LexingFailed,
	ExpectedCatchOrFinally,
	InvalidDeclareItem(&'static str),
	DestructuringRequiresValue,
	CannotAccessObjectLiteralDirectly,
	TrailingCommaNotAllowedHere,
	InvalidNumberLiteral,
	ReservedIdentifier,
	AwaitRequiresForOf,
	CannotUseLeadingParameterHere,
	ExpectedIdentifier,
	ExpectedNumberLiteral,
	NonStandardSyntaxUsedWithoutEnabled,
	ExpectRule,
	InvalidRegexFlag,
	ExpectedDeclaration,
	CannotHaveRegularMemberAfterSpread,
	InvalidLHSOfIs,
}

impl<'a> Display for ParseErrors<'a> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			ParseErrors::UnexpectedToken { expected, found } => {
				f.write_str("Expected ")?;
				match expected {
					[] => unreachable!("no expected tokens given"),
					[a] => f.write_fmt(format_args!("{a:?}")),
					[a, b] => f.write_fmt(format_args!("{a:?} or {b:?}")),
					[head @ .., end] => {
						let start = head
							.iter()
							.map(|token| format!("{token:?}"))
							.reduce(|mut acc, token| {
								acc.push_str(", ");
								acc.push_str(&token);
								acc
							})
							.unwrap();
						f.write_fmt(format_args!("{start} or {end:?}"))
					}
				}?;
				write!(f, " found {found:?}")
			}
			ParseErrors::UnexpectedSymbol(invalid_character) => Display::fmt(invalid_character, f),
			ParseErrors::ClosingTagDoesNotMatch { expected, found } => {
				write!(f, "Expected </{expected}> found </{found}>")
			}
			ParseErrors::NonStandardSyntaxUsedWithoutEnabled => {
				write!(f, "Cannot use this syntax without flag enabled")
			}
			ParseErrors::ExpectedStringLiteral { found } => {
				write!(f, "Expected string literal, found {found:?}")
			}
			ParseErrors::TypeArgumentsNotValidOnReference => {
				write!(f, "Type arguments not valid on reference",)
			}
			ParseErrors::UnmatchedBrackets => f.write_str("Unmatched brackets"),
			ParseErrors::FunctionParameterOptionalAndDefaultValue => {
				f.write_str("Function parameter cannot be optional *and* have default expression")
			}
			ParseErrors::ExpectedIdent { found, at_location } => {
				write!(f, "Expected identifier at {at_location}, found {found:?}")
			}
			ParseErrors::ParameterCannotHaveDefaultValueHere => {
				f.write_str("Function parameter cannot be have default value here")
			}
			ParseErrors::InvalidLHSAssignment => f.write_str("Invalid syntax on LHS of assignment"),
			ParseErrors::ExpectedCatchOrFinally => {
				f.write_str("Expected 'catch' or 'finally' to follow 'try'")
			}
			ParseErrors::LexingFailed => {
				// unreachable!("This should never be written"),
				f.write_str("Lexing issue")
			}
			ParseErrors::InvalidDeclareItem(item) => {
				write!(f, "Declare item '{item}' must be in .d.ts file")
			}
			ParseErrors::DestructuringRequiresValue => {
				write!(f, "RHS of destructured declaration requires expression")
			}
			ParseErrors::CannotAccessObjectLiteralDirectly => {
				write!(f, "Cannot get property on object literal directly")
			}
			ParseErrors::TrailingCommaNotAllowedHere => {
				write!(f, "Trailing comma not allowed here")
			}
			ParseErrors::InvalidNumberLiteral => {
				write!(f, "Invalid number literal")
			}
			ParseErrors::ReservedIdentifier => {
				write!(f, "Found reserved identifier")
			}
			ParseErrors::AwaitRequiresForOf => {
				write!(f, "Can only use await on for (.. of ..)")
			}
			ParseErrors::CannotUseLeadingParameterHere => {
				write!(f, "Cannot write this constraint in this kind of function")
			}
			ParseErrors::ExpectedIdentifier => {
				write!(f, "Expected variable identifier")
			}
			ParseErrors::ExpectedNumberLiteral => {
				write!(f, "Expected number literal")
			}
			ParseErrors::ExpectRule => {
				write!(f, "'-' must be followed by a readonly rule")
			}
			ParseErrors::InvalidRegexFlag => {
				write!(f, "Regexp flags must be 'd', 'g', 'i', 'm', 's', 'u' or 'y'")
			}
			ParseErrors::ExpectedDeclaration => {
				write!(f, "Expected identifier after variable declaration keyword")
			}
			ParseErrors::CannotHaveRegularMemberAfterSpread => {
				write!(f, "Cannot have regular member after spread")
			}
			ParseErrors::InvalidLHSOfIs => {
				write!(f, "LHS must be variable reference")
			}
		}
	}
}

#[allow(missing_docs)]
#[derive(Debug)]
pub enum LexingErrors {
	SecondDecimalPoint,
	NumberLiteralCannotHaveDecimalPoint,
	NumberLiteralBaseSpecifierMustPrecededWithZero,
	InvalidCharacterInJSXTag(char),
	UnbalancedJSXClosingTags,
	ExpectedClosingChevronAtEndOfSelfClosingTag,
	InvalidCharacterInAttributeKey(char),
	UnexpectedCharacter(derive_finite_automaton::InvalidCharacter),
	EmptyAttributeName,
	ExpectedJSXEndTag,
	NewLineInStringLiteral,
	ExpectedEndToMultilineComment,
	ExpectedEndToStringLiteral,
	UnexpectedEndToNumberLiteral,
	InvalidNumeralItemBecauseOfLiteralKind,
	ExpectedEndToRegexLiteral,
	ExpectedEndToJSXLiteral,
	ExpectedEndToTemplateLiteral,
	InvalidExponentUsage,
	InvalidUnderscore,
	CannotLoadLargeFile(usize),
	ExpectedDashInComment,
	ExpectedOpenChevron,
}

impl Display for LexingErrors {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			LexingErrors::SecondDecimalPoint => {
				f.write_str("Second decimal point found in number literal")
			}
			LexingErrors::NumberLiteralCannotHaveDecimalPoint => {
				f.write_str("Number literal with specified base cannot have decimal point")
			}
			LexingErrors::NumberLiteralBaseSpecifierMustPrecededWithZero => {
				f.write_str("Number literal base character must be proceeded with a zero")
			}
			LexingErrors::InvalidCharacterInJSXTag(chr) => {
				write!(f, "Invalid character {chr:?} in JSX tag")
			}
			LexingErrors::ExpectedClosingChevronAtEndOfSelfClosingTag => {
				f.write_str("Expected closing angle at end of self closing JSX tag")
			}
			LexingErrors::InvalidCharacterInAttributeKey(chr) => {
				write!(f, "Invalid character {chr:?} in JSX attribute name")
			}
			LexingErrors::EmptyAttributeName => f.write_str("Empty JSX attribute name"),
			LexingErrors::ExpectedJSXEndTag => f.write_str("Expected JSX end tag"),
			LexingErrors::NewLineInStringLiteral => {
				f.write_str("String literals cannot contain new lines")
			}
			LexingErrors::ExpectedEndToMultilineComment => {
				f.write_str("Unclosed multiline comment")
			}
			LexingErrors::ExpectedEndToStringLiteral => f.write_str("Unclosed string literal"),
			LexingErrors::UnexpectedEndToNumberLiteral => f.write_str("Unclosed number literal"),
			LexingErrors::ExpectedEndToRegexLiteral => f.write_str("Unclosed regex literal"),
			LexingErrors::ExpectedEndToJSXLiteral => f.write_str("Unclosed JSX literal"),
			LexingErrors::ExpectedEndToTemplateLiteral => f.write_str("Unclosed template literal"),
			LexingErrors::UnexpectedCharacter(err) => Display::fmt(err, f),
			LexingErrors::UnbalancedJSXClosingTags => f.write_str("Too many closing JSX tags"),
			LexingErrors::InvalidExponentUsage => f.write_str("Two e in number literal"),
			LexingErrors::InvalidUnderscore => f.write_str("Numeric separator in invalid place"),
			LexingErrors::InvalidNumeralItemBecauseOfLiteralKind => {
				f.write_str("Invalid item in binary, hex or octal literal")
			}
			LexingErrors::CannotLoadLargeFile(size) => {
				write!(f, "Cannot parse {size:?} byte file (4GB maximum)")
			}
			LexingErrors::ExpectedDashInComment => {
				f.write_str("JSX comments must have two dashes after `<!` start")
			}
			LexingErrors::ExpectedOpenChevron => {
				f.write_str("Unexpected token in HTML. Expected '<'")
			}
		}
	}
}

// For TokenReader::expect_next
impl From<Option<(TSXToken, Token<TSXToken, TokenStart>)>> for ParseError {
	fn from(opt: Option<(TSXToken, Token<TSXToken, TokenStart>)>) -> Self {
		if let Some((expected_type, token)) = opt {
			let position = token.get_span();
			let reason =
				ParseErrors::UnexpectedToken { expected: &[expected_type], found: token.0 };
			Self::new(reason, position)
		} else {
			parse_lexing_error()
		}
	}
}

// For TokenReader::next which only
pub(crate) fn parse_lexing_error() -> ParseError {
	ParseError::new(ParseErrors::LexingFailed, source_map::Nullable::NULL)
}

pub trait ParserErrorReason: Display {}

impl<'a> ParserErrorReason for ParseErrors<'a> {}
impl ParserErrorReason for LexingErrors {}

/// A error for not parsing
#[derive(Debug)]
pub struct ParseError {
	pub reason: String,
	pub position: Span,
}

impl ParseError {
	#[allow(clippy::needless_pass_by_value)]
	pub fn new(reason: impl ParserErrorReason, position: Span) -> Self {
		Self { reason: reason.to_string(), position }
	}
}

impl std::error::Error for ParseError {}
impl std::fmt::Display for ParseError {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		f.write_fmt(format_args!("ParseError: {} @ byte indices {:?}", self.reason, self.position))
	}
}

pub type ParseResult<T> = Result<T, ParseError>;