use std::fmt;
#[derive(Debug, Clone)]
pub struct IonError {
pub kind: ErrorKind,
pub message: String,
pub line: usize,
pub col: usize,
pub additional: Vec<IonError>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ErrorKind {
LexError,
ParseError,
RuntimeError,
TypeError,
NameError,
PropagatedErr,
PropagatedNone,
}
impl IonError {
pub fn format_with_source(&self, source: &str) -> String {
let mut out = Self::format_single(self, source);
for extra in &self.additional {
out.push('\n');
out.push_str(&Self::format_single(extra, source));
}
out
}
fn format_single(err: &IonError, source: &str) -> String {
let mut out = String::new();
out.push_str(&format!(
"\x1b[1;31merror[{}]\x1b[0m: {}\n",
err.kind_str(),
err.message
));
if err.line > 0 {
let lines: Vec<&str> = source.lines().collect();
if err.line <= lines.len() {
let line_str = lines[err.line - 1];
let line_num = format!("{}", err.line);
let padding = " ".repeat(line_num.len());
out.push_str(&format!(" {} \x1b[34m|\x1b[0m\n", padding));
out.push_str(&format!(" \x1b[34m{} |\x1b[0m {}\n", line_num, line_str));
out.push_str(&format!(" {} \x1b[34m|\x1b[0m ", padding));
if err.col > 0 && err.col <= line_str.len() + 1 {
out.push_str(&" ".repeat(err.col - 1));
out.push_str("\x1b[1;31m^\x1b[0m");
}
out.push('\n');
}
}
if let Some(hint) = Self::suggest_hint(&err.kind, &err.message) {
out.push_str(&format!(" \x1b[1;36mhelp\x1b[0m: {}\n", hint));
}
out
}
fn suggest_hint(kind: &ErrorKind, msg: &str) -> Option<&'static str> {
match kind {
ErrorKind::NameError => {
if msg.contains(&*ion_str!("undefined variable")) {
Some(ion_static_str!("check spelling, or ensure the variable is declared with `let` before use"))
} else {
None
}
}
ErrorKind::TypeError => {
if msg.contains(&*ion_str!("cannot assign to immutable")) {
Some(ion_static_str!("declare with `let mut` to allow reassignment"))
} else if msg.contains(&*ion_str!("cannot add"))
|| msg.contains(&*ion_str!("cannot subtract"))
{
Some(ion_static_str!("Ion has no implicit type coercions \u{2014} convert explicitly with `int()`, `float()`, or `str()`"))
} else if msg.contains(&*ion_str!("no method")) {
Some(ion_static_str!("use `.to_string()` to inspect the value's type, or check LANGUAGE.md for available methods"))
} else {
None
}
}
ErrorKind::ParseError => {
if msg.contains(&*ion_str!("expected ';'")) {
Some(ion_static_str!("Ion requires semicolons after statements"))
} else if msg.contains(&*ion_str!("expected '}'")) {
Some(ion_static_str!("check for unmatched `{` braces"))
} else {
None
}
}
ErrorKind::RuntimeError => {
if msg.contains(&*ion_str!("division by zero")) {
Some(ion_static_str!("check the divisor before dividing, or use a try/catch block"))
} else if msg.contains(&*ion_str!("stack overflow")) {
Some(ion_static_str!("check for infinite recursion, or increase the stack depth limit"))
} else if msg.contains(&*ion_str!("index out of bounds")) {
Some(ion_static_str!("use `.len()` to check the collection size, or `.get()` for safe access"))
} else {
None
}
}
_ => None,
}
}
fn kind_str(&self) -> &str {
match &self.kind {
ErrorKind::LexError => ion_static_str!("lex"),
ErrorKind::ParseError => ion_static_str!("parse"),
ErrorKind::RuntimeError => ion_static_str!("runtime"),
ErrorKind::TypeError => ion_static_str!("type"),
ErrorKind::NameError => ion_static_str!("name"),
ErrorKind::PropagatedErr => ion_static_str!("propagated_err"),
ErrorKind::PropagatedNone => ion_static_str!("propagated_none"),
}
}
pub fn lex(message: impl Into<String>, line: usize, col: usize) -> Self {
Self {
kind: ErrorKind::LexError,
message: message.into(),
line,
col,
additional: Vec::new(),
}
}
pub fn parse(message: impl Into<String>, line: usize, col: usize) -> Self {
Self {
kind: ErrorKind::ParseError,
message: message.into(),
line,
col,
additional: Vec::new(),
}
}
pub fn runtime(message: impl Into<String>, line: usize, col: usize) -> Self {
Self {
kind: ErrorKind::RuntimeError,
message: message.into(),
line,
col,
additional: Vec::new(),
}
}
pub fn type_err(message: impl Into<String>, line: usize, col: usize) -> Self {
Self {
kind: ErrorKind::TypeError,
message: message.into(),
line,
col,
additional: Vec::new(),
}
}
pub fn name(message: impl Into<String>, line: usize, col: usize) -> Self {
Self {
kind: ErrorKind::NameError,
message: message.into(),
line,
col,
additional: Vec::new(),
}
}
pub fn propagated_err(message: impl Into<String>, line: usize, col: usize) -> Self {
Self {
kind: ErrorKind::PropagatedErr,
message: message.into(),
line,
col,
additional: Vec::new(),
}
}
pub fn propagated_none(line: usize, col: usize) -> Self {
Self {
kind: ErrorKind::PropagatedNone,
message: String::new(),
line,
col,
additional: Vec::new(),
}
}
}
impl fmt::Display for IonError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let kind = match &self.kind {
ErrorKind::LexError => ion_str!("LexError"),
ErrorKind::ParseError => ion_str!("ParseError"),
ErrorKind::RuntimeError => ion_str!("RuntimeError"),
ErrorKind::TypeError => ion_str!("TypeError"),
ErrorKind::NameError => ion_str!("NameError"),
ErrorKind::PropagatedErr => ion_str!("PropagatedErr"),
ErrorKind::PropagatedNone => ion_str!("PropagatedNone"),
};
write!(
f,
"{} at {}:{}: {}",
kind, self.line, self.col, self.message
)
}
}
impl std::error::Error for IonError {}