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("Could not parse Essence AST: {0}")]
9
    TreeSitterError(String),
10
    #[error("Error running `conjure pretty`: {0}")]
11
    ConjurePrettyError(String),
12
    #[error("Internal parser error: {msg}{}\nThis indicates a bug in the parser or syntax validator. Please report this issue.",
13
        match range {
14
            Some(range) => format!(" at {}-{}", range.start_point, range.end_point),
15
            None => "".to_string(),
16
        }
17
    )]
18
    InternalError {
19
        msg: String,
20
        range: Option<tree_sitter::Range>,
21
    },
22
    #[error("JSON Error: {0}")]
23
    JsonError(#[from] JsonError),
24
    #[error("Error: {0} is not yet implemented.")]
25
    NotImplemented(String),
26
    #[error("Error: {0}")]
27
    Other(Error),
28
}
29

            
30
impl FatalParseError {
31
32953
    pub fn internal_error(msg: String, range: Option<tree_sitter::Range>) -> Self {
32
32953
        FatalParseError::InternalError { msg, range }
33
32953
    }
34
}
35

            
36
impl From<ConjureParseError> for FatalParseError {
37
28
    fn from(value: ConjureParseError) -> Self {
38
28
        match value {
39
28
            Error::Parse(msg) => FatalParseError::internal_error(msg, None),
40
            Error::NotImplemented(msg) => FatalParseError::NotImplemented(msg),
41
            Error::Json(err) => FatalParseError::JsonError(err),
42
            Error::Other(err) => FatalParseError::Other(err.into()),
43
        }
44
28
    }
45
}
46

            
47
#[derive(Debug, Clone)]
48
pub struct RecoverableParseError {
49
    pub msg: String,
50
    pub range: Option<tree_sitter::Range>,
51
    pub file_name: Option<String>,
52
    pub source_code: Option<String>,
53
}
54

            
55
impl RecoverableParseError {
56
1674
    pub fn new(msg: String, range: Option<tree_sitter::Range>) -> Self {
57
1674
        Self {
58
1674
            msg,
59
1674
            range,
60
1674
            file_name: None,
61
1674
            source_code: None,
62
1674
        }
63
1674
    }
64

            
65
928
    pub fn enrich(mut self, file_name: Option<String>, source_code: Option<String>) -> Self {
66
928
        self.file_name = file_name;
67
928
        self.source_code = source_code;
68
928
        self
69
928
    }
70
}
71

            
72
impl std::fmt::Display for RecoverableParseError {
73
928
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74
        // If we have all the info, format nicely with source context
75
927
        if let (Some(range), Some(file_name), Some(source_code)) =
76
928
            (&self.range, &self.file_name, &self.source_code)
77
        {
78
927
            let line_num = range.start_point.row + 1; // tree-sitter uses 0-indexed rows
79
927
            let col_num = range.start_point.column + 1; // tree-sitter uses 0-indexed columns
80

            
81
            // Get the specific line from source code
82
927
            let lines: Vec<&str> = source_code.lines().collect();
83
927
            let line_content = lines.get(range.start_point.row).unwrap_or(&"");
84

            
85
            // Build the pointer line (spaces + ^)
86
927
            let pointer = " ".repeat(range.start_point.column) + "^";
87

            
88
927
            write!(
89
927
                f,
90
                "{}:{}:{}:\n  |\n{} | {}\n  | {}\n{}",
91
                file_name, line_num, col_num, line_num, line_content, pointer, self.msg
92
            )
93
        } else {
94
            // Fall back to simple format without context
95
1
            write!(f, "Essence syntax error: {}", self.msg)?;
96
1
            if let Some(range) = &self.range {
97
                write!(f, " at {}-{}", range.start_point, range.end_point)?;
98
1
            }
99
1
            Ok(())
100
        }
101
928
    }
102
}
103

            
104
/// Error type for issues during model instantiation (when applying parameters to a problem model).
105
#[derive(Debug)]
106
pub struct InstantiateModelError {
107
    pub msg: String,
108
}
109

            
110
impl std::fmt::Display for InstantiateModelError {
111
28
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112
28
        write!(f, "{}", self.msg)
113
28
    }
