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::dominance::parse_dominance_relation;
14use super::find::{parse_find_statement, parse_given_statement};
15use super::letting::parse_letting_statement;
16use super::util::{TypecheckingContext, get_tree};
17use crate::diagnostics::source_map::SourceMap;
18use crate::errors::{FatalParseError, ParseErrorCollection, RecoverableParseError};
19use crate::expression::parse_expression;
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    let mut model = Model::new(context);
105    let mut source_map = SourceMap::default();
106    let mut declaration_spans = BTreeMap::new();
107    let root_node = tree.root_node();
108
109    // Create a ParseContext
110    let mut ctx = ParseContext::new(
111        &source_code,
112        &root_node,
113        Some(model.symbols_ptr_unchecked().clone()),
114        semantic_errors,
115        &mut source_map,
116        &mut declaration_spans,
117    );
118
119    let mut cursor = root_node.walk();
120    for statement in root_node.children(&mut cursor) {
121        if !statement.is_named() || statement.is_error() || statement.kind() == "ERROR" {
122            continue;
123        }
124
125        ctx.typechecking_context = TypecheckingContext::Unknown;
126        ctx.inner_typechecking_context = TypecheckingContext::Unknown;
127
128        match statement.kind() {
129            "single_line_comment" => {}
130            "language_declaration" => {}
131            "find_statement" => {
132                let var_hashmap = parse_find_statement(&mut ctx, statement)?;
133                for (name, domain) in var_hashmap {
134                    model
135                        .symbols_mut()
136                        .insert(DeclarationPtr::new_find(name, domain));
137                }
138            }
139            "given_statement" => {
140                let var_hashmap = parse_given_statement(&mut ctx, statement)?;
141                for (name, domain) in var_hashmap {
142                    model
143                        .symbols_mut()
144                        .insert(DeclarationPtr::new_given(name, domain));
145                }
146            }
147            "bool_expr" | "atom" | "comparison_expr" => {
148                ctx.typechecking_context = TypecheckingContext::Boolean;
149                let Some(expr) = parse_expression(&mut ctx, statement)? else {
150                    continue;
151                };
152                model.add_constraint(expr);
153            }
154            "language_label" => {}
155            "letting_statement" => {
156                let Some(letting_vars) = parse_letting_statement(&mut ctx, statement)? else {
157                    continue;
158                };
159                model.symbols_mut().extend(letting_vars);
160            }
161            "dominance_relation" => {
162                let Some(dominance) = parse_dominance_relation(&mut ctx, &statement)? else {
163                    continue;
164                };
165                if model.dominance.is_some() {
166                    ctx.record_error(RecoverableParseError::new(
167                        "Duplicate dominance relation".to_string(),
168                        None,
169                    ));
170                    continue;
171                }
172                model.dominance = Some(dominance);
173            }
174            _ => {
175                ctx.record_error(RecoverableParseError::new(
176                    format!("Unexpected top-level statement: {}", statement.kind()),
177                    Some(statement.range()),
178                ));
179                continue;
180            }
181        }
182    }
183
184    // Check if there were any recoverable errors
185    if !errors.is_empty() {
186        return Ok((None, source_map));
187    }
188    // otherwise return the model
189    Ok((Some(model), source_map))
190}
191
192pub fn parse_essence(src: &str) -> Result<(Model, SourceMap), Box<ParseErrorCollection>> {
193    let context = Arc::new(RwLock::new(Context::default()));
194    let mut errors = vec![];
195    match parse_essence_with_context_and_map(src, context, &mut errors, None) {
196        Ok((Some(model), source_map)) => {
197            debug_assert_model_well_formed(&model, "tree-sitter");
198            Ok((model, source_map))
199        }
200        Ok((None, _source_map)) => {
201            // Recoverable errors were found, return them as a ParseErrorCollection
202            Err(Box::new(ParseErrorCollection::multiple(
203                errors,
204                Some(src.to_string()),
205                None,
206            )))
207        }
208        Err(fatal) => Err(Box::new(ParseErrorCollection::fatal(fatal))),
209    }
210}
211
212mod test {
213    #[allow(unused_imports)]
214    use crate::parse_essence;
215    #[allow(unused_imports)]
216    use conjure_cp_core::ast::{Atom, Expression, Metadata, Moo, Name};
217    #[allow(unused_imports)]
218    use conjure_cp_core::{domain_int, matrix_expr, range};
219    #[allow(unused_imports)]
220    use std::ops::Deref;
221
222    #[test]
223    pub fn test_parse_xyz() {
224        let src = "
225        find x, y, z : int(1..4)
226        such that x + y + z = 4
227        such that x >= y
228        ";
229
230        let (model, _source_map) = parse_essence(src).unwrap();
231
232        let st = model.symbols();
233        let x = st.lookup(&Name::user("x")).unwrap();
234        let y = st.lookup(&Name::user("y")).unwrap();
235        let z = st.lookup(&Name::user("z")).unwrap();
236        assert_eq!(x.domain(), Some(domain_int!(1..4)));
237        assert_eq!(y.domain(), Some(domain_int!(1..4)));
238        assert_eq!(z.domain(), Some(domain_int!(1..4)));
239
240        let constraints = model.constraints();
241        assert_eq!(constraints.len(), 2);
242
243        let c1 = constraints[0].clone();
244        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x));
245        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y));
246        let z_e = Expression::Atomic(Metadata::new(), Atom::new_ref(z));
247        assert_eq!(
248            c1,
249            Expression::Eq(
250                Metadata::new(),
251                Moo::new(Expression::Sum(
252                    Metadata::new(),
253                    Moo::new(matrix_expr!(
254                        Expression::Sum(
255                            Metadata::new(),
256                            Moo::new(matrix_expr!(x_e.clone(), y_e.clone()))
257                        ),
258                        z_e
259                    ))
260                )),
261                Moo::new(Expression::Atomic(Metadata::new(), 4.into()))
262            )
263        );
264
265        let c2 = constraints[1].clone();
266        assert_eq!(
267            c2,
268            Expression::Geq(Metadata::new(), Moo::new(x_e), Moo::new(y_e))
269        );
270    }
271
272    #[test]
273    pub fn test_parse_letting_index() {
274        let src = "
275        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) ]
276        find b: int(1..5)
277        such that
278        b < a[-2,2],
279        allDiff(a[-2,..])
280        ";
281
282        let (model, _source_map) = parse_essence(src).unwrap();
283        let st = model.symbols();
284        let a_decl = st.lookup(&Name::user("a")).unwrap();
285        let a = a_decl.as_value_letting().unwrap().deref().clone();
286        assert_eq!(
287            a,
288            matrix_expr!(
289                matrix_expr!(1.into(), 2.into(), 3.into() ; domain_int!(1, 2, 4)),
290                matrix_expr!(1.into(), 3.into(), 2.into() ; domain_int!(1, 2, 4)),
291                matrix_expr!(3.into(), 2.into(), 1.into() ; domain_int!(1, 2, 4));
292                domain_int!(-2..0)
293            )
294        )
295    }
296
297    #[test]
298    pub fn test_parse_pareto_in_dominance_relation() {
299        let src = "
300        find x : int(0..3)
301
302        dominance relation
303            pareto(minimising x)
304        ";
305
306        let (model, _source_map) = parse_essence(src).unwrap();
307        let st = model.symbols();
308        let x = st.lookup(&Name::user("x")).unwrap();
309        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
310        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
311
312        assert_eq!(
313            model.dominance,
314            Some(Expression::DominanceRelation(
315                Metadata::new(),
316                Moo::new(Expression::And(
317                    Metadata::new(),
318                    Moo::new(matrix_expr!(
319                        Expression::Leq(
320                            Metadata::new(),
321                            Moo::new(x_e.clone()),
322                            Moo::new(x_prev.clone())
323                        ),
324                        Expression::Lt(Metadata::new(), Moo::new(x_e), Moo::new(x_prev))
325                    ))
326                ))
327            ))
328        );
329    }
330
331    #[test]
332    pub fn test_parse_pareto_with_mixed_directions() {
333        let src = "
334        find x : int(0..3)
335        find y : int(0..3)
336
337        dominance relation
338            pareto(minimising x, maximising y)
339        ";
340
341        let (model, _source_map) = parse_essence(src).unwrap();
342        let st = model.symbols();
343        let x = st.lookup(&Name::user("x")).unwrap();
344        let y = st.lookup(&Name::user("y")).unwrap();
345        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
346        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y.clone()));
347        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
348        let y_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(y)));
349
350        assert_eq!(
351            model.dominance,
352            Some(Expression::DominanceRelation(
353                Metadata::new(),
354                Moo::new(Expression::And(
355                    Metadata::new(),
356                    Moo::new(matrix_expr!(
357                        Expression::Leq(
358                            Metadata::new(),
359                            Moo::new(x_e.clone()),
360                            Moo::new(x_prev.clone())
361                        ),
362                        Expression::Geq(
363                            Metadata::new(),
364                            Moo::new(y_e.clone()),
365                            Moo::new(y_prev.clone())
366                        ),
367                        Expression::Or(
368                            Metadata::new(),
369                            Moo::new(matrix_expr!(
370                                Expression::Lt(Metadata::new(), Moo::new(x_e), Moo::new(x_prev)),
371                                Expression::Gt(Metadata::new(), Moo::new(y_e), Moo::new(y_prev))
372                            ))
373                        )
374                    ))
375                ))
376            ))
377        );
378    }
379
380    #[test]
381    pub fn test_parse_pareto_over_expression_component() {
382        let src = "
383        find x : int(0..3)
384
385        dominance relation
386            pareto(minimising x + 1)
387        ";
388
389        let (model, _source_map) = parse_essence(src).unwrap();
390        let st = model.symbols();
391        let x = st.lookup(&Name::user("x")).unwrap();
392        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x.clone()));
393        let x_prev = Expression::FromSolution(Metadata::new(), Moo::new(Atom::new_ref(x)));
394        let one = Expression::Atomic(Metadata::new(), 1.into());
395        let current = Expression::Sum(
396            Metadata::new(),
397            Moo::new(matrix_expr!(x_e.clone(), one.clone())),
398        );
399        let previous = Expression::Sum(Metadata::new(), Moo::new(matrix_expr!(x_prev, one)));
400
401        assert_eq!(
402            model.dominance,
403            Some(Expression::DominanceRelation(
404                Metadata::new(),
405                Moo::new(Expression::And(
406                    Metadata::new(),
407                    Moo::new(matrix_expr!(
408                        Expression::Leq(
409                            Metadata::new(),
410                            Moo::new(current.clone()),
411                            Moo::new(previous.clone())
412                        ),
413                        Expression::Lt(Metadata::new(), Moo::new(current), Moo::new(previous))
414                    ))
415                ))
416            ))
417        );
418    }
419}