use std::fmt;
use crate::ast::Span;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, PartialEq)]
pub struct Error {
pub kind: ErrorKind,
pub span: Span,
pub source: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ErrorKind {
UnexpectedChar(char),
UnterminatedString,
InvalidNumber(String),
UnexpectedToken {
expected: String,
found: String,
},
UnexpectedEof,
InvalidExpression(String),
InvalidTypeInst(String),
UnsupportedFeature {
feature: String,
phase: String,
workaround: Option<String>,
},
TypeError {
expected: String,
found: String,
},
DuplicateDeclaration(String),
UndefinedVariable(String),
ArraySizeMismatch {
declared: usize,
provided: usize,
},
Array2DSizeMismatch {
declared_rows: usize,
declared_cols: usize,
provided_rows: usize,
provided_cols: usize,
},
Array3DSizeMismatch {
declared_d1: usize,
declared_d2: usize,
declared_d3: usize,
provided_d1: usize,
provided_d2: usize,
provided_d3: usize,
},
Array2DValueCountMismatch {
expected: usize,
provided: usize,
},
Array3DValueCountMismatch {
expected: usize,
provided: usize,
},
Array2DInvalidContext,
Array3DInvalidContext,
Array2DValuesMustBeLiteral,
Array3DValuesMustBeLiteral,
Message(String),
}
impl Error {
pub fn new(kind: ErrorKind, span: Span) -> Self {
Self {
kind,
span,
source: None,
}
}
pub fn with_source(mut self, source: String) -> Self {
self.source = Some(source);
self
}
pub fn unexpected_token(expected: &str, found: &str, span: Span) -> Self {
Self::new(
ErrorKind::UnexpectedToken {
expected: expected.to_string(),
found: found.to_string(),
},
span,
)
}
pub fn unexpected_eof(span: Span) -> Self {
Self::new(ErrorKind::UnexpectedEof, span)
}
pub fn unsupported_feature(feature: &str, phase: &str, span: Span) -> Self {
Self::new(
ErrorKind::UnsupportedFeature {
feature: feature.to_string(),
phase: phase.to_string(),
workaround: None,
},
span,
)
}
pub fn type_error(expected: &str, found: &str, span: Span) -> Self {
Self::new(
ErrorKind::TypeError {
expected: expected.to_string(),
found: found.to_string(),
},
span,
)
}
pub fn array_size_mismatch(declared: usize, provided: usize, span: Span) -> Self {
Self::new(
ErrorKind::ArraySizeMismatch { declared, provided },
span,
)
}
pub fn array2d_size_mismatch(declared_rows: usize, declared_cols: usize,
provided_rows: usize, provided_cols: usize, span: Span) -> Self {
Self::new(
ErrorKind::Array2DSizeMismatch {
declared_rows, declared_cols, provided_rows, provided_cols
},
span,
)
}
pub fn array3d_size_mismatch(declared_d1: usize, declared_d2: usize, declared_d3: usize,
provided_d1: usize, provided_d2: usize, provided_d3: usize, span: Span) -> Self {
Self::new(
ErrorKind::Array3DSizeMismatch {
declared_d1, declared_d2, declared_d3, provided_d1, provided_d2, provided_d3
},
span,
)
}
pub fn array2d_value_count_mismatch(expected: usize, provided: usize, span: Span) -> Self {
Self::new(
ErrorKind::Array2DValueCountMismatch { expected, provided },
span,
)
}
pub fn array3d_value_count_mismatch(expected: usize, provided: usize, span: Span) -> Self {
Self::new(
ErrorKind::Array3DValueCountMismatch { expected, provided },
span,
)
}
pub fn array2d_invalid_context(span: Span) -> Self {
Self::new(ErrorKind::Array2DInvalidContext, span)
}
pub fn array3d_invalid_context(span: Span) -> Self {
Self::new(ErrorKind::Array3DInvalidContext, span)
}
pub fn array2d_values_must_be_literal(span: Span) -> Self {
Self::new(ErrorKind::Array2DValuesMustBeLiteral, span)
}
pub fn array3d_values_must_be_literal(span: Span) -> Self {
Self::new(ErrorKind::Array3DValuesMustBeLiteral, span)
}
pub fn message(msg: &str, span: Span) -> Self {
Self::new(ErrorKind::Message(msg.to_string()), span)
}
pub fn with_workaround(mut self, workaround: &str) -> Self {
if let ErrorKind::UnsupportedFeature { workaround: w, .. } = &mut self.kind {
*w = Some(workaround.to_string());
}
self
}
pub fn location(&self) -> (usize, usize) {
if let Some(source) = &self.source {
let mut line = 1;
let mut col = 1;
let pos = if self.span.start >= source.len() {
source.len().saturating_sub(1)
} else {
self.span.start
};
for (i, c) in source.chars().enumerate() {
if i >= pos {
break;
}
if c == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
(line, col)
} else {
(0, 0)
}
}
pub fn source_line(&self) -> Option<(String, usize)> {
self.source.as_ref().map(|source| {
let lines: Vec<&str> = source.lines().collect();
let (line_num, col) = self.location();
let line = if line_num > 0 && line_num <= lines.len() {
lines[line_num - 1].to_string()
} else {
String::new()
};
let adjusted_col = if col > line.len() {
line.len()
} else {
col
};
(line, adjusted_col)
})
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (line, col) = self.location();
write!(f, "Error")?;
if line > 0 {
write!(f, " at line {}, column {}", line, col)?;
}
write!(f, ": ")?;
match &self.kind {
ErrorKind::UnexpectedChar(c) => {
write!(f, "Unexpected character '{}'", c)
}
ErrorKind::UnterminatedString => {
write!(f, "Unterminated string literal")
}
ErrorKind::InvalidNumber(s) => {
write!(f, "Invalid number: {}", s)
}
ErrorKind::UnexpectedToken { expected, found } => {
write!(f, "Expected {}, found {}", expected, found)
}
ErrorKind::UnexpectedEof => {
write!(f, "Unexpected end of file")
}
ErrorKind::InvalidExpression(msg) => {
write!(f, "Invalid expression: {}", msg)
}
ErrorKind::InvalidTypeInst(msg) => {
write!(f, "Invalid type-inst: {}", msg)
}
ErrorKind::UnsupportedFeature { feature, phase, workaround } => {
write!(f, "Unsupported feature: {}", feature)?;
write!(f, " (will be supported in {})", phase)?;
if let Some(w) = workaround {
write!(f, "\nWorkaround: {}", w)?;
}
Ok(())
}
ErrorKind::TypeError { expected, found } => {
write!(f, "Type error: expected {}, found {}", expected, found)
}
ErrorKind::DuplicateDeclaration(name) => {
write!(f, "Duplicate declaration of '{}'", name)
}
ErrorKind::UndefinedVariable(name) => {
write!(f, "Undefined variable '{}'", name)
}
ErrorKind::ArraySizeMismatch { declared, provided } => {
write!(f, "Array size mismatch: declared {}, but got {}", declared, provided)
}
ErrorKind::Array2DSizeMismatch { declared_rows, declared_cols, provided_rows, provided_cols } => {
write!(f, "array2d size mismatch: declared {}x{}, but provided {}x{}",
declared_rows, declared_cols, provided_rows, provided_cols)
}
ErrorKind::Array3DSizeMismatch { declared_d1, declared_d2, declared_d3, provided_d1, provided_d2, provided_d3 } => {
write!(f, "array3d size mismatch: declared {}x{}x{}, but provided {}x{}x{}",
declared_d1, declared_d2, declared_d3, provided_d1, provided_d2, provided_d3)
}
ErrorKind::Array2DValueCountMismatch { expected, provided } => {
write!(f, "array2d value count mismatch: expected {}, got {}", expected, provided)
}
ErrorKind::Array3DValueCountMismatch { expected, provided } => {
write!(f, "array3d value count mismatch: expected {}, got {}", expected, provided)
}
ErrorKind::Array2DInvalidContext => {
write!(f, "array2d() can only be used for 2D arrays")
}
ErrorKind::Array3DInvalidContext => {
write!(f, "array3d() can only be used for 3D arrays")
}
ErrorKind::Array2DValuesMustBeLiteral => {
write!(f, "array2d() values must be an array literal [...]")
}
ErrorKind::Array3DValuesMustBeLiteral => {
write!(f, "array3d() values must be an array literal [...]")
}
ErrorKind::Message(msg) => {
write!(f, "{}", msg)
}
}?;
if let Some((source_line, col)) = self.source_line() {
write!(f, "\n {}", source_line)?;
if col > 0 {
write!(f, "\n {}{}", " ".repeat(col - 1), "^")?;
} else if source_line.is_empty() && matches!(self.kind, ErrorKind::UnexpectedEof) {
write!(f, "\n ^")?;
}
}
Ok(())
}
}
impl std::error::Error for Error {}
impl From<String> for Error {
fn from(msg: String) -> Self {
Self::new(ErrorKind::Message(msg), Span::dummy())
}
}
impl From<&str> for Error {
fn from(msg: &str) -> Self {
Self::new(ErrorKind::Message(msg.to_string()), Span::dummy())
}
}