114
}
115

            
116
impl std::error::Error for InstantiateModelError {}
117
/// Collection of parse errors
118
#[derive(Debug)]
119
pub enum ParseErrorCollection {
120
    /// A single fatal error that stops parsing entirely
121
    Fatal(FatalParseError),
122
    /// Multiple recoverable errors accumulated during parsing
123
    Multiple {
124
        errors: Vec<RecoverableParseError>,
125
    },
126

            
127
    InstantiateModel(InstantiateModelError),
128
}
129

            
130
impl ParseErrorCollection {
131
    /// Create a fatal error collection from a single fatal error
132
858
    pub fn fatal(error: FatalParseError) -> Self {
133
858
        ParseErrorCollection::Fatal(error)
134
858
    }
135

            
136
    /// Create a multiple error collection from recoverable errors
137
    /// This enriches all errors with file_name and source_code
138
681
    pub fn multiple(
139
681
        errors: Vec<RecoverableParseError>,
140
681
        source_code: Option<String>,
141
681
        file_name: Option<String>,
142
681
    ) -> Self {
143
681
        let enriched_errors = errors
144
681
            .into_iter()
145
928
            .map(|err| err.enrich(file_name.clone(), source_code.clone()))
146
681
            .collect();
147
681
        ParseErrorCollection::Multiple {
148
681
            errors: enriched_errors,
149
681
        }
150
681
    }
151
}
152

            
153
impl std::fmt::Display for ParseErrorCollection {
154
1566
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155
1566
        match self {
156
858
            ParseErrorCollection::Fatal(error) => write!(f, "{}", error),
157
27
            ParseErrorCollection::InstantiateModel(error) => write!(f, "{}", error),
158
681
            ParseErrorCollection::Multiple { errors } => {
159
                // Create indices sorted by line and column
160
681
                let mut indices: Vec<usize> = (0..errors.len()).collect();
161
681
                indices.sort_by(|&a, &b| {
162
247
                    match (&errors[a], &errors[b]) {
163
                        (
164
                            RecoverableParseError {
165
247
                                range: Some(r1), ..
166
                            },
167
                            RecoverableParseError {
168
247
                                range: Some(r2), ..
169
                            },
170
                        ) => {
171
                            // Compare by row first, then by column
172
247
                            match r1.start_point.row.cmp(&r2.start_point.row) {
173
                                std::cmp::Ordering::Equal => {
174
117
                                    r1.start_point.column.cmp(&r2.start_point.column)
175
                                }
176
130
                                other => other,
177
                            }
178
                        }
179
                        // Errors without ranges go last
180
                        (RecoverableParseError { range: Some(_), .. }, _) => {
181
                            std::cmp::Ordering::Less
182
                        }
183
                        (_, RecoverableParseError { range: Some(_), .. }) => {
184
                            std::cmp::Ordering::Greater
185
                        }
186
                        _ => std::cmp::Ordering::Equal,
187
                    }
188
247
                });
189

            
190
                // Print out each error using Display
191
928
                for (i, &idx) in indices.iter().enumerate() {
192
928
                    if i > 0 {
193
247
                        write!(f, "\n\n")?;
194
681
                    }
195
928
                    write!(f, "{}", errors[idx])?;
196
                }
197
681
                Ok(())
198
            }
199
        }
200
1566
    }
201
}
202

            
203
impl std::error::Error for ParseErrorCollection {}
204

            
205
#[cfg(test)]
206
mod tests {
207
    use super::*;
208

            
209
    #[test]
210
1
    fn instantiate_model_error_display_and_error_trait() {
211
1
        let err = InstantiateModelError {
212
1
            msg: "hello".to_string(),
213
1
        };
214

            
215
1
        assert_eq!(err.to_string(), "hello");
216
1
        let _as_error: &dyn std::error::Error = &err;
217
1
    }
218

            
219
    #[test]
220
1
    fn parse_error_collection_instantiate_model_variant_is_displayed() {
221
1
        let err = ParseErrorCollection::InstantiateModel(InstantiateModelError {
222
1
            msg: "missing param".to_string(),
223
1
        });
224
1
        assert_eq!(err.to_string(), "missing param");
225
1
    }
226

            
227
    #[test]
228
1
    fn parse_error_collection_multiple_constructor_works() {
229
1
        let err = ParseErrorCollection::multiple(
230
1
            vec![RecoverableParseError::new("bad token".to_string(), None)],
231
1
            None,
232
1
            None,
233
        );
234
1
        let formatted = err.to_string();
235
1
        assert!(formatted.contains("bad token"));
236
1
    }
237
}