calyx_utils/
errors.rs

1//! Errors generated by the compiler.
2use itertools::Itertools;
3
4use crate::{GPosIdx, Id, WithPos};
5
6/// Convience wrapper to represent success or meaningul compiler error.
7pub type CalyxResult<T> = std::result::Result<T, Error>;
8
9/// Errors generated by the compiler
10#[derive(Clone)]
11pub struct Error {
12    kind: Box<ErrorKind>,
13    pos: GPosIdx,
14    annotations: Vec<(GPosIdx, String)>,
15    post_msg: Option<String>,
16}
17
18/// A collection of errors generated by the compiler
19pub struct MultiError {
20    errors: Vec<Error>,
21}
22
23impl From<Vec<Error>> for MultiError {
24    fn from(errors: Vec<Error>) -> Self {
25        MultiError { errors }
26    }
27}
28
29impl std::fmt::Debug for Error {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        if self.pos == GPosIdx::UNKNOWN {
32            write!(f, "{}", self.kind)?
33        } else {
34            write!(f, "{}", self.pos.format(self.kind.to_string()))?;
35            for (other_pos, msg) in &self.annotations {
36                write!(f, "\n...\n{}", other_pos.format_raw(msg))?;
37            }
38        }
39        if let Some(post) = &self.post_msg {
40            write!(f, "\n{post}")?;
41        }
42        Ok(())
43    }
44}
45
46impl std::fmt::Debug for MultiError {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        let errors = self.errors.iter().map(|e| format!("{e:?}")).join("\n\n");
49        write!(f, "{errors}")
50    }
51}
52
53impl Error {
54    pub fn with_pos<T: WithPos>(mut self, pos: &T) -> Self {
55        self.pos = pos.copy_span();
56        self
57    }
58
59    pub fn with_annotation<T: WithPos, S: ToString>(
60        mut self,
61        pos: &T,
62        msg: S,
63    ) -> Self {
64        self.annotations.push((pos.copy_span(), msg.to_string()));
65        self
66    }
67
68    pub fn with_annotations<T: WithPos, S: ToString>(
69        mut self,
70        pos_iter: impl Iterator<Item = (T, S)>,
71    ) -> Self {
72        self.annotations.extend(
73            pos_iter.map(|(pos, msg)| (pos.copy_span(), msg.to_string())),
74        );
75        self
76    }
77
78    pub fn with_post_msg(mut self, msg: Option<String>) -> Self {
79        self.post_msg = msg;
80        self
81    }
82
83    pub fn reserved_name(name: Id) -> Self {
84        Self {
85            kind: Box::new(ErrorKind::ReservedName(name)),
86            pos: GPosIdx::UNKNOWN,
87            annotations: vec![],
88            post_msg: None,
89        }
90    }
91    pub fn malformed_control<S: ToString>(msg: S) -> Self {
92        Self {
93            kind: Box::new(ErrorKind::MalformedControl(msg.to_string())),
94            pos: GPosIdx::UNKNOWN,
95            annotations: vec![],
96            post_msg: None,
97        }
98    }
99    pub fn malformed_structure<S: ToString>(msg: S) -> Self {
100        Self {
101            kind: Box::new(ErrorKind::MalformedStructure(msg.to_string())),
102            pos: GPosIdx::UNKNOWN,
103            annotations: vec![],
104            post_msg: None,
105        }
106    }
107    pub fn pass_assumption<S: ToString, M: ToString>(pass: S, msg: M) -> Self {
108        Self {
109            kind: Box::new(ErrorKind::PassAssumption(
110                pass.to_string(),
111                msg.to_string(),
112            )),
113            pos: GPosIdx::UNKNOWN,
114            annotations: vec![],
115            post_msg: None,
116        }
117    }
118    pub fn undefined<S: ToString>(name: Id, typ: S) -> Self {
119        Self {
120            kind: Box::new(ErrorKind::Undefined(name, typ.to_string())),
121            pos: GPosIdx::UNKNOWN,
122            annotations: vec![],
123            post_msg: None,
124        }
125    }
126    pub fn already_bound<S: ToString>(name: Id, typ: S) -> Self {
127        Self {
128            kind: Box::new(ErrorKind::AlreadyBound(name, typ.to_string())),
129            pos: GPosIdx::UNKNOWN,
130            annotations: vec![],
131            post_msg: None,
132        }
133    }
134    pub fn unused<S: ToString>(group: Id, typ: S) -> Self {
135        Self {
136            kind: Box::new(ErrorKind::Unused(group, typ.to_string())),
137            pos: GPosIdx::UNKNOWN,
138            annotations: vec![],
139            post_msg: None,
140        }
141    }
142    pub fn papercut<S: ToString>(msg: S) -> Self {
143        Self {
144            kind: Box::new(ErrorKind::Papercut(msg.to_string())),
145            pos: GPosIdx::UNKNOWN,
146            annotations: vec![],
147            post_msg: None,
148        }
149    }
150    pub fn misc<S: ToString>(msg: S) -> Self {
151        Self {
152            kind: Box::new(ErrorKind::Misc(msg.to_string())),
153            pos: GPosIdx::UNKNOWN,
154            annotations: vec![],
155            post_msg: None,
156        }
157    }
158    pub fn parse_error<S: ToString>(msg: S) -> Self {
159        Self {
160            kind: Box::new(ErrorKind::Parse),
161            pos: GPosIdx::UNKNOWN,
162            annotations: vec![],
163            post_msg: Some(msg.to_string()),
164        }
165    }
166    pub fn invalid_file<S: ToString>(msg: S) -> Self {
167        Self {
168            kind: Box::new(ErrorKind::InvalidFile(msg.to_string())),
169            pos: GPosIdx::UNKNOWN,
170            annotations: vec![],
171            post_msg: None,
172        }
173    }
174    pub fn write_error<S: ToString>(msg: S) -> Self {
175        Self {
176            kind: Box::new(ErrorKind::WriteError(msg.to_string())),
177            pos: GPosIdx::UNKNOWN,
178            annotations: vec![],
179            post_msg: None,
180        }
181    }
182    pub fn location(&self) -> (&str, usize, usize) {
183        self.pos.get_location()
184    }
185    pub fn message(&self) -> String {
186        self.kind.to_string()
187    }
188    pub fn annotations(&self) -> Vec<(String, usize, usize)> {
189        self.annotations
190            .iter()
191            .map(|(pos, msg)| {
192                let (_, s, e) = pos.get_location();
193                (msg.to_string(), s, e)
194            })
195            .collect()
196    }
197}
198
199/// Standard error type for Calyx errors.
200#[derive(Clone)]
201enum ErrorKind {
202    /// Using a reserved keyword as a program identifier.
203    ReservedName(Id),
204
205    /// The control program is malformed.
206    MalformedControl(String),
207    /// The connections are malformed.
208    MalformedStructure(String),
209
210    /// Requirement of a pass was not satisfied
211    PassAssumption(String, String),
212
213    /// The name has not been bound
214    Undefined(Id, String),
215    /// The name has already been bound.
216    AlreadyBound(Id, String),
217
218    /// The group was not used in the program.
219    Unused(Id, String),
220
221    /// Papercut error: signals a commonly made mistake in Calyx program.
222    Papercut(String),
223
224    // =========== Frontend Errors ===============
225    /// Parse error
226    Parse,
227    /// Miscellaneous error message
228    Misc(String),
229    /// The input file is invalid (does not exist).
230    InvalidFile(String),
231    /// Failed to write the output
232    WriteError(String),
233}
234
235impl std::fmt::Display for ErrorKind {
236    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
237        use ErrorKind::*;
238        match self {
239            Papercut(msg) => {
240                write!(f, "[Papercut] {msg}")
241            }
242            Unused(name, typ) => {
243                write!(f, "Unused {typ} `{name}'")
244            }
245            AlreadyBound(name, bound_by) => {
246                write!(f, "Name `{name}' already bound by {bound_by}")
247            }
248            ReservedName(name) => {
249                write!(f, "Use of reserved keyword: {name}")
250            }
251            Undefined(name, typ) => {
252                write!(f, "Undefined {typ} name: {name}")
253            }
254            MalformedControl(msg) => write!(f, "Malformed Control: {msg}"),
255            PassAssumption(pass, msg) => {
256                write!(f, "Pass `{pass}` assumption violated: {msg}")
257            }
258            MalformedStructure(msg) => {
259                write!(f, "Malformed Structure: {msg}")
260            }
261            Parse => {
262                write!(f, "Parse error")
263            }
264            InvalidFile(msg) | WriteError(msg) | Misc(msg) => {
265                write!(f, "{msg}")
266            }
267        }
268    }
269}
270
271// Conversions from other error types to our error type so that
272// we can use `?` in all the places.
273impl From<std::str::Utf8Error> for Error {
274    fn from(err: std::str::Utf8Error) -> Self {
275        Error::invalid_file(err.to_string())
276    }
277}
278
279impl From<std::io::Error> for Error {
280    fn from(e: std::io::Error) -> Self {
281        Error::write_error(format!("IO Error: {e}"))
282    }
283}
284
285impl From<serde_json::Error> for Error {
286    fn from(e: serde_json::Error) -> Self {
287        Error::write_error(format!("serde_json Error: {e}"))
288    }
289}
290
291impl From<std::str::Utf8Error> for MultiError {
292    fn from(value: std::str::Utf8Error) -> Self {
293        MultiError {
294            errors: vec![value.into()],
295        }
296    }
297}
298
299impl From<std::io::Error> for MultiError {
300    fn from(value: std::io::Error) -> Self {
301        MultiError {
302            errors: vec![value.into()],
303        }
304    }
305}
306
307impl From<serde_json::Error> for MultiError {
308    fn from(value: serde_json::Error) -> Self {
309        MultiError {
310            errors: vec![value.into()],
311        }
312    }
313}
314
315impl From<Error> for MultiError {
316    fn from(value: Error) -> Self {
317        MultiError {
318            errors: vec![value],
319        }
320    }
321}