use crate::SourceFile;
use crate::Span;
use std::collections::HashSet;
use std::fmt;
use std::sync::Arc;
#[cfg(feature = "ariadne")]
mod ariadne;
#[cfg(feature = "ariadne")]
pub use self::ariadne::Report;
#[derive(Debug, Clone)]
#[repr(u16)]
pub(crate) enum ErrorKind {
Silent,
UnknownCharacter(Span),
UnterminatedGroup {
start: String,
span: Span,
},
UnterminatedChar(Span),
LongChar(Span),
UnterminatedString(Span),
UnexpectedToken {
expected: HashSet<String>,
span: Span,
},
EndOfFile(usize),
Custom {
message: String,
span: Span,
code: u16,
},
}
impl ErrorKind {
const fn code(&self) -> u16 {
match self {
ErrorKind::Silent => 0,
ErrorKind::UnknownCharacter(_) => 1,
ErrorKind::UnterminatedGroup { .. } => 2,
ErrorKind::UnterminatedChar(_) => 3,
ErrorKind::LongChar(_) => 4,
ErrorKind::UnterminatedString(_) => 5,
ErrorKind::UnexpectedToken { .. } => 6,
ErrorKind::EndOfFile(_) => 7,
ErrorKind::Custom { code, .. } => 8 + *code,
}
}
fn span(&self, source: &Arc<SourceFile>) -> Span {
match self {
ErrorKind::Silent => panic!("called `span` on `ErrorKind::Silent`"),
ErrorKind::Custom { span, .. }
| ErrorKind::UnknownCharacter(span)
| ErrorKind::UnterminatedGroup { span, .. }
| ErrorKind::UnterminatedChar(span)
| ErrorKind::LongChar(span)
| ErrorKind::UnterminatedString(span)
| ErrorKind::UnexpectedToken { span, .. } => span.clone(),
&ErrorKind::EndOfFile(n) => Span::new(n as u32, n as u32, Arc::clone(source)),
}
}
}
fn unexpected_token_message(expected: &HashSet<String>) -> String {
if expected.len() == 1 {
format!("Expected {}", expected.iter().next().unwrap())
} else if expected.len() == 2 {
let mut iter = expected.iter();
format!(
"Expected {} or {}",
iter.next().unwrap(),
iter.next().unwrap()
)
} else {
let mut message = "Expected one of: ".to_string();
for (i, token) in expected.iter().enumerate() {
message.push_str(token);
if i + 1 < expected.len() {
message.push_str(", ");
}
}
message
}
}
#[derive(Debug, Clone)]
pub(crate) struct SingleError {
source: Arc<SourceFile>,
kind: ErrorKind,
}
#[derive(Debug, Clone)]
pub struct Error {
errors: Vec<SingleError>,
}
impl Error {
pub(crate) fn new(source: Arc<SourceFile>, kind: ErrorKind) -> Error {
Error {
errors: vec![SingleError { source, kind }],
}
}
pub(crate) const fn empty() -> Error {
Error { errors: vec![] }
}
pub(crate) const fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub(crate) fn eof_to_group(&mut self, span: &Span, start: &str) {
for error in &mut self.errors {
if let ErrorKind::EndOfFile(_) = error.kind {
error.kind = ErrorKind::UnterminatedGroup {
start: start.to_string(),
span: span.clone(),
};
}
}
}
pub(crate) fn group_to_string(&mut self) {
for error in &mut self.errors {
if let ErrorKind::UnterminatedGroup { span, .. } = &error.kind {
error.kind = ErrorKind::UnterminatedString(span.clone());
}
}
}
pub(crate) fn string_to_char(&mut self) {
for error in &mut self.errors {
if let ErrorKind::UnterminatedString(span) = &error.kind {
error.kind = ErrorKind::UnterminatedChar(span.clone());
}
}
}
pub fn add(&mut self, mut other: Error) {
self.errors.append(&mut other.errors);
}
#[must_use]
pub fn with(mut self, other: Error) -> Self {
self.add(other);
self
}
pub(crate) fn details(&self) -> Option<String> {
#[cfg(feature = "ariadne")]
let details = {
let mut buf = vec![];
self.to_reports()
.into_iter()
.try_for_each(|report| report.write(&mut buf))
.ok()
.and_then(|()| String::from_utf8(buf).ok())
};
#[cfg(not(feature = "ariadne"))]
let details = None;
details
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for error in &self.errors {
let file_name = error.source.name();
match &error.kind {
ErrorKind::Silent => {}
ErrorKind::Custom { message, span, .. } => {
writeln!(f, "[E{:02}] Error: {}", error.kind.code(), message)?;
let (line, col) = span.start_location();
write!(f, "[{file_name}:{line}:{col}]")?;
}
ErrorKind::UnknownCharacter(span) => {
writeln!(
f,
"[E{:02}] Error: Unrecognised character",
error.kind.code()
)?;
let (line, col) = span.start_location();
write!(f, "[{file_name}:{line}:{col}]")?;
}
ErrorKind::UnterminatedGroup { start, span } => {
writeln!(
f,
"[E{:02}] Error: Unmatched '{}'",
error.kind.code(),
start
)?;
let (line, col) = span.start_location();
write!(f, "[{file_name}:{line}:{col}]")?;
}
ErrorKind::UnterminatedChar(span) => {
writeln!(
f,
"[E{:02}] Error: Unterminated character literal",
error.kind.code()
)?;
let (line, col) = span.start_location();
write!(f, "[{file_name}:{line}:{col}]")?;
}
ErrorKind::LongChar(span) => {
writeln!(
f,
"[E{:02}] Error: Character literals must be exactly one character long",
error.kind.code()
)?;
let (line, col) = span.start_location();
write!(f, "[{file_name}:{line}:{col}]")?;
}
ErrorKind::UnterminatedString(span) => {
writeln!(
f,
"[E{:02}] Error: Unterminated string literal",
error.kind.code()
)?;
let (line, col) = span.start_location();
write!(f, "[{file_name}:{line}:{col}]")?;
}
ErrorKind::UnexpectedToken { expected, span } => {
writeln!(f, "[E{:02}] Error: Unexpected token", error.kind.code())?;
let (line, col) = span.start_location();
writeln!(f, "[{file_name}:{line}:{col}]")?;
write!(f, "{}", unexpected_token_message(expected))?;
}
ErrorKind::EndOfFile(_) => write!(f, "Unexpected end of file while parsing")?,
}
}
Ok(())
}
}