use crate::{Diagnostic, Expression, tokens::Tokens};
use common_macros::b_tree_map;
use core::fmt;
use detached_str::{Str, StrSlice};
use nom::error::{ErrorKind, ParseError};
use std::{collections::BTreeMap, error::Error as StdError};
#[derive(Debug)]
pub struct SyntaxError {
pub source: Str,
pub kind: SyntaxErrorKind,
}
#[derive(Debug)]
pub enum SyntaxErrorKind {
Expected {
input: StrSlice,
expected: &'static str,
found: Option<String>,
hint: Option<&'static str>,
},
TokenizationErrors(Box<[Diagnostic]>),
ExpectedChar {
expected: char,
at: Option<StrSlice>,
},
NomError {
kind: ErrorKind,
at: Option<StrSlice>,
cause: Option<Box<SyntaxError>>,
},
InternalError(String),
InvalidCmdSymbol(String),
CustomError(String, StrSlice),
UnknownOperator(String, StrSlice),
UnExpectedToken(String, StrSlice),
InvalidEscapeSequence(String, StrSlice),
PrecedenceTooLow(StrSlice),
NoExpression,
ArgumentMismatch {
name: String,
expected: u8,
received: u8,
},
RecursionDepth {
input: StrSlice,
depth: usize,
},
}
impl StdError for SyntaxError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match &self.kind {
SyntaxErrorKind::NomError { cause, .. } => {
cause
.as_ref()
.map(|c| c.as_ref() as &(dyn StdError + 'static))
}
_ => None,
}
}
}
impl SyntaxError {
pub const ERROR_CODE_EXPECTED: u8 = 1;
pub const ERROR_CODE_TOKENIZATION_ERRORS: u8 = 2;
pub const ERROR_CODE_EXPECTED_CHAR: u8 = 3;
pub const ERROR_CODE_NOM_ERROR: u8 = 4;
pub const ERROR_CODE_INTERNAL_ERROR: u8 = 5;
pub const ERROR_CODE_INVALID_CMD_SYMBOL: u8 = 6;
pub const ERROR_CODE_CUSTOM_ERROR: u8 = 7;
pub const ERROR_CODE_UNKNOWN_OPERATOR: u8 = 8;
pub const ERROR_CODE_UNEXPECTED_TOKEN: u8 = 9;
pub const ERROR_CODE_INVALID_ESCAPE_SEQUENCE: u8 = 10;
pub const ERROR_CODE_PRECEDENCE_TOO_LOW: u8 = 11;
pub const ERROR_CODE_NO_EXPRESSION: u8 = 12;
pub const ERROR_CODE_ARGUMENT_MISMATCH: u8 = 13;
pub const ERROR_CODE_RECURSION_DEPTH: u8 = 14;
pub fn codes() -> BTreeMap<String, Expression> {
b_tree_map! {
String::from("expected") => Expression::from(Self::ERROR_CODE_EXPECTED),
String::from("tokenization_errors") => Expression::from(Self::ERROR_CODE_TOKENIZATION_ERRORS),
String::from("expected_char") => Expression::from(Self::ERROR_CODE_EXPECTED_CHAR),
String::from("nom_error") => Expression::from(Self::ERROR_CODE_NOM_ERROR),
String::from("internal_error") => Expression::from(Self::ERROR_CODE_INTERNAL_ERROR),
String::from("invalid_cmd_symbol") => Expression::from(Self::ERROR_CODE_INVALID_CMD_SYMBOL),
String::from("custom_error") => Expression::from(Self::ERROR_CODE_CUSTOM_ERROR),
String::from("unknown_operator") => Expression::from(Self::ERROR_CODE_UNKNOWN_OPERATOR),
String::from("unexpected_token") => Expression::from(Self::ERROR_CODE_UNEXPECTED_TOKEN),
String::from("invalid_escape_sequence") => Expression::from(Self::ERROR_CODE_INVALID_ESCAPE_SEQUENCE),
String::from("precedence_too_low") => Expression::from(Self::ERROR_CODE_PRECEDENCE_TOO_LOW),
String::from("no_expression") => Expression::from(Self::ERROR_CODE_NO_EXPRESSION),
String::from("argument_mismatch") => Expression::from(Self::ERROR_CODE_ARGUMENT_MISMATCH),
String::from("recursion_depth") => Expression::from(Self::ERROR_CODE_RECURSION_DEPTH),
}
}
pub fn code(&self) -> u8 {
match self.kind {
SyntaxErrorKind::Expected { .. } => Self::ERROR_CODE_EXPECTED,
SyntaxErrorKind::TokenizationErrors(..) => Self::ERROR_CODE_TOKENIZATION_ERRORS,
SyntaxErrorKind::ExpectedChar { .. } => Self::ERROR_CODE_EXPECTED_CHAR,
SyntaxErrorKind::NomError { .. } => Self::ERROR_CODE_NOM_ERROR,
SyntaxErrorKind::InternalError(..) => Self::ERROR_CODE_INTERNAL_ERROR,
SyntaxErrorKind::InvalidCmdSymbol(..) => Self::ERROR_CODE_INVALID_CMD_SYMBOL,
SyntaxErrorKind::CustomError(..) => Self::ERROR_CODE_CUSTOM_ERROR,
SyntaxErrorKind::UnknownOperator(..) => Self::ERROR_CODE_UNKNOWN_OPERATOR,
SyntaxErrorKind::UnExpectedToken(..) => Self::ERROR_CODE_UNEXPECTED_TOKEN,
SyntaxErrorKind::InvalidEscapeSequence(..) => Self::ERROR_CODE_INVALID_ESCAPE_SEQUENCE,
SyntaxErrorKind::PrecedenceTooLow(..) => Self::ERROR_CODE_PRECEDENCE_TOO_LOW,
SyntaxErrorKind::NoExpression => Self::ERROR_CODE_NO_EXPRESSION,
SyntaxErrorKind::ArgumentMismatch { .. } => Self::ERROR_CODE_ARGUMENT_MISMATCH,
SyntaxErrorKind::RecursionDepth { .. } => Self::ERROR_CODE_RECURSION_DEPTH,
}
}
pub fn new(source: Str, kind: SyntaxErrorKind) -> Self {
Self { source, kind }
}
}
impl SyntaxErrorKind {
#[inline]
pub fn failure(
input: StrSlice,
expected: &'static str,
found: Option<String>,
hint: Option<&'static str>,
) -> nom::Err<Self> {
nom::Err::Failure(SyntaxErrorKind::Expected {
input,
expected,
found,
hint,
})
}
pub fn empty_fail(input: Tokens<'_>) -> Result<(), nom::Err<Self>> {
if input.is_empty() {
Err(nom::Err::Failure(SyntaxErrorKind::Expected {
input: input.get_str_slice(),
expected: "Some Expression",
found: Some("Nothing".into()),
hint: None,
}))
} else {
Ok(())
}
}
pub fn empty_back(input: Tokens<'_>) -> Result<(), nom::Err<Self>> {
if input.is_empty() {
Err(nom::Err::Error(SyntaxErrorKind::Expected {
input: input.get_str_slice(),
expected: "Some Expression to parse",
found: Some("Nothing".into()),
hint: None,
}))
} else {
Ok(())
}
}
#[inline]
pub fn expected(
input: StrSlice,
expected: &'static str,
found: Option<String>,
hint: Option<&'static str>,
) -> nom::Err<Self> {
nom::Err::Error(SyntaxErrorKind::Expected {
input,
expected,
found,
hint,
})
}
pub fn unclosed_delimiter(start: StrSlice, delim: &'static str) -> nom::Err<Self> {
nom::Err::Error(SyntaxErrorKind::Expected {
input: start,
expected: delim,
found: None,
hint: Some("Check if parentheses/quotes are matched"),
})
}
}
impl ParseError<Tokens<'_>> for SyntaxErrorKind {
fn from_error_kind(input: Tokens<'_>, kind: ErrorKind) -> Self {
SyntaxErrorKind::NomError {
kind,
at: input.first().map(|t| t.range),
cause: None,
}
}
fn append(input: Tokens<'_>, kind: ErrorKind, _: Self) -> Self {
SyntaxErrorKind::NomError {
kind,
at: input.first().map(|t| t.range),
cause: None,
}
}
fn from_char(input: Tokens<'_>, expected: char) -> Self {
SyntaxErrorKind::ExpectedChar {
expected,
at: input.first().map(|t| t.range),
}
}
fn or(self, other: Self) -> Self {
use SyntaxErrorKind::*;
match (&self, &other) {
(TokenizationErrors(_), _) => self,
(_, TokenizationErrors(_)) => other,
(RecursionDepth { .. }, _) => self,
(_, RecursionDepth { .. }) => other,
(Expected { .. }, NomError { .. }) => self,
(NomError { .. }, Expected { .. }) => other,
(ArgumentMismatch { .. }, NomError { .. }) => self,
(NomError { .. }, ArgumentMismatch { .. }) => other,
(UnknownOperator(..), NoExpression) => self,
(NoExpression, UnknownOperator(..)) => other,
(InternalError(_), _) => other,
(_, InternalError(_)) => self,
(
Expected {
input: input1,
hint: hint1,
..
},
Expected {
input: input2,
hint: hint2,
..
},
) => {
if hint1.is_some() && hint2.is_none() {
self
} else if hint1.is_none() && hint2.is_some() {
other
} else {
if input1.start() <= input2.start() {
self
} else {
other
}
}
}
_ => self,
}
}
}
impl ParseError<Tokens<'_>> for SyntaxError {
fn from_error_kind(input: Tokens<'_>, kind: ErrorKind) -> Self {
Self::new(
input.str.clone(),
SyntaxErrorKind::NomError {
kind,
at: input.first().map(|t| t.range),
cause: None,
},
)
}
fn append(input: Tokens<'_>, kind: ErrorKind, other: Self) -> Self {
Self::new(
input.str.clone(),
SyntaxErrorKind::NomError {
kind,
at: input.first().map(|t| t.range),
cause: Some(Box::new(other)),
},
)
}
fn from_char(input: Tokens<'_>, expected: char) -> Self {
Self::new(
input.str.clone(),
SyntaxErrorKind::ExpectedChar {
expected,
at: input.first().map(|t| t.range),
},
)
}
fn or(self, other: Self) -> Self {
match self.kind {
SyntaxErrorKind::InternalError(_) => other,
SyntaxErrorKind::TokenizationErrors(..) => self,
_ => self,
}
}
}
impl fmt::Display for SyntaxError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.kind {
SyntaxErrorKind::Expected {
input,
expected,
found,
hint,
} => {
write!(f, "{RED_START}{BOLD}syntax error{RESET}: ")?;
write!(f, "expect {YELLOW_START}{expected}{RESET}")?;
if let Some(found) = found {
write!(f, ", found {RED2_START}{found}{RESET}")?;
}
writeln!(f)?;
print_error_lines(&self.source, *input, f, 72)?;
if let Some(hint) = hint {
writeln!(f, " hint: {hint}")?;
}
Ok(())
}
SyntaxErrorKind::TokenizationErrors(errors) => {
for err in errors.iter() {
fmt_token_error(&self.source, err, f)?;
}
Ok(())
}
SyntaxErrorKind::ExpectedChar { expected, at } => {
write!(f, "{RED_START}{BOLD}syntax error{RESET}: ")?;
write!(f, "expect character {YELLOW_START}{expected:?}{RESET}")?;
writeln!(f)?;
if let Some(at) = at {
print_error_lines(&self.source, *at, f, 72)?;
}
Ok(())
}
SyntaxErrorKind::NomError { kind, at, cause } => {
write!(f, "{RED_START}{BOLD}nom syntax error{RESET}: ")?;
writeln!(f, "`{kind:?}`")?;
if let Some(at) = at {
print_error_lines(&self.source, *at, f, 72)?;
}
if let Some(cause) = cause {
writeln!(f, "Caused by: {cause}")?;
}
Ok(())
}
SyntaxErrorKind::InternalError(s) => {
writeln!(f, "{RED_START}{BOLD}internal syntax error: {s}{RESET}")
}
SyntaxErrorKind::InvalidCmdSymbol(s) => {
writeln!(f, "{RED_START}{BOLD}invalid cmd symbo: {s}{RESET}")
}
SyntaxErrorKind::CustomError(s, at) => {
writeln!(f, "{RED_START}{BOLD}syntax error: {s}{RESET}")?;
print_error_lines(&self.source, *at, f, 72)?;
Ok(())
}
SyntaxErrorKind::NoExpression => {
writeln!(f, "{RED_START}{BOLD}no expression recognized{RESET}")
}
SyntaxErrorKind::UnknownOperator(op, at) => {
writeln!(f, "{RED_START}{BOLD}unknown operator {op:?}{RESET}")?;
print_error_lines(&self.source, *at, f, 72)?;
Ok(())
}
SyntaxErrorKind::UnExpectedToken(op, at) => {
writeln!(f, "{RED_START}{BOLD}unexpected token {op:?}{RESET}")?;
print_error_lines(&self.source, *at, f, 72)?;
Ok(())
}
SyntaxErrorKind::InvalidEscapeSequence(op, at) => {
writeln!(f, "{RED_START}{BOLD}invalid escape sequence {op:?}{RESET}")?;
print_error_lines(&self.source, *at, f, 72)?;
Ok(())
}
SyntaxErrorKind::PrecedenceTooLow(at) => {
writeln!(f, "{RED_START}{BOLD}precedence too low {RESET}")?;
print_error_lines(&self.source, *at, f, 72)?;
Ok(())
}
SyntaxErrorKind::ArgumentMismatch {
name,
expected,
received,
} => {
writeln!(
f,
"{RED_START}{BOLD}arguments mismatch for function `{name}`: expected {expected}, found {received} {RESET}"
)
}
SyntaxErrorKind::RecursionDepth { input, depth } => {
write!(f, "{RED_START}{BOLD}max recursion reached{RESET}: ")?;
write!(f, "depth: {YELLOW_START}{depth}{RESET}")?;
writeln!(f)?;
print_error_lines(&self.source, *input, f, 72)?;
writeln!(
f,
" hint: simplify your script, or config LUME_MAX_SYNTAX_RECURSION larger."
)?;
Ok(())
}
}
}
}
fn fmt_token_error(string: &Str, err: &Diagnostic, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{RED_START}{BOLD}token error{RESET}: ")?;
match err {
Diagnostic::Valid => Ok(()),
&Diagnostic::InvalidNumber(at) => {
let num = at.to_str(string).trim();
writeln!(f, "invalid number `{num}`")?;
print_error_lines(string, at, f, 72)
}
&Diagnostic::IllegalChar(at) => {
writeln!(f, "invalid char {:?}", at.to_str(string))?;
print_error_lines(string, at, f, 72)
}
&Diagnostic::NotTokenized(at) => {
writeln!(
f,
"there are leftover tokens after tokenization:\n{}",
at.to_str(string)
)?;
print_error_lines(string, at, f, 72)
}
&Diagnostic::UnterminatedString(at) => {
writeln!(f, "unterminated string:\n{}", at.to_str(string))?;
print_error_lines(string, at, f, 72)
}
}
}
const DIM_START: &str = "\x1b[2m";
const BLUE_START: &str = "\x1b[34m";
const YELLOW_START: &str = "\x1b[38;5;230m";
const RED2_START: &str = "\x1b[38;5;210m";
const RED_START: &str = "\x1b[38;5;9m";
const BOLD: &str = "\x1b[1m";
const RESET: &str = "\x1b[m\x1b[0m";
fn print_error_lines(
string: &Str,
at: StrSlice,
f: &mut fmt::Formatter,
_max_width: usize,
) -> fmt::Result {
let error_start = at.start();
let error_end = at.end();
let before_text = &string[..error_start];
let lines_before: Vec<&str> = before_text.lines().collect();
let error_line_num = lines_before.len();
let error_col = lines_before.last().map(|line| line.len()).unwrap_or(0);
let all_lines: Vec<&str> = string.lines().collect();
let context_start = error_line_num.saturating_sub(3);
let context_end = (error_line_num + 3).min(all_lines.len());
writeln!(f, " {BLUE_START} ▏{RESET}")?;
for (i, line) in all_lines[context_start..context_end].iter().enumerate() {
let line_num = context_start + i + 1;
let is_error_line = line_num == error_line_num;
if is_error_line {
let line_start = before_text.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
let error_start_in_line = error_start.saturating_sub(line_start);
let error_end_in_line = (error_end.saturating_sub(line_start)).min(line.len());
let safe_start = error_start_in_line.min(line.len());
let safe_end = error_end_in_line.min(line.len()).max(safe_start);
write!(f, "{RED_START}{line_num:>5}{RESET} {BLUE_START}▏{RESET} ")?;
if safe_start > 0 {
write!(f, "{}", &line[..safe_start])?;
}
if safe_end > safe_start {
write!(f, "{}{}{}", RED_START, &line[safe_start..safe_end], RESET)?;
}
if safe_end < line.len() {
writeln!(f, "{}", &line[safe_end..])?;
} else {
writeln!(f)?;
}
if safe_end >= safe_start {
write!(f, " {BLUE_START}▏{RESET} ")?;
for _ in 0..safe_start {
write!(f, " ")?;
}
write!(f, "{RED_START}{BOLD}\x1b[5m^")?;
for _ in 1..(safe_end - safe_start) {
write!(f, "~")?;
}
writeln!(f, "{RESET}")?;
}
} else {
writeln!(
f,
"{BLUE_START}{line_num:>5} ▏{RESET} {DIM_START}{line}{RESET}"
)?;
}
}
writeln!(f, " {BLUE_START} ▏{RESET}")?;
writeln!(
f,
" ↳ at line {}, column {}",
error_line_num,
error_col + 1
)?;
Ok(())
}