1
use std::sync::{Arc, RwLock};
2
use std::{fs, vec};
3

            
4
use conjure_cp_core::Model;
5
use conjure_cp_core::ast::DeclarationPtr;
6
use conjure_cp_core::ast::assertions::debug_assert_model_well_formed;
7
use conjure_cp_core::context::Context;
8
#[allow(unused)]
9
use uniplate::Uniplate;
10

            
11
use super::ParseContext;
12
use super::find::parse_find_statement;
13
use super::letting::parse_letting_statement;
14
use super::util::{TypecheckingContext, get_tree};
15
use crate::diagnostics::diagnostics_api::SymbolKind;
16
use crate::diagnostics::source_map::{HoverInfo, SourceMap, span_with_hover};
17
use crate::errors::{FatalParseError, ParseErrorCollection, RecoverableParseError};
18
use crate::expression::parse_expression;
19
use crate::syntax_errors::detect_syntactic_errors;
20

            
21
/// Parse an Essence file into a Model using the tree-sitter parser.
22
902
pub fn parse_essence_file_native(
23
2544
    path: &str,
24
2544
    context: Arc<RwLock<Context<'static>>>,
25
2544
) -> Result<Model, Box<ParseErrorCollection>> {
26
2544
    let source_code = fs::read_to_string(path)
27
2544
        .unwrap_or_else(|_| panic!("Failed to read the source code file {path}"));
28
1642

            
29
902
    let mut errors = vec![];
30
2544
    let model = parse_essence_with_context(&source_code, context, &mut errors);
31
1642

            
32
902
    match model {
33
2144
        Ok(Some(m)) => {
34
1304
            debug_assert_model_well_formed(&m, "tree-sitter");
35
1304
            Ok(m)
36
802
        }
37
        Ok(None) => {
38
            // Recoverable errors were found, return them as a ParseErrorCollection
39
400
            Err(Box::new(ParseErrorCollection::multiple(
40
1240
                errors,
41
1240
                Some(source_code),
42
1240
                Some(path.to_string()),
43
1240
            )))
44
840
        }
45
        Err(fatal) => {
46
            // Fatal error - wrap in ParseErrorCollection::Fatal
47
            Err(Box::new(ParseErrorCollection::fatal(fatal)))
48
        }
49
    }
50
902
}
51
1642

            
52
1322
pub fn parse_essence_with_context(
53
3734
    src: &str,
54
3734
    context: Arc<RwLock<Context<'static>>>,
55
3734
    errors: &mut Vec<RecoverableParseError>,
56
3734
) -> Result<Option<Model>, FatalParseError> {
57
3734
    match parse_essence_with_context_and_map(src, context, errors)? {
58
2926
        Some((model, _source_map)) => Ok(Some(model)),
59
1632
        None => Ok(None),
60
1588
    }
61
1322
}
62
2412

            
63
1324
pub fn parse_essence_with_context_and_map(
64
3740
    src: &str,
65
3740
    context: Arc<RwLock<Context<'static>>>,
66
3740
    errors: &mut Vec<RecoverableParseError>,
67
3740
) -> Result<Option<(Model, SourceMap)>, FatalParseError> {
68
3740
    let (tree, source_code) = match get_tree(src) {
69
3740
        Some(tree) => tree,
70
2416
        None => {
71
            return Err(FatalParseError::TreeSitterError(
72
                "Failed to parse source code".to_string(),
73
            ));
74
        }
75
    };
76

            
77
1324
    if tree.root_node().has_error() {
78
2900
        detect_syntactic_errors(src, &tree, errors);
79
1456
        return Ok(None);
80
1812
    }
81
1444

            
82
840
    let mut model = Model::new(context);
83
2284
    let mut source_map = SourceMap::default();
84
2284
    let root_node = tree.root_node();
85
1444

            
86
    // Create a ParseContext
87
840
    let mut ctx = ParseContext::new(
88
2284
        &source_code,
89
2284
        &root_node,
90
2284
        Some(model.symbols_ptr_unchecked().clone()),
91
2284
        errors,
92
2284
        &mut source_map,
93
1444
    );
94

            
95
840
    let mut cursor = root_node.walk();
96
6362
    for statement in root_node.children(&mut cursor) {
97
7594
        /*
98
           since find and letting are unnamed children
99
           hover info is added here.
100
           other unnamed children will be skipped.
101
        */
102
4918
        if statement.kind() == "find" {
103
8698
            span_with_hover(
104
2746
                &statement,
105
2746
                ctx.source_code,
106
2746
                ctx.source_map,
107
2746
                HoverInfo {
108
2746
                    description: "Find keyword".to_string(),
109
2746
                    kind: Some(SymbolKind::Find),
110
2746
                    ty: None,
111
2746
                    decl_span: None,
112
2746
                },
113
2746
            );
114
5456
        } else if statement.kind() == "letting" {
115
6385
            span_with_hover(
116
1249
                &statement,
117
1249
                ctx.source_code,
118
1249
                ctx.source_map,
119
1249
                HoverInfo {
120
1249
                    description: "Letting keyword".to_string(),
121
1249
                    kind: Some(SymbolKind::Letting),
122
1249
                    ty: None,
123
1249
                    decl_span: None,
124
1249
                },
125
1249
            );
126
4197
        }
127
5136

            
128
4918
        if !statement.is_named() {
129
10023
            continue;
130
6275
        }
131
3808

            
132
2489
        match statement.kind() {
133
6297
            "single_line_comment" => {}
134
6297
            "language_declaration" => {}
135
6297
            "find_statement" => {
136
4890
                let var_hashmap = parse_find_statement(&mut ctx, statement)?;
137
2856
                for (name, domain) in var_hashmap {
138
3036
                    model
139
3036
                        .symbols_mut()
140
3036
                        .insert(DeclarationPtr::new_find(name, domain));
141
3036
                }
142
1822
            }
143
1385
            "bool_expr" | "atom" | "comparison_expr" => {
144
3036
                ctx.typechecking_context = TypecheckingContext::Boolean;
145
2220
                let Some(expr) = parse_expression(&mut ctx, statement)? else {
146
1508
                    continue;
147
352
                };
148
712
                model.add_constraint(expr);
149
976
            }
150
493
            "language_label" => {}
151
1309
            "letting_statement" => {
152
1249
                let Some(letting_vars) = parse_letting_statement(&mut ctx, statement)? else {
153
816
                    continue;
154
                };
155
433
                model.symbols_mut().extend(letting_vars);
156
816
            }
157
60
            "dominance_relation" => {
158
60
                let Some(dominance) = parse_expression(&mut ctx, statement)? else {
159
                    continue;
160
                };
161
60
                if model.dominance.is_some() {
162
                    ctx.record_error(RecoverableParseError::new(
163
                        "Duplicate dominance relation".to_string(),
164
                        None,
165
                    ));
166
                    continue;
167
60
                }
168
60
                model.dominance = Some(dominance);
169
            }
170
            _ => {
171
                return Err(FatalParseError::internal_error(
172
                    format!("Unexpected top-level statement: {}", statement.kind()),
173
                    Some(statement.range()),
174
                ));
175
            }
176
        }
177
    }
178

            
179
    // check for errors (keyword as identifier)
180
840
    keyword_as_identifier(&mut ctx);
181

            
182
    // Check if there were any recoverable errors
183
2284
    if !errors.is_empty() {
184
324
        return Ok(None);
185
516
    }
186
    // otherwise return the model
187
1132
    Ok(Some((model, source_map)))
188
2152
}
189

            
190
828
const KEYWORDS: [&str; 21] = [
191
2416
    "forall", "exists", "such", "that", "letting", "find", "minimise", "maximise", "subject", "to",
192
    "where", "and", "or", "not", "if", "then", "else", "in", "sum", "product", "bool",
193
];
194

            
195
840
fn keyword_as_identifier(ctx: &mut ParseContext) {
196
840
    let mut stack = vec![*ctx.root];
197
53399
    while let Some(node) = stack.pop() {
198
54003
        if (node.kind() == "variable" || node.kind() == "identifier" || node.kind() == "parameter")
199
5787
            && let Ok(text) = node.utf8_text(ctx.source_code.as_bytes())
200
74050
        {
201
76949
            let ident = text.trim();
202
9725
            if KEYWORDS.contains(&ident) {
203
84
                let start_point = node.start_position();
204
5466
                let end_point = node.end_position();
205
5466
                ctx.errors.push(RecoverableParseError::new(
206
238
                    format!("Keyword '{ident}' used as identifier"),
207
238
                    Some(tree_sitter::Range {
208
238
                        start_byte: node.start_byte(),
209
238
                        end_byte: node.end_byte(),
210
238
                        start_point,
211
238
                        end_point,
212
238
                    }),
213
238
                ));
214
4413
            }
215
48370
        }
216
154

            
217
        // push children onto stack
218
119783
        for i in 0..node.child_count() {
219
51719
            if let Some(child) = u32::try_from(i).ok().and_then(|i| node.child(i)) {
220
51719
                stack.push(child);
221
124325
            }
222
71162
        }
223
71162
    }
224
72002
}
225

            
226
2
pub fn parse_essence(src: &str) -> Result<(Model, SourceMap), Box<ParseErrorCollection>> {
227
1446
    let context = Arc::new(RwLock::new(Context::default()));
228
2
    let mut errors = vec![];
229
6
    match parse_essence_with_context_and_map(src, context, &mut errors) {
230
6
        Ok(Some((model, source_map))) => {
231
6
            debug_assert_model_well_formed(&model, "tree-sitter");
232
6
            Ok((model, source_map))
233
4
        }
234
4
        Ok(None) => {
235
            // Recoverable errors were found, return them as a ParseErrorCollection
236
            Err(Box::new(ParseErrorCollection::multiple(
237
                errors,
238
                Some(src.to_string()),
239
                None,
240
            )))
241
        }
242
        Err(fatal) => Err(Box::new(ParseErrorCollection::fatal(fatal))),
243
    }
244
2
}
245

            
246
mod test {
247
4
    #[allow(unused_imports)]
248
    use crate::parse_essence;
249
    #[allow(unused_imports)]
250
    use conjure_cp_core::ast::{Atom, Expression, Metadata, Moo, Name};
251
    #[allow(unused_imports)]
252
    use conjure_cp_core::{domain_int, matrix_expr, range};
253
    #[allow(unused_imports)]
254
    use std::ops::Deref;
255

            
256
    #[test]
257
1
    pub fn test_parse_xyz() {
258
1
        let src = "
259
1
        find x, y, z : int(1..4)
260
3
        such that x + y + z = 4
261
3
        such that x >= y
262
3
        ";
263
2

            
264
3
        let (model, _source_map) = parse_essence(src).unwrap();
265
2

            
266
1
        let st = model.symbols();
267
3
        let x = st.lookup(&Name::user("x")).unwrap();
268
1
        let y = st.lookup(&Name::user("y")).unwrap();
269
3
        let z = st.lookup(&Name::user("z")).unwrap();
270
3
        assert_eq!(x.domain(), Some(domain_int!(1..4)));
271
3
        assert_eq!(y.domain(), Some(domain_int!(1..4)));
272
3
        assert_eq!(z.domain(), Some(domain_int!(1..4)));
273
2

            
274
3
        let constraints = model.constraints();
275
3
        assert_eq!(constraints.len(), 2);
276

            
277
3
        let c1 = constraints[0].clone();
278
3
        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x));
