turtle/interpreter/
exceptions.rs

1use crate::{
2    parser, CallSnapshot, Environment, Expression, Keyword, SourcePosition, Symbol, Value,
3};
4use ansi_term::{Color, Style};
5use std::error::Error;
6use std::fmt;
7
8use crate::Locker;
9
10#[macro_export]
11macro_rules! exp {
12    ($value:expr) => {
13        return Err(Exception::new($value, None, None));
14    };
15    ($value:expr, $snapshot:expr) => {
16        return Err(Exception::new($value, Some($snapshot.clone()), None));
17    };
18    ($value:expr, $snapshot:expr, $note:expr) => {
19        return Err(Exception::new($value, Some($snapshot.clone()), Some($note)));
20    };
21}
22
23#[macro_export]
24macro_rules! exp_opt {
25    ($value:expr $(, $rest:expr)*) => {
26        match $value {
27            Some(value) => value,
28            None => exp!($($rest)*)
29        }
30    };
31}
32
33#[macro_export]
34macro_rules! exp_assert {
35    ($test:expr $(, $rest:expr)*) => {
36        if (!$test) {
37            exp!($($rest),*);
38        }
39    };
40}
41
42#[derive(Debug, Clone)]
43pub enum ExceptionValue {
44    Other(Expression),
45    UndefinedSymbol(Symbol),
46    ArgumentMismatch(usize, String),
47    InvalidArgument,
48    Syntax,
49    InvalidIncludePath(String),
50    InvalidOperator(Value),
51    StackOverflow,
52    Assignment(Symbol, Expression),
53    Concurrency,
54}
55
56impl ExceptionValue {
57    pub fn explain(&self) -> String {
58        use ExceptionValue::*;
59
60        match self {
61            Other(exp) => format!("{}", exp),
62            UndefinedSymbol(symbol) => format!(
63                "the symbol `{}` has no assigned value (did you mean to quote this symbol?)",
64                symbol
65            ),
66            ArgumentMismatch(given, expected) => format!(
67                "wrong number of arguments: {} required, but {} given",
68                expected, given,
69            ),
70            InvalidArgument => String::from("the arguments to this function are invalid"),
71            Syntax => String::from("the syntax of this code is incorrect"),
72            InvalidIncludePath(path) => format!("no code is available for import from `{}`", path),
73            InvalidOperator(value) => format!(
74                "`{}` is not a valid list operator (did you mean to quote this list?)",
75                value
76            ),
77            StackOverflow => "the call stack exceeded the limit (1000)".to_string(),
78            Assignment(sym, exp) => format!("could not assign `{}` to `{}`", sym, exp),
79            Concurrency => {
80                "something went wrong when evaluating this expression concurrently".to_string()
81            }
82        }
83    }
84
85    pub fn into_expression(self) -> Expression {
86        use ExceptionValue::*;
87
88        let _root_env = Locker::new(Environment::root());
89
90        match self {
91            Other(expression) => expression,
92            UndefinedSymbol(_) => {
93                Expression::new(Value::Keyword(Keyword::from_str("undefined-symbol-exp")))
94            }
95            ArgumentMismatch(_, _) => {
96                Expression::new(Value::Keyword(Keyword::from_str("argument-mismatch-exp")))
97            }
98            Syntax => Expression::new(Value::Keyword(Keyword::from_str("syntax-exp"))),
99            InvalidArgument => {
100                Expression::new(Value::Keyword(Keyword::from_str("invalid-argument-exp")))
101            }
102            InvalidIncludePath(_) => Expression::new(Value::Keyword(Keyword::from_str(
103                "invalid-include-path-exp",
104            ))),
105            InvalidOperator(_) => {
106                Expression::new(Value::Keyword(Keyword::from_str("invalid-operator-exp")))
107            }
108            StackOverflow => {
109                Expression::new(Value::Keyword(Keyword::from_str("stack-overflow-exp")))
110            }
111            Assignment(_, _) => {
112                Expression::new(Value::Keyword(Keyword::from_str("assignment-exp")))
113            }
114            Concurrency => Expression::new(Value::Keyword(Keyword::from_str("concurrency-exp"))),
115        }
116    }
117}
118
119impl fmt::Display for ExceptionValue {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        write!(f, "{} ({})", self.explain(), self.clone().into_expression())
122    }
123}
124
125#[derive(Debug, Clone)]
126pub struct Exception {
127    value: ExceptionValue,
128    snapshot: Option<Locker<CallSnapshot>>,
129    additional_sources: Vec<SourcePosition>,
130    note: Option<String>,
131}
132
133impl Exception {
134    pub fn new(
135        value: ExceptionValue,
136        snapshot: Option<Locker<CallSnapshot>>,
137        note: Option<String>,
138    ) -> Self {
139        Exception {
140            value,
141            snapshot,
142            note,
143            additional_sources: vec![],
144        }
145    }
146
147    pub fn into_value(self) -> ExceptionValue {
148        self.value
149    }
150}
151
152impl From<pest::error::Error<parser::Rule>> for Exception {
153    fn from(err: pest::error::Error<parser::Rule>) -> Self {
154        use pest::error::InputLocation::*;
155
156        let (_start, _end) = match err.location {
157            Pos(start) => (start, start),
158            Span((start, end)) => (start, end),
159        };
160
161        Self {
162            value: ExceptionValue::Syntax,
163            snapshot: None,
164            note: Some(format!("{}", err)),
165            // TODO: find a nice way to extract the text-level information
166            additional_sources: vec![],
167        }
168    }
169}
170
171impl fmt::Display for Exception {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        writeln!(
174            f,
175            "{}{}{} {}",
176            Color::Red.bold().paint("error"),
177            Color::Blue.bold().paint(" ┬ "),
178            Style::new().paint("uncaught exception"),
179            Color::Yellow.paint(format!("{}", self.value.clone().into_expression()))
180        )?;
181
182        match &self.snapshot {
183            Some(snapshot_lock) => match snapshot_lock.read() {
184                Ok(snapshot) => write!(f, "{}", snapshot)?,
185                Err(_) => {
186                    write!(
187                        f,
188                        "{}{}",
189                        Color::Yellow.bold().paint("warning"),
190                        Style::new()
191                            .bold()
192                            .paint(": unable to access execution snapshot (are threads locked?)")
193                    )?;
194                }
195            },
196            None => {}
197        };
198
199        for addl_source in &self.additional_sources {
200            write!(f, "{}", addl_source)?;
201        }
202
203        write!(
204            f,
205            "      {}{}",
206            Color::Blue.bold().paint("└ "),
207            Style::new().bold().paint(self.value.explain()),
208        )?;
209
210        match &self.note {
211            Some(note) => write!(
212                f,
213                "\n        {} {}",
214                Style::new().dimmed().paint("note:"),
215                note
216            ),
217            None => write!(f, ""),
218        }
219    }
220}
221
222impl Error for Exception {}