1
pub use conjure_cp_core::error::Error as ConjureParseError;
2
use conjure_cp_core::error::Error;
3
use serde_json::Error as JsonError;
4
use thiserror::Error as ThisError;
5

            
6
#[derive(Debug, ThisError)]
7
pub 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

            
36
impl FatalParseError {
37
5197220
    pub fn internal_error(msg: String, range: Option<tree_sitter::Range>) -> Self {
38
5197220
        FatalParseError::InternalError { msg, range }
39
5197220
    }
40
}
41

            
42
impl From<ConjureParseError> for FatalParseError {
43
74
    fn from(value: ConjureParseError) -> Self {
44
74
        match value {
45
74
            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
74
    }
51
}
52

            
53
#[derive(Debug, Clone)]
54
pub 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

            
61
impl RecoverableParseError {
62
3156
    pub fn new(msg: String, range: Option<tree_sitter::Range>) -> Self {
63
3156
        Self {
64
3156
            msg,
65
3156
            range,
66
3156
            file_name: None,
67
3156
            source_code: None,
68
3156
        }
69
3156
    }
70

            
71
1905
    pub fn enrich(mut self, file_name: Option<String>, source_code: Option<String>) -> Self {
72
1905
        self.file_name = file_name;
73
1905
        self.source_code = source_code;
74
1905
        self
75
1905
    }
76
}
77

            
78
impl std::fmt::Display for RecoverableParseError {
79
1905
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80
        // If we have all the info, format nicely with source context
81
1905
        if let (Some(range), Some(file_name), Some(source_code)) =
82
1905
            (&self.range, &self.file_name, &self.source_code)
83
        {
84
1904
            let line_num = range.start_point.row + 1; // tree-sitter uses 0-indexed rows
85
1904
            let col_num = range.start_point.column + 1; // tree-sitter uses 0-indexed columns
86

            
87
            // Get the specific line from source code
88
1904
            let lines: Vec<&str> = source_code.lines().collect();
89
1904
            let line_content = lines.get(range.start_point.row).unwrap_or(&"");
90

            
91
            // Build the pointer line (spaces + ^)
92
1904
            let pointer = " ".repeat(range.start_point.column) + "^";
93

            
94
1904
            write!(
95
1904
                f,
96
                "{}:{}:{}:\n  |\n{} | {}\n  | {}\n{}",
97
1904
                file_name, line_num, col_num, line_num, line_content, pointer, self.msg
98
            )
99
        } else {
100
            // Fall back to simple format without context
101
1
            write!(f, "Essence syntax error: {}", self.msg)?;
102
1
            if let Some(range) = &self.range {
103
                write!(f, " at {}-{}", range.start_point, range.end_point)?;
104
1
            }
105
1
            Ok(())
106
        }
107
1905
    }
108
}
109

            
110
/// Error type for issues during model instantiation (when applying parameters to a problem model).
111
#[derive(Debug)]
112
pub struct InstantiateModelError {
113
    pub msg: String,
114
}
115

            
116
impl std::fmt::Display for InstantiateModelError {
117
50
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118
50
        write!(f, "{}", self.msg)
119
50
    }
120
}
121

            
122
impl std::error::Error for InstantiateModelError {}
123
/// Collection of parse errors
124
#[derive(Debug)]
125
pub enum ParseErrorCollection {
126
    /// A single fatal error that stops parsing entirely
127
    Fatal(FatalParseError),
128
    /// Multiple recoverable errors accumulated during parsing
129
    Multiple {
130
        errors: Vec<RecoverableParseError>,
131
    },
132

            
133
    InstantiateModel(InstantiateModelError),
134
}
135

            
136
impl ParseErrorCollection {
137
    /// Create a fatal error collection from a single fatal error
138
1900
    pub fn fatal(error: FatalParseError) -> Self {
139
1900
        ParseErrorCollection::Fatal(error)
140
1900
    }
141

            
142
    /// Create a multiple error collection from recoverable errors
143
    /// This enriches all errors with file_name and source_code
144
1541
    pub fn multiple(
145
1541
        errors: Vec<RecoverableParseError>,
146
1541
        source_code: Option<String>,
147
1541
        file_name: Option<String>,
148
1541
    ) -> Self {
149
1541
        let enriched_errors = errors
150
1541
            .into_iter()
151
1905
            .map(|err| err.enrich(file_name.clone(), source_code.clone()))
152
            .collect();
153
1541
        ParseErrorCollection::Multiple {
154
1541
            errors: enriched_errors,
155
1541
        }
156
1541
    }
157
}
158

            
159
impl std::fmt::Display for ParseErrorCollection {
160
3490
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161
3490
        match self {
162
1900
            ParseErrorCollection::Fatal(error) => write!(f, "{}", error),
163
49
            ParseErrorCollection::InstantiateModel(error) => write!(f, "{}", error),
164
            ParseErrorCollection::Multiple { errors } => {
165
                // Create indices sorted by line and column
166
1541
                let mut indices: Vec<usize> = (0..errors.len()).collect();
167
1541
                indices.sort_by(|&a, &b| {
168
364
                    match (&errors[a], &errors[b]) {
169
                        (
170
                            RecoverableParseError {
171
                                range: Some(r1), ..
172
                            },
173
                            RecoverableParseError {
174
                                range: Some(r2), ..
175
                            },
176
                        ) => {
177
                            // Compare by row first, then by column
178
364
                            match r1.start_point.row.cmp(&r2.start_point.row) {
179
                                std::cmp::Ordering::Equal => {
180
148
                                    r1.start_point.column.cmp(&r2.start_point.column)
181
                                }
182
216
                                other => other,
183
                            }
184
                        }
185
                        // Errors without ranges go last
186
                        (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
364
                });
195

            
196
                // Print out each error using Display
197
1541
                for (i, &idx) in indices.iter().enumerate() {
198
1905
                    if i > 0 {
199
364
                        write!(f, "\n\n")?;
200
1541
                    }
201
1905
                    write!(f, "{}", errors[idx])?;
202
                }
203
1541
                Ok(())
204
            }
205
        }
206
3490
    }
207
}
208

            
209
impl std::error::Error for ParseErrorCollection {}
210

            
211
#[cfg(test)]
212
mod tests {
213
    use super::*;
214

            
215
    #[test]
216
1
    fn instantiate_model_error_display_and_error_trait() {
217
1
        let err = InstantiateModelError {
218
1
            msg: "hello".to_string(),
219
1
        };
220

            
221
1
        assert_eq!(err.to_string(), "hello");
222
1
        let _as_error: &dyn std::error::Error = &err;
223
1
    }
224

            
225
    #[test]
226
1
    fn parse_error_collection_instantiate_model_variant_is_displayed() {
227
1
        let err = ParseErrorCollection::InstantiateModel(InstantiateModelError {
228
1
            msg: "missing param".to_string(),
229
1
        });
230
1
        assert_eq!(err.to_string(), "missing param");
231
1
    }
232

            
233
    #[test]
234
1
    fn parse_error_collection_multiple_constructor_works() {
235
1
        let err = ParseErrorCollection::multiple(
236
1
            vec![RecoverableParseError::new("bad token".to_string(), None)],
237
1
            None,
238
1
            None,
239
1
        );
240
1
        let formatted = err.to_string();
241
1
        assert!(formatted.contains("bad token"));
242
1
    }
243
}