use crate::lisp::{ expr::{Arity, LispExpr}, lex::{Span, SpanDisplay}, number::LispNumber, }; use std::{fmt, io}; #[derive(Debug)] pub enum LispError { Parse(ParseError), Eval(EvalError), Stringified(String), } impl std::error::Error for LispError {} impl fmt::Display for LispError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Parse(p) => write!(f, "parse error: {}", p.kind), Self::Eval(e) => write!(f, "eval error: {}", e), Self::Stringified(s) => write!(f, "{}", s), } } } #[derive(Debug, PartialEq, Clone)] pub struct ParseError { pub span: Span, pub kind: ParseErrorKind, } impl ParseError { pub fn new(span: Span, kind: ParseErrorKind) -> Self { Self { span, kind } } pub fn display(&self, text: &str, file_name: &str) -> String { let SpanDisplay { line, col, .. } = SpanDisplay::highlight_span(self.span, text, file_name); format!( "in file `{}`; line {}, col {}: {}", file_name, line, col, self.kind ) } } #[derive(Debug, PartialEq, Clone)] pub enum ParseErrorKind { InvalidLiteral, InvalidToken, InvalidChar(char), LiteralParseError, MissingCloseParen, UnbalancedComma, UnexpectedEof, UnexpectedToken { expected: &'static str, found: &'static str, }, UnmatchedParen, UnterminatedString, } impl fmt::Display for ParseErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ParseErrorKind::InvalidChar(ch) => write!(f, "invalid character {}", ch), ParseErrorKind::InvalidToken => write!(f, "invalid token"), ParseErrorKind::UnexpectedEof => write!(f, "unexpected end of file"), ParseErrorKind::InvalidLiteral => write!(f, "invalid literal"), ParseErrorKind::UnmatchedParen => write!(f, "unmatched `)`"), ParseErrorKind::UnbalancedComma => write!(f, "unbalanced comma"), ParseErrorKind::UnexpectedToken { expected, found } => { write!( f, "unexpected token, got `{}`, expected `{}`", found, expected ) } ParseErrorKind::LiteralParseError => write!(f, "error parsing literal"), ParseErrorKind::MissingCloseParen => write!(f, "missing `)`"), ParseErrorKind::UnterminatedString => write!(f, "unterminated string literal"), } } } impl From for LispError { fn from(p: ParseError) -> Self { LispError::Parse(p) } } #[derive(Debug)] pub enum EvalError { ArgumentCount(Arity), BadForm, UnboundVariable(String), DivByZero, TypeMismatch, NoFileName, FileError(io::Error), AccessEmptyList, InvalidCoordinates((LispNumber, LispNumber)), AssertionError { expected: LispExpr, got: LispExpr }, ScriptLoadError(io::Error), CustomInternal(&'static str), Custom(String), } impl fmt::Display for EvalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ArgumentCount(i) => { write!(f, "invalid number of arguments, expected ")?; match i { Arity::Exact(a) => write!(f, "exactly {} argument(s)", a), Arity::Atleast(a) => write!(f, "atleast {} argument(s)", a), Arity::Atmost(a) => write!(f, "atmost {} argument(s)", a), Arity::Range(low, high) => { write!(f, "between {} and {} argument(s)", low, high) } Arity::None => write!(f, "any number of arguments"), } } Self::BadForm => write!(f, "bad expression form"), Self::UnboundVariable(s) => write!(f, "unbound variable {}", s), Self::TypeMismatch => write!(f, "mismatched types"), Self::DivByZero => write!(f, "attempt to divide by zero"), Self::NoFileName => write!(f, "no file name specified"), Self::FileError(e) => write!(f, "file error: {}", e), Self::AccessEmptyList => write!(f, "attempted to access empty list"), Self::InvalidCoordinates((x, y)) => write!(f, "invalid coordinates: {} {}", x, y), Self::AssertionError { expected, got } => { write!(f, "assertion error: expected `{}` got `{}`", expected, got) } Self::ScriptLoadError(s) => write!(f, "error while loading script: {}", s), Self::CustomInternal(s) => write!(f, "{}", s), Self::Custom(s) => write!(f, "error: {}", s), } } } impl From for LispError { fn from(e: EvalError) -> Self { LispError::Eval(e) } }