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

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

            
12
use super::ParseContext;
13
use super::dominance::parse_dominance_relation;
14
use super::find::{parse_find_statement, parse_given_statement};
15
use super::letting::parse_letting_statement;
16
use super::util::{TypecheckingContext, get_tree};
17
use crate::diagnostics::source_map::SourceMap;
18
use crate::errors::{FatalParseError, ParseErrorCollection, RecoverableParseError};
19
use crate::expression::parse_expression;
20
use crate::parser::keyword_checks::keyword_as_identifier;
21
use crate::syntax_errors::detect_syntactic_errors;
22
use tree_sitter::Tree;
23

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

            
32
18611
    let mut errors = vec![];
33
18611
    let model = parse_essence_with_context(&source_code, context, &mut errors);
34

            
35
18611
    match model {
36
17814
        Ok(Some(m)) => {
37
17814
            debug_assert_model_well_formed(&m, "tree-sitter");
38
17814
            Ok(m)
39
        }
40
        Ok(None) => {
41
            // Recoverable errors were found, return them as a ParseErrorCollection
42
797
            Err(Box::new(ParseErrorCollection::multiple(
43
797
                errors,
44
797
                Some(source_code),
45
797
                Some(path.to_string()),
46
797
            )))
47
        }
48
        Err(fatal) => {
49
            // Fatal error - wrap in ParseErrorCollection::Fatal
50
            Err(Box::new(ParseErrorCollection::fatal(fatal)))
51
        }
52
    }
