use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub const fn point(at: usize) -> Self {
Self { start: at, end: at }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
E001,
E002,
E003,
E004,
E005,
E006,
E007,
E008,
E009,
E010,
}
#[derive(Debug, Clone, Error, PartialEq)]
pub enum NightjarLanguageError {
#[error("[{code:?}] Parse error at {span:?}: {message}")]
ParseError {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Type error at {span:?}: {message}")]
TypeError {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Argument error at {span:?}: {message}")]
ArgumentError {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Symbol not found at {span:?}: {message}")]
SymbolNotFound {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Ambiguous symbol at {span:?}: {message}")]
AmbiguousSymbol {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Division by zero at {span:?}: {message}")]
DivisionByZero {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Recursion depth limit exceeded at {span:?}: {message}")]
RecursionError {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Index out of bounds at {span:?}: {message}")]
IndexError {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Integer overflow at {span:?}: {message}")]
IntegerOverflow {
span: Span,
code: ErrorCode,
message: String,
},
#[error("[{code:?}] Scope error at {span:?}: {message}")]
ScopeError {
span: Span,
code: ErrorCode,
message: String,
},
}
impl NightjarLanguageError {
pub fn span(&self) -> Span {
match self {
NightjarLanguageError::ParseError { span, .. }
| NightjarLanguageError::TypeError { span, .. }
| NightjarLanguageError::ArgumentError { span, .. }
| NightjarLanguageError::SymbolNotFound { span, .. }
| NightjarLanguageError::AmbiguousSymbol { span, .. }
| NightjarLanguageError::DivisionByZero { span, .. }
| NightjarLanguageError::RecursionError { span, .. }
| NightjarLanguageError::IndexError { span, .. }
| NightjarLanguageError::IntegerOverflow { span, .. }
| NightjarLanguageError::ScopeError { span, .. } => *span,
}
}
pub fn code(&self) -> ErrorCode {
match self {
NightjarLanguageError::ParseError { code, .. }
| NightjarLanguageError::TypeError { code, .. }
| NightjarLanguageError::ArgumentError { code, .. }
| NightjarLanguageError::SymbolNotFound { code, .. }
| NightjarLanguageError::AmbiguousSymbol { code, .. }
| NightjarLanguageError::DivisionByZero { code, .. }
| NightjarLanguageError::RecursionError { code, .. }
| NightjarLanguageError::IndexError { code, .. }
| NightjarLanguageError::IntegerOverflow { code, .. }
| NightjarLanguageError::ScopeError { code, .. } => *code,
}
}
pub fn message(&self) -> &str {
match self {
NightjarLanguageError::ParseError { message, .. }
| NightjarLanguageError::TypeError { message, .. }
| NightjarLanguageError::ArgumentError { message, .. }
| NightjarLanguageError::SymbolNotFound { message, .. }
| NightjarLanguageError::AmbiguousSymbol { message, .. }
| NightjarLanguageError::DivisionByZero { message, .. }
| NightjarLanguageError::RecursionError { message, .. }
| NightjarLanguageError::IndexError { message, .. }
| NightjarLanguageError::IntegerOverflow { message, .. }
| NightjarLanguageError::ScopeError { message, .. } => message,
}
}
}
pub(crate) fn parse_error(span: Span, message: impl Into<String>) -> NightjarLanguageError {
NightjarLanguageError::ParseError {
span,
code: ErrorCode::E001,
message: message.into(),
}
}
pub(crate) fn type_error(span: Span, message: impl Into<String>) -> NightjarLanguageError {
NightjarLanguageError::TypeError {
span,
code: ErrorCode::E002,
message: message.into(),
}
}
pub(crate) fn argument_error(span: Span, message: impl Into<String>) -> NightjarLanguageError {
NightjarLanguageError::ArgumentError {
span,
code: ErrorCode::E003,
message: message.into(),
}
}
pub(crate) fn symbol_not_found(span: Span, path: &str) -> NightjarLanguageError {
NightjarLanguageError::SymbolNotFound {
span,
code: ErrorCode::E004,
message: format!("symbol `{}` not found", path),
}
}
pub(crate) fn division_by_zero(span: Span) -> NightjarLanguageError {
NightjarLanguageError::DivisionByZero {
span,
code: ErrorCode::E006,
message: "division or modulo by zero".to_string(),
}
}
pub(crate) fn recursion_error(span: Span, limit: usize) -> NightjarLanguageError {
NightjarLanguageError::RecursionError {
span,
code: ErrorCode::E007,
message: format!("expression recursion depth exceeds limit ({})", limit),
}
}
pub(crate) fn index_error(span: Span, idx: i64, len: usize) -> NightjarLanguageError {
NightjarLanguageError::IndexError {
span,
code: ErrorCode::E008,
message: format!("index {} out of bounds for list of length {}", idx, len),
}
}
pub(crate) fn integer_overflow(span: Span, op: &str) -> NightjarLanguageError {
NightjarLanguageError::IntegerOverflow {
span,
code: ErrorCode::E009,
message: format!("integer overflow in {}", op),
}
}
pub(crate) fn scope_error(span: Span, message: impl Into<String>) -> NightjarLanguageError {
NightjarLanguageError::ScopeError {
span,
code: ErrorCode::E010,
message: message.into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn span_constructors() {
assert_eq!(Span::new(3, 7), Span { start: 3, end: 7 });
assert_eq!(Span::point(5), Span { start: 5, end: 5 });
}
#[test]
fn span_accessor_roundtrip() {
let span = Span::new(10, 20);
let err = parse_error(span, "bad token");
assert_eq!(err.span(), span);
}
#[test]
fn code_accessor_per_variant() {
assert_eq!(parse_error(Span::point(0), "x").code(), ErrorCode::E001);
assert_eq!(type_error(Span::point(0), "x").code(), ErrorCode::E002);
assert_eq!(argument_error(Span::point(0), "x").code(), ErrorCode::E003);
assert_eq!(
symbol_not_found(Span::point(0), ".foo").code(),
ErrorCode::E004
);
assert_eq!(division_by_zero(Span::point(0)).code(), ErrorCode::E006);
assert_eq!(recursion_error(Span::point(0), 256).code(), ErrorCode::E007);
assert_eq!(index_error(Span::point(0), 5, 3).code(), ErrorCode::E008);
assert_eq!(
integer_overflow(Span::point(0), "Add").code(),
ErrorCode::E009
);
assert_eq!(
scope_error(Span::point(0), "@ outside quantifier").code(),
ErrorCode::E010
);
}
#[test]
fn message_accessor() {
let err = type_error(Span::new(0, 3), "bad types");
assert_eq!(err.message(), "bad types");
}
#[test]
fn display_formatting_contains_code_and_span() {
let err = parse_error(Span::new(4, 9), "unexpected token");
let rendered = format!("{}", err);
assert!(rendered.contains("E001"));
assert!(rendered.contains("4"));
assert!(rendered.contains("9"));
assert!(rendered.contains("unexpected token"));
}
#[test]
fn symbol_not_found_formats_path() {
let err = symbol_not_found(Span::point(0), ".data.missing");
assert!(err.message().contains(".data.missing"));
}
#[test]
fn index_out_of_bounds_formats_idx_and_len() {
let err = index_error(Span::point(0), 7, 3);
assert!(err.message().contains('7'));
assert!(err.message().contains('3'));
}
}