conjure_cp_essence_parser/
errors.rs1pub use conjure_cp_core::error::Error as ConjureParseError;
2use conjure_cp_core::error::Error;
3use serde_json::Error as JsonError;
4use thiserror::Error as ThisError;
5
6#[derive(Debug, ThisError)]
7pub enum FatalParseError {
8 #[error("Failed to read Essence file `{path}`: {source}")]
9 FileRead {
10 path: String,
11 #[source]
12 source: std::io::Error,
13 },
14 #[error("Could not parse Essence AST: {0}")]
15 TreeSitterError(String),
16 #[error("Error running `conjure pretty`: {0}")]
17 ConjurePrettyError(String),
18 #[error("Internal parser error: {msg}{}\nThis indicates a bug in the parser or syntax validator. Please report this issue.",
19 match range {
20 Some(range) => format!(" at {}-{}", range.start_point, range.end_point),
21 None => "".to_string(),
22 }
23 )]
24 InternalError {
25 msg: String,
26 range: Option<tree_sitter::Range>,
27 },
28 #[error("JSON Error: {0}")]
29 JsonError(#[from] JsonError),
30 #[error("Error: {0} is not yet implemented.")]
31 NotImplemented(String),
32 #[error("Error: {0}")]
33 Other(Error),
34}
35
36impl FatalParseError {
37 pub fn internal_error(msg: String, range: Option<tree_sitter::Range>) -> Self {
38 FatalParseError::InternalError { msg, range }
39 }
40}
41
42impl From<ConjureParseError> for FatalParseError {
43 fn from(value: ConjureParseError) -> Self {
44 match value {
45 Error::Parse(msg) => FatalParseError::internal_error(msg, None),
46 Error::NotImplemented(msg) => FatalParseError::NotImplemented(msg),
47 Error::Json(err) => FatalParseError::JsonError(err),
48 Error::Other(err) => FatalParseError::Other(err.into()),
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
54pub struct RecoverableParseError {
55 pub msg: String,
56 pub range: Option<tree_sitter::Range>,
57 pub file_name: Option<String>,
58 pub source_code: Option<String>,
59}
60
61impl RecoverableParseError {
62 pub fn new(msg: String, range: Option<tree_sitter::Range>) -> Self {
63 Self {
64 msg,
65 range,
66 file_name: None,
67 source_code: None,
68 }
69 }
70
71 pub fn enrich(mut self, file_name: Option<String>, source_code: Option<String>) -> Self {
72 self.file_name = file_name;
73 self.source_code = source_code;
74 self
75 }
76}
77
78impl std::fmt::Display for RecoverableParseError {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 if let (Some(range), Some(file_name), Some(source_code)) =
82 (&self.range, &self.file_name, &self.source_code)
83 {
84 let line_num = range.start_point.row + 1; let col_num = range.start_point.column + 1; let lines: Vec<&str> = source_code.lines().collect();
89 let line_content = lines.get(range.start_point.row).unwrap_or(&"");
90
91 let pointer = " ".repeat(range.start_point.column) + "^";
93
94 write!(
95 f,
96 "{}:{}:{}:\n |\n{} | {}\n | {}\n{}",
97 file_name, line_num, col_num, line_num, line_content, pointer, self.msg
98 )
99 } else {
100 write!(f, "Essence syntax error: {}", self.msg)?;
102 if let Some(range) = &self.range {
103 write!(f, " at {}-{}", range.start_point, range.end_point)?;
104 }
105 Ok(())
106 }
107 }
108}
109
110#[derive(Debug)]
112pub struct InstantiateModelError {
113 pub msg: String,
114}
115
116impl std::fmt::Display for InstantiateModelError {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 write!(f, "{}", self.msg)
119 }
120}
121
122impl std::error::Error for InstantiateModelError {}
123#[derive(Debug)]
125pub enum ParseErrorCollection {
126 Fatal(FatalParseError),
128 Multiple {
130 errors: Vec<RecoverableParseError>,
131 },
132
133 InstantiateModel(InstantiateModelError),
134}
135
136impl ParseErrorCollection {
137 pub fn fatal(error: FatalParseError) -> Self {
139 ParseErrorCollection::Fatal(error)
140 }
141
142 pub fn multiple(
145 errors: Vec<RecoverableParseError>,
146 source_code: Option<String>,
147 file_name: Option<String>,
148 ) -> Self {
149 let enriched_errors = errors
150 .into_iter()
151 .map(|err| err.enrich(file_name.clone(), source_code.clone()))
152 .collect();
153 ParseErrorCollection::Multiple {
154 errors: enriched_errors,
155 }
156 }
157}
158
159impl std::fmt::Display for ParseErrorCollection {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 match self {
162 ParseErrorCollection::Fatal(error) => write!(f, "{}", error),
163 ParseErrorCollection::InstantiateModel(error) => write!(f, "{}", error),
164 ParseErrorCollection::Multiple { errors } => {
165 let mut indices: Vec<usize> = (0..errors.len()).collect();
167 indices.sort_by(|&a, &b| {
168 match (&errors[a], &errors[b]) {
169 (
170 RecoverableParseError {
171 range: Some(r1), ..
172 },
173 RecoverableParseError {
174 range: Some(r2), ..
175 },
176 ) => {
177 match r1.start_point.row.cmp(&r2.start_point.row) {
179 std::cmp::Ordering::Equal => {
180 r1.start_point.column.cmp(&r2.start_point.column)
181 }
182 other => other,
183 }
184 }
185 (RecoverableParseError { range: Some(_), .. }, _) => {
187 std::cmp::Ordering::Less
188 }
189 (_, RecoverableParseError { range: Some(_), .. }) => {
190 std::cmp::Ordering::Greater
191 }
192 _ => std::cmp::Ordering::Equal,
193 }
194 });
195
196 for (i, &idx) in indices.iter().enumerate() {
198 if i > 0 {
199 write!(f, "\n\n")?;
200 }
201 write!(f, "{}", errors[idx])?;
202 }
203 Ok(())
204 }
205 }
206 }
207}
208
209impl std::error::Error for ParseErrorCollection {}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn instantiate_model_error_display_and_error_trait() {
217 let err = InstantiateModelError {
218 msg: "hello".to_string(),
219 };
220
221 assert_eq!(err.to_string(), "hello");
222 let _as_error: &dyn std::error::Error = &err;
223 }
224
225 #[test]
226 fn parse_error_collection_instantiate_model_variant_is_displayed() {
227 let err = ParseErrorCollection::InstantiateModel(InstantiateModelError {
228 msg: "missing param".to_string(),
229 });
230 assert_eq!(err.to_string(), "missing param");
231 }
232
233 #[test]
234 fn parse_error_collection_multiple_constructor_works() {
235 let err = ParseErrorCollection::multiple(
236 vec![RecoverableParseError::new("bad token".to_string(), None)],
237 None,
238 None,
239 );
240 let formatted = err.to_string();
241 assert!(formatted.contains("bad token"));
242 }
243}