53
18611
}
54

            
55
18611
pub fn parse_essence_with_context(
56
18611
    src: &str,
57
18611
    context: Arc<RwLock<Context<'static>>>,
58
18611
    errors: &mut Vec<RecoverableParseError>,
59
18611
) -> Result<Option<Model>, FatalParseError> {
60
18611
    match parse_essence_with_context_and_map(src, context, errors, None)? {
61
17814
        (Some(model), _source_map) => Ok(Some(model)),
62
797
        (None, _source_map) => Ok(None),
63
    }
64
18611
}
65

            
66
/*
67
    this function is used by both the file-based parser and the LSP parser (which needs the source map)
68
    the LSP parser can also optionally pass in a pre-parsed tree to avoid parsing twice (which is how caching is implemented)
69
    if the tree is not passed in, we will parse it from scratch (this is what the file-based parser does)
70
    when cache is dirty, LSP has to call parse_essence_with_context_and_map with None for the tree,
71
    which will cause it to re-parse the source code and update the cache (Model = ast, SorceMap = map)
72
*/
73
19084
pub fn parse_essence_with_context_and_map(
74
19084
    src: &str,
75
19084
    context: Arc<RwLock<Context<'static>>>,
76
19084
    errors: &mut Vec<RecoverableParseError>,
77
19084
    tree: Option<&Tree>,
78
19084
) -> Result<(Option<Model>, SourceMap), FatalParseError> {
79
19084
    let (tree, source_code) = if let Some(tree) = tree {
80
468
        (tree.clone(), src.to_string())
81
    } else {
82
18616
        match get_tree(src) {
83
18616
            Some(tree) => tree,
84
            None => {
85
                return Err(FatalParseError::TreeSitterError(
86
                    "Failed to parse source code".to_string(),
87
                ));
88
            }
89
        }
90
    };
91

            
92
19084
    let has_syntax_errors = tree.root_node().has_error();
93
19084
    if has_syntax_errors {
94
719
        detect_syntactic_errors(src, &tree, errors);
95
18365
    }
96

            
97
    // don't detect semantic errors if there are syntactic errors, but still parse for source map.
98
19084
    let mut suppressed_semantic_errors = Vec::new();
99
19084
    let semantic_errors: &mut Vec<RecoverableParseError> = if has_syntax_errors {
100
719
        &mut suppressed_semantic_errors
101
    } else {
102
18365
        errors
103
    };
104

            
105
19084
    keyword_as_identifier(tree.root_node(), src, semantic_errors);
106

            
107
19084
    let mut model = Model::new(context);
108
19084
    let mut source_map = SourceMap::default();
109
19084
    let mut declaration_spans = BTreeMap::new();
110
19084
    let root_node = tree.root_node();
111

            
112
    // Create a ParseContext
113
19084
    let mut ctx = ParseContext::new(
114
19084
        &source_code,
115
19084
        &root_node,
116
19084
        Some(model.symbols_ptr_unchecked().clone()),
117
19084
        semantic_errors,
118
19084
        &mut source_map,
119
19084
        &mut declaration_spans,
120
    );
121

            
122
19084
    let mut cursor = root_node.walk();
123
166460
    for statement in root_node.children(&mut cursor) {
124
166460
        if !statement.is_named() || statement.is_error() || statement.kind() == "ERROR" {
125
35470
            continue;
126
130990
        }
127

            
128
130990
        ctx.typechecking_context = TypecheckingContext::Unknown;
129
130990
        ctx.inner_typechecking_context = TypecheckingContext::Unknown;
130

            
131
130990
        match statement.kind() {
132
130990
            "single_line_comment" => {}
133
88074
            "language_declaration" => {}
134
81554
            "find_statement" => {
135
38422
                let var_hashmap = parse_find_statement(&mut ctx, statement)?;
136
45175
                for (name, domain) in var_hashmap {
137
45175
                    model
138
45175
                        .symbols_mut()
139
45175
                        .insert(DeclarationPtr::new_find(name, domain));
140
45175
                }
141
            }
142
43132
            "given_statement" => {
143
142
                let var_hashmap = parse_given_statement(&mut ctx, statement)?;
144
142
                for (name, domain) in var_hashmap {
145
129
                    model
146
129
                        .symbols_mut()
147
129
                        .insert(DeclarationPtr::new_given(name, domain));
148
129
                }
149
            }
150
42990
            "bool_expr" | "atom" | "comparison_expr" => {
151
34894
                ctx.typechecking_context = TypecheckingContext::Boolean;
152
34894
                let Some(expr) = parse_expression(&mut ctx, statement)? else {
153
546
                    continue;
154
                };
155
34348
                model.add_constraint(expr);
156
            }
157
8096
            "language_label" => {}
158
8096
            "letting_statement" => {
159
2399
                let Some(letting_vars) = parse_letting_statement(&mut ctx, statement)? else {
160
                    continue;
161
                };
162
2399
                model.symbols_mut().extend(letting_vars);
163
            }
164
5697
            "dominance_relation" => {
165
5697
                let Some(dominance) = parse_dominance_relation(&mut ctx, &statement)? else {
166
                    continue;
167
                };
168
5697
                if model.dominance.is_some() {
169
                    ctx.record_error(RecoverableParseError::new(
170
                        "Duplicate dominance relation".to_string(),
171
                        None,
172
                    ));
173
                    continue;
174
5697
                }
175
5697
                model.dominance = Some(dominance);
176
            }
177
            _ => {
178
                ctx.record_error(RecoverableParseError::new(
179
                    format!("Unexpected top-level statement: {}", statement.kind()),
180
                    Some(statement.range()),
181
                ));
182
                continue;
183
            }
184
        }
185
    }
186

            
187
    // Check if there were any recoverable errors
188
19084
    if !errors.is_empty() {
189
1252
        return Ok((None, source_map));
190
17832
    }
191
    // otherwise return the model
192
17832
    Ok((Some(model), source_map))
193
19084
}
194

            
195
5
pub fn parse_essence(src: &str) -> Result<(Model, SourceMap), Box<ParseErrorCollection>> {
196
5
    let context = Arc::new(RwLock::new(Context::default()));
197
5
    let mut errors = vec![];
198
5
    match parse_essence_with_context_and_map(src, context, &mut errors, None) {
199
5
        Ok((Some(model), source_map)) => {
200
5
            debug_assert_model_well_formed(&model, "tree-sitter");
201
5
            Ok((model, source_map))
202
        }
203
        Ok((None, _source_map)) => {
204
            // Recoverable errors were found, return them as a ParseErrorCollection
205
            Err(Box::new(ParseErrorCollection::multiple(
206
                errors,
207
                Some(src.to_string()),
208
                None,
209
            )))
210
        }
211
        Err(fatal) => Err(Box::new(ParseErrorCollection::fatal(fatal))),
212
    }
213
5
}
214

            
215
mod test {
216
    #[allow(unused_imports)]
217
    use crate::parse_essence;
218
    #[allow(unused_imports)]
219
    use conjure_cp_core::ast::{Atom, Expression, Metadata, Moo, Name};
220
    #[allow(unused_imports)]
221
    use conjure_cp_core::{domain_int, matrix_expr, range};
222
    #[allow(unused_imports)]
223
    use std::ops::Deref;
224

            
225
    #[test]
226
1
    pub fn test_parse_xyz() {
227
1
        let src = "
228
1
        find x, y, z : int(1..4)
229
1
        such that x + y + z = 4
230
1
        such that x >= y
231
1
        ";
232

            
233
1
        let (model, _source_map) = parse_essence(src).unwrap();
234

            
235
1
        let st = model.symbols();
236
1
        let x = st.lookup(&Name::user("x")).unwrap();
237
1
        let y = st.lookup(&Name::user("y")).unwrap();
238
1
        let z = st.lookup(&Name::user("z")).unwrap();
239
1
        assert_eq!(x.domain(), Some(domain_int!(1..4)));
240
1
        assert_eq!(y.domain(), Some(domain_int!(1..4)));
241
1
        assert_eq!(z.domain(), Some(domain_int!(1..4)));
242

            
243
1
        let constraints = model.constraints();
244
1
        assert_eq!(constraints.len(), 2);
245

            
246
1
        let c1 = constraints[0].clone();
247
1
        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x));
