use crate::compiler::syntax::SyntaxKind;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceLocation {
pub line: usize,
pub column: usize,
pub offset: usize,
}
impl SourceLocation {
pub fn from_offset(source: &str, offset: usize) -> Self {
let offset = offset.min(source.len());
let before = &source[..offset];
let line = before.chars().filter(|&c| c == '\n').count() + 1;
let line_start = before.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
let line_content = &source[line_start..offset];
let column = line_content.chars().map(|c| c.len_utf16()).sum::<usize>() + 1;
Self {
line,
column,
offset,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseErrorKind {
UnexpectedToken,
UnexpectedEof,
InvalidOperator,
MissingOperand,
UnterminatedExpression,
InvalidAssignmentTarget,
MissingClosingParen,
MissingClosingBracket,
MissingClosingBrace,
MissingArrowBody,
InvalidArrowParams,
MissingConditionalColon,
InvalidPropertyName,
DuplicateSpread,
InvalidRestPosition,
TypeAnnotationInExpression,
InvalidPrefixOperator,
InvalidPostfixOperator,
InvalidBinaryOperator,
MissingPropertyName,
InvalidComputedProperty,
UnterminatedTemplateLiteral,
UnterminatedStringLiteral,
InvalidNumericLiteral,
InvalidBigIntLiteral,
ExpectedIdentifier,
ExpectedExpression,
ExpectedTypeAnnotation,
InvalidNewExpression,
InvalidAwaitExpression,
InvalidYieldExpression,
MissingFunctionBody,
InvalidFunctionParameter,
DuplicateParameter,
InvalidDestructuringPattern,
MissingInitializer,
InvalidOptionalChaining,
InvalidTaggedTemplate,
InvalidClassExpression,
ReservedWordAsIdentifier,
InvalidInterpolation,
InvalidRustExpression,
MissingElseBranch,
}
impl ParseErrorKind {
pub fn description(&self) -> &'static str {
match self {
Self::UnexpectedToken => "unexpected token",
Self::UnexpectedEof => "unexpected end of input",
Self::InvalidOperator => "invalid operator in this context",
Self::MissingOperand => "missing operand for operator",
Self::UnterminatedExpression => "expression was not properly terminated",
Self::InvalidAssignmentTarget => "invalid assignment target",
Self::MissingClosingParen => "missing closing parenthesis ')'",
Self::MissingClosingBracket => "missing closing bracket ']'",
Self::MissingClosingBrace => "missing closing brace '}'",
Self::MissingArrowBody => "missing arrow function body after '=>'",
Self::InvalidArrowParams => "invalid arrow function parameters",
Self::MissingConditionalColon => "missing ':' in conditional expression",
Self::InvalidPropertyName => "invalid property name",
Self::DuplicateSpread => "multiple spread operators not allowed in this position",
Self::InvalidRestPosition => "rest element must be last",
Self::TypeAnnotationInExpression => "type annotation not allowed in expression",
Self::InvalidPrefixOperator => "invalid prefix operator",
Self::InvalidPostfixOperator => "invalid postfix operator",
Self::InvalidBinaryOperator => "invalid binary operator",
Self::MissingPropertyName => "missing property name after '.'",
Self::InvalidComputedProperty => "invalid computed property",
Self::UnterminatedTemplateLiteral => "unterminated template literal",
Self::UnterminatedStringLiteral => "unterminated string literal",
Self::InvalidNumericLiteral => "invalid numeric literal",
Self::InvalidBigIntLiteral => "invalid BigInt literal",
Self::ExpectedIdentifier => "expected identifier",
Self::ExpectedExpression => "expected expression",
Self::ExpectedTypeAnnotation => "expected type annotation",
Self::InvalidNewExpression => "invalid 'new' expression",
Self::InvalidAwaitExpression => "invalid 'await' expression",
Self::InvalidYieldExpression => "invalid 'yield' expression",
Self::MissingFunctionBody => "missing function body",
Self::InvalidFunctionParameter => "invalid function parameter",
Self::DuplicateParameter => "duplicate parameter name",
Self::InvalidDestructuringPattern => "invalid destructuring pattern",
Self::MissingInitializer => "missing initializer",
Self::InvalidOptionalChaining => "invalid optional chaining",
Self::InvalidTaggedTemplate => "invalid tagged template",
Self::InvalidClassExpression => "invalid class expression",
Self::ReservedWordAsIdentifier => "reserved word cannot be used as identifier",
Self::InvalidInterpolation => "invalid interpolation syntax",
Self::InvalidRustExpression => "invalid Rust expression in interpolation",
Self::MissingElseBranch => {
"if-expression requires an {:else} branch to produce a value"
}
}
}
}
#[derive(Debug, Clone)]
pub struct ParseError {
pub kind: ParseErrorKind,
pub position: usize,
pub context: String,
pub expected: Vec<String>,
pub found: Option<String>,
pub help: Option<String>,
}
impl ParseError {
pub fn new(kind: ParseErrorKind, position: usize) -> Self {
Self {
kind,
position,
context: String::new(),
expected: Vec::new(),
found: None,
help: None,
}
}
pub fn unexpected_token(position: usize, expected: &[&str], found: SyntaxKind) -> Self {
Self {
kind: ParseErrorKind::UnexpectedToken,
position,
context: String::new(),
expected: expected.iter().map(|s| (*s).to_string()).collect(),
found: Some(format!("{:?}", found)),
help: None,
}
}
pub fn unexpected_token_text(position: usize, expected: &[&str], found: &str) -> Self {
Self {
kind: ParseErrorKind::UnexpectedToken,
position,
context: String::new(),
expected: expected.iter().map(|s| (*s).to_string()).collect(),
found: Some(format!("'{}'", found)),
help: None,
}
}
pub fn unexpected_eof(position: usize, context: &str) -> Self {
Self {
kind: ParseErrorKind::UnexpectedEof,
position,
context: context.to_string(),
expected: Vec::new(),
found: None,
help: None,
}
}
pub fn expected_expression(position: usize) -> Self {
Self {
kind: ParseErrorKind::ExpectedExpression,
position,
context: String::new(),
expected: vec!["expression".to_string()],
found: None,
help: None,
}
}
pub fn expected_expression_found(position: usize, found: SyntaxKind) -> Self {
Self {
kind: ParseErrorKind::ExpectedExpression,
position,
context: String::new(),
expected: vec!["expression".to_string()],
found: Some(format!("{:?}", found)),
help: None,
}
}
pub fn invalid_assignment_target(position: usize) -> Self {
Self {
kind: ParseErrorKind::InvalidAssignmentTarget,
position,
context: String::new(),
expected: vec!["identifier or member expression".to_string()],
found: None,
help: Some(
"The left-hand side of an assignment must be a variable, property, or pattern"
.to_string(),
),
}
}
pub fn missing_closing(kind: ParseErrorKind, position: usize, opened_at: usize) -> Self {
let delimiter = match kind {
ParseErrorKind::MissingClosingParen => "')'",
ParseErrorKind::MissingClosingBracket => "']'",
ParseErrorKind::MissingClosingBrace => "'}'",
_ => "delimiter",
};
Self {
kind,
position,
context: format!("opened at position {}", opened_at),
expected: vec![delimiter.to_string()],
found: None,
help: None,
}
}
pub fn with_context(mut self, context: &str) -> Self {
self.context = context.to_string();
self
}
pub fn with_expected(mut self, expected: &[&str]) -> Self {
self.expected = expected.iter().map(|s| (*s).to_string()).collect();
self
}
pub fn with_found(mut self, found: &str) -> Self {
self.found = Some(found.to_string());
self
}
pub fn with_help(mut self, help: &str) -> Self {
self.help = Some(help.to_string());
self
}
pub fn format_with_source(&self, source: &str) -> String {
self.format_with_source_and_file(source, "input", 0)
}
pub fn format_with_source_and_file(
&self,
source: &str,
filename: &str,
line_offset: usize,
) -> String {
use crate::compiler::error_fmt::{ErrorFormat, build_annotation};
let annotation = build_annotation(
self.found.as_deref(),
&self.expected.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
);
let mut fmt = ErrorFormat::new(self.kind.description(), source, self.position)
.filename(filename)
.line_offset(line_offset);
if let Some(ann) = annotation {
fmt = fmt.annotation(ann);
}
if let Some(ref help) = self.help {
fmt = fmt.help(help);
}
fmt.format()
}
pub fn to_message(&self) -> String {
let mut msg = format!("Parse error at position {}: ", self.position);
msg.push_str(self.kind.description());
if let Some(ref found) = self.found {
msg.push_str(&format!(", found {}", found));
}
if !self.expected.is_empty() {
if self.expected.len() == 1 {
msg.push_str(&format!(", expected {}", self.expected[0]));
} else {
msg.push_str(&format!(", expected one of: {}", self.expected.join(", ")));
}
}
if !self.context.is_empty() {
msg.push_str(&format!(" (while parsing {})", self.context));
}
if let Some(ref help) = self.help {
msg.push_str(&format!("\n help: {}", help));
}
msg
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_message())
}
}
impl std::error::Error for ParseError {}
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug)]
pub enum ParseOutcome<T> {
Node(T),
Skip,
}
impl<T> ParseOutcome<T> {
pub fn is_node(&self) -> bool {
matches!(self, Self::Node(_))
}
pub fn is_skip(&self) -> bool {
matches!(self, Self::Skip)
}
pub fn into_option(self) -> Option<T> {
match self {
Self::Node(node) => Some(node),
Self::Skip => None,
}
}
}
pub type ParseNodeResult<T> = Result<ParseOutcome<T>, ParseError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unexpected_token_error() {
let err = ParseError::unexpected_token(42, &["identifier", "number"], SyntaxKind::LParen);
let msg = err.to_message();
assert!(msg.contains("position 42"));
assert!(msg.contains("unexpected token"));
assert!(msg.contains("identifier"));
assert!(msg.contains("number"));
}
#[test]
fn test_unexpected_eof_error() {
let err = ParseError::unexpected_eof(100, "function body");
let msg = err.to_message();
assert!(msg.contains("position 100"));
assert!(msg.contains("unexpected end of input"));
assert!(msg.contains("function body"));
}
#[test]
fn test_invalid_assignment_target() {
let err = ParseError::invalid_assignment_target(50);
let msg = err.to_message();
assert!(msg.contains("invalid assignment target"));
assert!(msg.contains("help:"));
}
#[test]
fn test_error_with_context() {
let err = ParseError::new(ParseErrorKind::MissingOperand, 10)
.with_context("binary expression")
.with_expected(&["+", "-", "*", "/"])
.with_found("EOF");
let msg = err.to_message();
assert!(msg.contains("binary expression"));
assert!(msg.contains("EOF"));
}
#[test]
fn test_source_location_from_offset() {
let source = "line1\nline2\n = @{raw_var_ident} as @{inner}[];";
let line3 = " = @{raw_var_ident} as @{inner}[]";
let offset_of_bracket = 6 + 6 + line3.len() - 1;
let loc = SourceLocation::from_offset(source, offset_of_bracket);
println!("Source: {:?}", source);
println!("Offset: {}", offset_of_bracket);
println!("Line: {}, Column: {}", loc.line, loc.column);
assert_eq!(loc.line, 3, "Should be line 3");
assert_eq!(loc.column, 44, "Should be column 44");
}
#[test]
fn test_source_location_first_line() {
let source = "hello world";
let loc = SourceLocation::from_offset(source, 6); assert_eq!(loc.line, 1);
assert_eq!(loc.column, 7); }
#[test]
fn test_source_location_after_newline() {
let source = "hello\nworld";
let loc = SourceLocation::from_offset(source, 6); assert_eq!(loc.line, 2);
assert_eq!(loc.column, 1); }
#[test]
fn test_caret_with_placeholder_and_array_type() {
let source = r#"instance.@{field._field_ident} = @{raw_var_ident} as @{inner}[];"#;
use crate::compiler::lexer::Lexer;
let tokens: Vec<_> = Lexer::new(source).tokenize().unwrap();
println!("Tokens:");
for (i, t) in tokens.iter().enumerate() {
println!(" {}: {:?} '{}' at byte {}", i, t.kind, t.text, t.start);
}
let rbracket = tokens.iter().find(|t| t.text == "]").unwrap();
println!("\nRBracket token at byte offset: {}", rbracket.start);
let loc = SourceLocation::from_offset(source, rbracket.start);
println!("SourceLocation: line {}, column {}", loc.line, loc.column);
let char_at_offset = source.chars().nth(rbracket.start).unwrap();
println!(
"Character at offset {}: '{}'",
rbracket.start, char_at_offset
);
assert_eq!(char_at_offset, ']', "Token start should point to ']'");
}
#[test]
fn test_all_error_kinds_have_descriptions() {
let kinds = [
ParseErrorKind::UnexpectedToken,
ParseErrorKind::UnexpectedEof,
ParseErrorKind::InvalidOperator,
ParseErrorKind::MissingOperand,
ParseErrorKind::UnterminatedExpression,
ParseErrorKind::InvalidAssignmentTarget,
ParseErrorKind::MissingClosingParen,
ParseErrorKind::MissingClosingBracket,
ParseErrorKind::MissingClosingBrace,
ParseErrorKind::MissingArrowBody,
ParseErrorKind::InvalidArrowParams,
ParseErrorKind::MissingConditionalColon,
ParseErrorKind::InvalidPropertyName,
ParseErrorKind::DuplicateSpread,
ParseErrorKind::InvalidRestPosition,
ParseErrorKind::TypeAnnotationInExpression,
ParseErrorKind::InvalidPrefixOperator,
ParseErrorKind::InvalidPostfixOperator,
ParseErrorKind::InvalidBinaryOperator,
ParseErrorKind::MissingPropertyName,
ParseErrorKind::InvalidComputedProperty,
ParseErrorKind::UnterminatedTemplateLiteral,
ParseErrorKind::UnterminatedStringLiteral,
ParseErrorKind::InvalidNumericLiteral,
ParseErrorKind::InvalidBigIntLiteral,
ParseErrorKind::ExpectedIdentifier,
ParseErrorKind::ExpectedExpression,
ParseErrorKind::ExpectedTypeAnnotation,
ParseErrorKind::InvalidNewExpression,
ParseErrorKind::InvalidAwaitExpression,
ParseErrorKind::InvalidYieldExpression,
ParseErrorKind::MissingFunctionBody,
ParseErrorKind::InvalidFunctionParameter,
ParseErrorKind::DuplicateParameter,
ParseErrorKind::InvalidDestructuringPattern,
ParseErrorKind::MissingInitializer,
ParseErrorKind::InvalidOptionalChaining,
ParseErrorKind::InvalidTaggedTemplate,
ParseErrorKind::InvalidClassExpression,
ParseErrorKind::ReservedWordAsIdentifier,
ParseErrorKind::InvalidInterpolation,
ParseErrorKind::InvalidRustExpression,
ParseErrorKind::MissingElseBranch,
];
for kind in kinds {
let desc = kind.description();
assert!(!desc.is_empty(), "{:?} has empty description", kind);
}
}
}