Skip to main content

conjure_cp_essence_parser/parser/
parse_model.rs

1use std::collections::BTreeMap;
2use std::sync::{Arc, RwLock};
3use std::{fs, vec};
4
5use conjure_cp_core::Model;
6use conjure_cp_core::ast::DeclarationPtr;
7use conjure_cp_core::ast::assertions::debug_assert_model_well_formed;
8use conjure_cp_core::context::Context;
9#[allow(unused)]
10use uniplate::Uniplate;
11
12use super::ParseContext;
13use super::find::{parse_find_statement, parse_given_statement};
14use super::letting::parse_letting_statement;
15use super::util::{TypecheckingContext, get_tree};
16use crate::diagnostics::source_map::SourceMap;
17use crate::errors::{FatalParseError, ParseErrorCollection, RecoverableParseError};
18use crate::expression::parse_expression;
19use crate::parser::keyword_checks::keyword_as_identifier;
20use crate::syntax_errors::detect_syntactic_errors;
21use tree_sitter::Tree;
22
23/// Parse an Essence file into a Model using the tree-sitter parser.
24pub fn parse_essence_file_native(
25    path: &str,
26    context: Arc<RwLock<Context<'static>>>,
27) -> Result<Model, Box<ParseErrorCollection>> {
28    let source_code = fs::read_to_string(path)
29        .unwrap_or_else(|_| panic!("Failed to read the source code file {path}"));
30
31    let mut errors = vec![];
32    let model = parse_essence_with_context(&source_code, context, &mut errors);
33
34    match model {
35        Ok(Some(m)) => {
36            debug_assert_model_well_formed(&m, "tree-sitter");
37            Ok(m)
38        }
39        Ok(None) => {
40            // Recoverable errors were found, return them as a ParseErrorCollection
41            Err(Box::new(ParseErrorCollection::multiple(
42                errors,
43                Some(source_code),
44                Some(path.to_string()),
45            )))
46        }
47        Err(fatal) => {
48            // Fatal error - wrap in ParseErrorCollection::Fatal
49            Err(Box::new(ParseErrorCollection::fatal(fatal)))
50        }
51    }
52}
53
54pub fn parse_essence_with_context(
55    src: &str,
56    context: Arc<RwLock<Context<'static>>>,
57    errors: &mut Vec<RecoverableParseError>,
58) -> Result<Option<Model>, FatalParseError> {
59    match parse_essence_with_context_and_map(src, context, errors, None)? {
60        (Some(model), _source_map) => Ok(Some(model)),
61        (None, _source_map) => Ok(None),
62    }
63}
64
65/*
66    this function is used by both the file-based parser and the LSP parser (which needs the source map)
67    the LSP parser can also optionally pass in a pre-parsed tree to avoid parsing twice (which is how caching is implemented)
68    if the tree is not passed in, we will parse it from scratch (this is what the file-based parser does)
69    when cache is dirty, LSP has to call parse_essence_with_context_and_map with None for the tree,
70    which will cause it to re-parse the source code and update the cache (Model = ast, SorceMap = map)
71*/
72pub fn parse_essence_with_context_and_map(
73    src: &str,
74    context: Arc<RwLock<Context<'static>>>,
75    errors: &mut Vec<RecoverableParseError>,
76    tree: Option<&Tree>,
77) -> Result<(Option<Model>, SourceMap), FatalParseError> {
78    let (tree, source_code) = if let Some(tree) = tree {
79        (tree.clone(), src.to_string())
80    } else {
81        match get_tree(src) {
82            Some(tree) => tree,
83            None => {
84                return Err(FatalParseError::TreeSitterError(
85                    "Failed to parse source code".to_string(),
86                ));
87            }
88        }
89    };
90
91    let has_syntax_errors = tree.root_node().has_error();
92    if has_syntax_errors {
93        detect_syntactic_errors(src, &tree, errors);
94    }
95
96    // don't detect semantic errors if there are syntactic errors, but still parse for source map.
97    let mut suppressed_semantic_errors = Vec::new();
98    let semantic_errors: &mut Vec<RecoverableParseError> = if has_syntax_errors {
99        &mut suppressed_semantic_errors
100    } else {
101        errors
102    };
103
104    keyword_as_identifier(tree.root_node(), src, semantic_errors);
105
106    let mut model = Model::new(context);
107    let mut source_map = SourceMap::default();
108    let mut declaration_spans = BTreeMap::new();
109    let root_node = tree.root_node();
110
111    // Create a ParseContext
112    let mut ctx = ParseContext::new(
113        &source_code,
114        &root_node,
115        Some(model.symbols_ptr_unchecked().clone()),
116        semantic_errors,
117        &mut source_map,
118        &mut declaration_spans,
119    );
120
121    let mut cursor = root_node.walk();
122    for statement in root_node.children(&mut cursor) {
123        if !statement.is_named() || statement.is_error() || statement.kind() == "ERROR" {
124            continue;
125        }
126
127        match statement.kind() {
128            "single_line_comment" => {}
129            "language_declaration" => {}
130            "find_statement" => {
131                let var_hashmap = parse_find_statement(&mut ctx, statement)?;
132                for (name, domain) in var_hashmap {
133                    model
134                        .symbols_mut()
135                        .insert(DeclarationPtr::new_find(name, domain));
136                }
137            }
138            "given_statement" => {
139                let var_hashmap = parse_given_statement(&mut ctx, statement)?;
140                for (name, domain) in var_hashmap {
141                    model
142                        .symbols_mut()
143                        .insert(DeclarationPtr::new_given(name, domain));
144                }
145            }
146            "bool_expr" | "atom" | "comparison_expr" => {
147                ctx.typechecking_context = TypecheckingContext::Boolean;
148                let Some(expr) = parse_expression(&mut ctx, statement)? else {
149                    continue;
150                };
151                model.add_constraint(expr);
152            }
153            "language_label" => {}
154            "letting_statement" => {
155                let Some(letting_vars) = parse_letting_statement(&mut ctx, statement)? else {
156                    continue;
157                };
158                model.symbols_mut().extend(letting_vars);
159            }
160            "dominance_relation" => {
161                let Some(dominance) = parse_expression(&mut ctx, statement)? else {
162                    continue;
163                };
164                if model.dominance.is_some() {
165                    ctx.record_error(RecoverableParseError::new(
166                        "Duplicate dominance relation".to_string(),
167                        None,
168                    ));
169                    continue;
170                }
171                model.dominance = Some(dominance);
172            }
173            _ => {
174                ctx.record_error(RecoverableParseError::new(
175                    format!("Unexpected top-level statement: {}", statement.kind()),
176                    Some(statement.range()),
177                ));
178                continue;
179            }
180        }
181    }
182
183    // Check if there were any recoverable errors
184    if !errors.is_empty() {
185        return Ok((None, source_map));
186    }
187    // otherwise return the model
188    Ok((Some(model), source_map))
189}
190
191pub fn parse_essence(src: &str) -> Result<(Model, SourceMap), Box<ParseErrorCollection>> {
192    let context = Arc::new(RwLock::new(Context::default()));
193    let mut errors = vec![];
194    match parse_essence_with_context_and_map(src, context, &mut errors, None) {
195        Ok((Some(model), source_map)) => {
196            debug_assert_model_well_formed(&model, "tree-sitter");
197            Ok((model, source_map))
198        }
199        Ok((None, _source_map)) => {
200            // Recoverable errors were found, return them as a ParseErrorCollection
201            Err(Box::new(ParseErrorCollection::multiple(
202                errors,
203                Some(src.to_string()),
204                None,
205            )))
206        }
207        Err(fatal) => Err(Box::new(ParseErrorCollection::fatal(fatal))),
208    }
209}
210
211mod test {
212    #[allow(unused_imports)]
213    use crate::parse_essence;
214    #[allow(unused_imports)]
215    use conjure_cp_core::ast::{Atom, Expression, Metadata, Moo, Name};
216    #[allow(unused_imports)]
217    use conjure_cp_core::{domain_int, matrix_expr, range};
218    #[allow(unused_imports)]
219    use std::ops::Deref;
220
221    #[test]
222    pub fn test_parse_xyz() {
223        let src = "
224        find x, y, z : int(1..4)
225        such that x + y + z = 4
226        such that x >= y
227        ";
228
229        let (model, _source_map) = parse_essence(src).unwrap();
230
231        let st = model.symbols();
232        let x = st.lookup(&Name::user("x")).unwrap();
233        let y = st.lookup(&Name::user("y")).unwrap();
234        let z = st.lookup(&Name::user("z")).unwrap();
235        assert_eq!(x.domain(), Some(domain_int!(1..4)));
236        assert_eq!(y.domain(), Some(domain_int!(1..4)));
237        assert_eq!(z.domain(), Some(domain_int!(1..4)));
238
239        let constraints = model.constraints();
240        assert_eq!(constraints.len(), 2);
241
242        let c1 = constraints[0].clone();
243        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x));
244        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y));
245        let z_e = Expression::Atomic(Metadata::new(), Atom::new_ref(z));
246        assert_eq!(
247            c1,
248            Expression::Eq(
249                Metadata::new(),
250                Moo::new(Expression::Sum(
251                    Metadata::new(),
252                    Moo::new(matrix_expr!(
253                        Expression::Sum(
254                            Metadata::new(),
255                            Moo::new(matrix_expr!(x_e.clone(), y_e.clone()))
256                        ),
257                        z_e
258                    ))
259                )),
260                Moo::new(Expression::Atomic(Metadata::new(), 4.into()))
261            )
262        );
263
264        let c2 = constraints[1].clone();
265        assert_eq!(
266            c2,
267            Expression::Geq(Metadata::new(), Moo::new(x_e), Moo::new(y_e))
268        );
269    }
270
271    #[test]
272    pub fn test_parse_letting_index() {
273        let src = "
274        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) ]
275        find b: int(1..5)
276        such that
277        b < a[-2,2],
278        allDiff(a[-2,..])
279        ";
280
281        let (model, _source_map) = parse_essence(src).unwrap();
282        let st = model.symbols();
283        let a_decl = st.lookup(&Name::user("a")).unwrap();
284        let a = a_decl.as_value_letting().unwrap().deref().clone();
285        assert_eq!(
286            a,
287            matrix_expr!(
288                matrix_expr!(1.into(), 2.into(), 3.into() ; domain_int!(1, 2, 4)),
289                matrix_expr!(1.into(), 3.into(), 2.into() ; domain_int!(1, 2, 4)),
290                matrix_expr!(3.into(), 2.into(), 1.into() ; domain_int!(1, 2, 4));
291                domain_int!(-2..0)
292            )
293        )
294    }
295
296    #[test]
297    pub fn test_parse_pareto_in_dominance_relation() {
298        let src = "
299        find x : int(0..3)
300
301        dominance relation
302            pareto(minimising x)
303        ";
304
305        let (model, _source_map) = parse_essence(src).unwrap();
306        let st = model.symbols();
307        let x = st.lookup(&Name::user("x")).unwrap();
308        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
309        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
310
311        assert_eq!(
312            model.dominance,
313            Some(Expression::DominanceRelation(
314                Metadata::new(),
315                Moo::new(Expression::And(
316                    Metadata::new(),
317                    Moo::new(matrix_expr!(
318                        Expression::Leq(
319                            Metadata::new(),
320                            Moo::new(x_e.clone()),
321                            Moo::new(x_prev.clone())
322                        ),
323                        Expression::Lt(Metadata::new(), Moo::new(x_e), Moo::new(x_prev))
324                    ))
325                ))
326            ))
327        );
328    }
329
330    #[test]
331    pub fn test_parse_pareto_with_mixed_directions() {
332        let src = "
333        find x : int(0..3)
334        find y : int(0..3)
335
336        dominance relation
337            pareto(minimising x, maximising y)
338        ";
339
340        let (model, _source_map) = parse_essence(src).unwrap();
341        let st = model.symbols();
342        let x = st.lookup(&Name::user("x")).unwrap();
343        let y = st.lookup(&Name::user("y")).unwrap();
344        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
345        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y.clone()));
346        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
347        let y_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(y)));
348
349        assert_eq!(
350            model.dominance,
351            Some(Expression::DominanceRelation(
352                Metadata::new(),
353                Moo::new(Expression::And(
354                    Metadata::new(),
355                    Moo::new(matrix_expr!(
356                        Expression::Leq(
357                            Metadata::new(),
358                            Moo::new(x_e.clone()),
359                            Moo::new(x_prev.clone())
360                        ),
361                        Expression::Geq(
362                            Metadata::new(),
363                            Moo::new(y_e.clone()),
364                            Moo::new(y_prev.clone())
365                        ),
366                        Expression::Or(
367                            Metadata::new(),
368                            Moo::new(matrix_expr!(
369                                Expression::Lt(Metadata::new(), Moo::new(x_e), Moo::new(x_prev)),
370                                Expression::Gt(Metadata::new(), Moo::new(y_e), Moo::new(y_prev))
371                            ))
372                        )
373                    ))
374                ))
375            ))
376        );
377    }
378
379    #[test]
380    pub fn test_parse_pareto_over_expression_component() {
381        let src = "
382        find x : int(0..3)
383
384        dominance relation
385            pareto(minimising x + 1)
386        ";
387
388        let (model, _source_map) = parse_essence(src).unwrap();
389        let st = model.symbols();
390        let x = st.lookup(&Name::user("x")).unwrap();
391        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
392        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
393        let one = Expression::Atomic(Metadata::new(), 1.into());
394        let current = Expression::Sum(
395            Metadata::new(),
396            Moo::new(matrix_expr!(x_e.clone(), one.clone())),
397        );
398        let previous = Expression::Sum(Metadata::new(), Moo::new(matrix_expr!(x_prev, one)));
399
400        assert_eq!(
401            model.dominance,
402            Some(Expression::DominanceRelation(
403                Metadata::new(),
404                Moo::new(Expression::And(
405                    Metadata::new(),
406                    Moo::new(matrix_expr!(
407                        Expression::Leq(
408                            Metadata::new(),
409                            Moo::new(current.clone()),
410                            Moo::new(previous.clone())
411                        ),
412                        Expression::Lt(Metadata::new(), Moo::new(current), Moo::new(previous))
413                    ))
414                ))
415            ))
416        );
417    }
418}