248
1
        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y));
249
1
        let z_e = Expression::Atomic(Metadata::new(), Atom::new_ref(z));
250
1
        assert_eq!(
251
            c1,
252
1
            Expression::Eq(
253
1
                Metadata::new(),
254
1
                Moo::new(Expression::Sum(
255
1
                    Metadata::new(),
256
1
                    Moo::new(matrix_expr!(
257
1
                        Expression::Sum(
258
1
                            Metadata::new(),
259
1
                            Moo::new(matrix_expr!(x_e.clone(), y_e.clone()))
260
1
                        ),
261
1
                        z_e
262
1
                    ))
263
1
                )),
264
1
                Moo::new(Expression::Atomic(Metadata::new(), 4.into()))
265
1
            )
266
        );
267

            
268
1
        let c2 = constraints[1].clone();
269
1
        assert_eq!(
270
            c2,
271
1
            Expression::Geq(Metadata::new(), Moo::new(x_e), Moo::new(y_e))
272
        );
273
1
    }
274

            
275
    #[test]
276
1
    pub fn test_parse_letting_index() {
277
1
        let src = "
278
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) ]
279
1
        find b: int(1..5)
280
1
        such that
281
1
        b < a[-2,2],
282
1
        allDiff(a[-2,..])
283
1
        ";
284

            
285
1
        let (model, _source_map) = parse_essence(src).unwrap();
286
1
        let st = model.symbols();
287
1
        let a_decl = st.lookup(&Name::user("a")).unwrap();
288
1
        let a = a_decl.as_value_letting().unwrap().deref().clone();
289
1
        assert_eq!(
290
            a,
291
1
            matrix_expr!(
292
1
                matrix_expr!(1.into(), 2.into(), 3.into() ; domain_int!(1, 2, 4)),
293
1
                matrix_expr!(1.into(), 3.into(), 2.into() ; domain_int!(1, 2, 4)),
294
1
                matrix_expr!(3.into(), 2.into(), 1.into() ; domain_int!(1, 2, 4));
295
1
                domain_int!(-2..0)
296
            )
297
        )
298
1
    }
299

            
300
    #[test]
