Skip to main content

matugen_parser/
errors.rs

1use std::{cell::RefCell, collections::HashSet};
2
3use ariadne::{Color, Label, Report, ReportKind, Source};
4use chumsky::span::SimpleSpan;
5
6use thiserror::Error as ThisError;
7
8use crate::Engine;
9
10#[derive(Debug, Default)]
11pub struct ErrorCollector {
12    errors: RefCell<Vec<Error>>,
13    seen_spans: RefCell<HashSet<SimpleSpan>>,
14}
15
16impl ErrorCollector {
17    pub fn new() -> Self {
18        Self {
19            errors: RefCell::new(Vec::new()),
20            seen_spans: RefCell::new(HashSet::new()),
21        }
22    }
23
24    pub fn add(&self, error: Error) {
25        let seen = match &error {
26            Error::TemplateNotFound { template: _, .. } => false,
27            Error::ParseError { kind: _, span, .. } => self.seen_spans.borrow().contains(span),
28            Error::ResolveError { span, .. } => self.seen_spans.borrow().contains(span),
29            Error::IncludeError { span, .. } => self.seen_spans.borrow().contains(span),
30        };
31        if !seen {
32            let span = error.get_span();
33            if span.is_some() {
34                self.seen_spans.borrow_mut().insert(span.unwrap());
35            }
36
37            self.errors.borrow_mut().push(error);
38        }
39    }
40
41    pub fn is_empty(&self) -> bool {
42        self.errors.borrow().is_empty()
43    }
44
45    pub fn into_inner(self) -> Vec<Error> {
46        self.errors.into_inner()
47    }
48
49    pub fn take(&self) -> Vec<Error> {
50        let mut errors = self.errors.borrow_mut();
51        let mut taken = Vec::new();
52        std::mem::swap(&mut *errors, &mut taken);
53        self.seen_spans.borrow_mut().clear();
54        taken
55    }
56}
57
58#[derive(ThisError, Debug)]
59pub enum Error {
60    #[error("Could not find template: {template}")]
61    TemplateNotFound { template: String, name: String },
62    #[error("Parse Error: {kind}")]
63    ParseError {
64        kind: ParseErrorKind,
65        span: SimpleSpan,
66        name: String,
67    },
68    #[error("Value does not exist in the context")]
69    ResolveError { span: SimpleSpan, name: String },
70    #[error("Failed to include file")]
71    IncludeError { span: SimpleSpan, name: String },
72}
73
74#[derive(Debug, ThisError)]
75pub enum ParseErrorKind {
76    #[error(transparent)]
77    Filter(#[from] FilterError),
78
79    #[error(transparent)]
80    Keyword(#[from] KeywordError),
81
82    #[error(transparent)]
83    Loop(#[from] LoopError),
84
85    #[error(transparent)]
86    BinOp(#[from] BinaryOperatorError),
87
88    #[error(transparent)]
89    If(#[from] IfError),
90}
91
92#[derive(Debug, ThisError)]
93pub enum IfError {
94    #[error("You can only use if conditions with Booleans")]
95    InvalidIfCondition,
96}
97
98#[derive(Debug, ThisError)]
99pub enum BinaryOperatorError {
100    #[error("Cannot apply '{op}' operator between {lhs} and {rhs}")]
101    InvalidBinaryOperatorType {
102        lhs: String,
103        op: String,
104        rhs: String,
105    },
106}
107
108#[derive(Debug, ThisError)]
109pub enum LoopError {
110    #[error("You can only loop over Arrays, Maps and Colors")]
111    LoopOverNonIterableValue,
112    #[error(
113        "For loop over an Array supports only one variable. Key and value iteration (`<* for key, value in map *>`) is only valid for Maps."
114    )]
115    TooManyLoopVariablesArray,
116    #[error("For loop supports only one or two variables")]
117    TooManyLoopVariables,
118}
119
120#[derive(Debug, ThisError)]
121pub enum KeywordError {
122    #[error("The format provided is not valid. Available formats are: {formats:?}")]
123    InvalidFormat { formats: &'static [&'static str] },
124    #[error("Invalid color mode. The color mode can only be one of: [dark, light, default]")]
125    ColorDoesNotExist,
126    #[error("The format for colors is 'colors.<color>.<scheme>.<format>'")]
127    InvalidColorDefinition,
128}
129
130#[derive(Debug, ThisError)]
131pub enum FilterError {
132    #[error("Not enough arguments provided for filter")]
133    NotEnoughArguments,
134    #[error("Found '{actual}' expected '{expected}'")]
135    InvalidArgumentType {
136        span: SimpleSpan,
137        expected: String,
138        actual: String,
139    },
140    #[error("Cannot use color filters on a string filter, consider using the 'to_color' filter")]
141    ColorFilterOnString,
142    #[error("Cannot use color filters on a boolean value")]
143    ColorFilterOnBool,
144    #[error("Could not find the filter: {filter}")]
145    FilterNotFound { filter: String },
146    #[error("Invalid String, expected one of: [{expected}]")]
147    UnexpectedStringValue { expected: String, span: SimpleSpan },
148    #[error("Invalid format for the 'format' filter, expected one of: {expected:?}")]
149    InvalidFormatString {
150        expected: &'static [&'static str],
151        span: SimpleSpan,
152    },
153    #[error(
154        "You should not use the set_alpha filter with a format that doesn't have an alpha channel. Consider using one of these formats instead: [{replacement}]"
155    )]
156    SetAlphaOnNonAlphaFormat { replacement: &'static str },
157}
158
159impl Error {
160    pub fn get_span(&self) -> Option<SimpleSpan> {
161        match self {
162            Error::TemplateNotFound { template: _, .. } => None,
163            Error::ParseError { kind: _, span, .. } => Some(*span),
164            Error::ResolveError { span, .. } => Some(*span),
165            Error::IncludeError { span, .. } => Some(*span),
166        }
167    }
168
169    pub fn get_name(&self) -> String {
170        match self {
171            Error::TemplateNotFound { .. } => "TemplateNotFound".to_owned(),
172            Error::ParseError { kind, .. } => match kind {
173                ParseErrorKind::Filter(e) => format!("ParseError::{}", e.name()),
174                ParseErrorKind::Keyword(e) => format!("ParseError::{}", e.name()),
175                ParseErrorKind::Loop(e) => format!("ParseError::{}", e.name()),
176                ParseErrorKind::BinOp(e) => format!("ParseError::{}", e.name()),
177                ParseErrorKind::If(e) => format!("ParseError::{}", e.name()),
178            },
179            Error::ResolveError { .. } => "ResolveError".to_owned(),
180            Error::IncludeError { .. } => "IncludeError".to_owned(),
181        }
182    }
183
184    pub fn get_file_name(&self) -> &String {
185        match self {
186            Error::TemplateNotFound { name, .. } => name,
187            Error::ParseError { name, .. } => name,
188            Error::ResolveError { name, .. } => name,
189            Error::IncludeError { name, .. } => name,
190        }
191    }
192
193    pub fn emit(&self, engine: &Engine) -> Result<(), color_eyre::Report> {
194        let name = self.get_name();
195        let message = self.to_string();
196        let span = self.get_span();
197        let file_name = self.get_file_name();
198        let source_code = engine.get_source(&file_name)?;
199
200        if let Some(span) = span {
201            Ok(build_report(&name, source_code, message, span, file_name))
202        } else {
203            Ok(eprintln!("{}", message))
204        }
205    }
206}
207
208impl FilterError {
209    pub fn name(&self) -> &str {
210        match self {
211            FilterError::NotEnoughArguments => "NotEnoughArguments",
212            FilterError::InvalidArgumentType { .. } => "InvalidArgumentType",
213            FilterError::ColorFilterOnString => "ColorFilterOnString",
214            FilterError::ColorFilterOnBool => "ColorFilterOnBool",
215            FilterError::FilterNotFound { .. } => "FilterNotFound",
216            FilterError::UnexpectedStringValue { .. } => "UnexpectedStringValue",
217            FilterError::InvalidFormatString { .. } => "InvalidFormatString",
218            FilterError::SetAlphaOnNonAlphaFormat { .. } => "SetAlphaOnNonAlphaFormat",
219        }
220    }
221}
222
223impl KeywordError {
224    pub fn name(&self) -> &str {
225        match self {
226            KeywordError::InvalidFormat { .. } => "InvalidFormat",
227            KeywordError::ColorDoesNotExist => "ColorDoesNotExist",
228            KeywordError::InvalidColorDefinition => "InvalidColorDefinition",
229        }
230    }
231}
232
233impl LoopError {
234    pub fn name(&self) -> &str {
235        match self {
236            LoopError::LoopOverNonIterableValue => "LoopOverNonIterableValue",
237            LoopError::TooManyLoopVariablesArray => "TooManyLoopVariables",
238            LoopError::TooManyLoopVariables => "TooManyLoopVariables",
239        }
240    }
241}
242
243impl BinaryOperatorError {
244    pub fn name(&self) -> &str {
245        match self {
246            BinaryOperatorError::InvalidBinaryOperatorType { .. } => "InvalidBinaryOperatorType",
247        }
248    }
249}
250
251impl IfError {
252    pub fn name(&self) -> &str {
253        match self {
254            IfError::InvalidIfCondition => "InvalidIfCondition",
255        }
256    }
257}
258
259fn build_report(name: &str, source_code: &str, message: String, span: SimpleSpan, file_name: &str) {
260    Report::build(ReportKind::Error, (file_name, span.into_range()))
261        .with_config(ariadne::Config::default().with_index_type(ariadne::IndexType::Byte))
262        .with_message(name)
263        .with_label(
264            Label::new((file_name, span.into_range()))
265                .with_message(message)
266                .with_color(Color::Red),
267        )
268        .finish()
269        .print((file_name, Source::from(&source_code)))
270        .unwrap();
271}