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 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 {}