301
1
    pub fn test_parse_pareto_in_dominance_relation() {
302
1
        let src = "
303
1
        find x : int(0..3)
304
1

            
305
1
        dominance relation
306
1
            pareto(minimising x)
307
1
        ";
308

            
309
1
        let (model, _source_map) = parse_essence(src).unwrap();
310
1
        let st = model.symbols();
311
1
        let x = st.lookup(&Name::user("x")).unwrap();
312
1
        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
313
1
        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
314

            
315
1
        assert_eq!(
316
            model.dominance,
317
1
            Some(Expression::DominanceRelation(
318
1
                Metadata::new(),
319
1
                Moo::new(Expression::And(
320
1
                    Metadata::new(),
321
1
                    Moo::new(matrix_expr!(
322
1
                        Expression::Leq(
323
1
                            Metadata::new(),
324
1
                            Moo::new(x_e.clone()),
325
1
                            Moo::new(x_prev.clone())
326
1
                        ),
327
1
                        Expression::Lt(Metadata::new(), Moo::new(x_e), Moo::new(x_prev))
328
1
                    ))
329
1
                ))
330
1
            ))
331
        );
332
1
    }
333

            
334
    #[test]
335
1
    pub fn test_parse_pareto_with_mixed_directions() {
336
1
        let src = "
337
1
        find x : int(0..3)
338
1
        find y : int(0..3)
339
1

            
340
1
        dominance relation
341
1
            pareto(minimising x, maximising y)
342
1
        ";
343

            
344
1
        let (model, _source_map) = parse_essence(src).unwrap();
345
1
        let st = model.symbols();
346
1
        let x = st.lookup(&Name::user("x")).unwrap();
347
1
        let y = st.lookup(&Name::user("y")).unwrap();
348
1
        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
349
1
        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y.clone()));
350
1
        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
351
1
        let y_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(y)));
352

            
353
1
        assert_eq!(
354
            model.dominance,
355
1
            Some(Expression::DominanceRelation(
356
1
                Metadata::new(),
357
1
                Moo::new(Expression::And(
358
1
                    Metadata::new(),
359
1
                    Moo::new(matrix_expr!(
360
1
                        Expression::Leq(
361
1
                            Metadata::new(),
362
1
                            Moo::new(x_e.clone()),
363
1
                            Moo::new(x_prev.clone())
364
1
                        ),
365
1
                        Expression::Geq(
366
1
                            Metadata::new(),
367
1
                            Moo::new(y_e.clone()),
368
1
                            Moo::new(y_prev.clone())
369
1
                        ),
370
1
                        Expression::Or(
371
1
                            Metadata::new(),
372
1
                            Moo::new(matrix_expr!(
373
1
                                Expression::Lt(Metadata::new(), Moo::new(x_e), Moo::new(x_prev)),
374
1
                                Expression::Gt(Metadata::new(), Moo::new(y_e), Moo::new(y_prev))
375
1
                            ))
376
1
                        )
377
1
                    ))
378
1
                ))
379
1
            ))
380
        );
381
1
    }
382

            
383
    #[test]
384
1
    pub fn test_parse_pareto_over_expression_component() {
385
1
        let src = "
386
1
        find x : int(0..3)
387
1

            
388
1
        dominance relation
389
1
            pareto(minimising x + 1)
390
1
        ";
391

            
392
1
        let (model, _source_map) = parse_essence(src).unwrap();
393
1
        let st = model.symbols();
394
1
        let x = st.lookup(&Name::user("x")).unwrap();
395
1
        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
396
1
        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
397
1
        let one = Expression::Atomic(Metadata::new(), 1.into());
398
1
        let current = Expression::Sum(
399
1
            Metadata::new(),
400
1
            Moo::new(matrix_expr!(x_e.clone(), one.clone())),
401
1
        );
402
1
        let previous = Expression::Sum(Metadata::new(), Moo::new(matrix_expr!(x_prev, one)));
403

            
404
1
        assert_eq!(
405
            model.dominance,
406
1
            Some(Expression::DominanceRelation(
407
1
                Metadata::new(),
408
1
                Moo::new(Expression::And(
409
1
                    Metadata::new(),
410
1
                    Moo::new(matrix_expr!(
411
1
                        Expression::Leq(
412
1
                            Metadata::new(),
413
1
                            Moo::new(current.clone()),
414
1
                            Moo::new(previous.clone())
415
1
                        ),
416
1
                        Expression::Lt(Metadata::new(), Moo::new(current), Moo::new(previous))
417
1
                    ))
418
1
                ))
419
1
            ))
420
        );
421
1
    }
422
}