279
1
        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y));
280
3
        let z_e = Expression::Atomic(Metadata::new(), Atom::new_ref(z));
281
3
        assert_eq!(
282
2
            c1,
283
3
            Expression::Eq(
284
3
                Metadata::new(),
285
1
                Moo::new(Expression::Sum(
286
3
                    Metadata::new(),
287
3
                    Moo::new(matrix_expr!(
288
3
                        Expression::Sum(
289
3
                            Metadata::new(),
290
3
                            Moo::new(matrix_expr!(x_e.clone(), y_e.clone()))
291
3
                        ),
292
3
                        z_e
293
3
                    ))
294
3
                )),
295
3
                Moo::new(Expression::Atomic(Metadata::new(), 4.into()))
296
3
            )
297
2
        );
298
2

            
299
3
        let c2 = constraints[1].clone();
300
1
        assert_eq!(
301
            c2,
302
3
            Expression::Geq(Metadata::new(), Moo::new(x_e), Moo::new(y_e))
303
2
        );
304
1
    }
305
2

            
306
    #[test]
307
3
    pub fn test_parse_letting_index() {
308
1
        let src = "
309
1
        letting a be [ [ 1,2,3 ; int(1,2,4) ], [ 1,3,2 ; int(1,2,4) ], [ 3,2,1 ; int(1,2,4) ] ; int(-2..0) ]
310
3
        find b: int(1..5)
311
3
        such that
312
3
        b < a[-2,2],
313
3
        allDiff(a[-2,..])
314
3
        ";
315
2

            
316
3
        let (model, _source_map) = parse_essence(src).unwrap();
317
3
        let st = model.symbols();
318
1
        let a_decl = st.lookup(&Name::user("a")).unwrap();
319
3
        let a = a_decl.as_value_letting().unwrap().deref().clone();
320
3
        assert_eq!(
321
2
            a,
322
3
            matrix_expr!(
323
3
                matrix_expr!(1.into(), 2.into(), 3.into() ; domain_int!(1, 2, 4)),
324
1
                matrix_expr!(1.into(), 3.into(), 2.into() ; domain_int!(1, 2, 4)),
325
3
                matrix_expr!(3.into(), 2.into(), 1.into() ; domain_int!(1, 2, 4));
326
3
                domain_int!(-2..0)
327
2
            )
328
2
        )
329
3
    }
330
}