Skip to main content

conjure_cp_essence_parser/parser/
parse_model.rs

1use std::sync::{Arc, RwLock};
2use std::{fs, vec};
3
4use conjure_cp_core::Model;
5use conjure_cp_core::ast::assertions::debug_assert_model_well_formed;
6use conjure_cp_core::ast::{DeclarationPtr, Expression, Metadata, Moo};
7use conjure_cp_core::context::Context;
8#[allow(unused)]
9use uniplate::Uniplate;
10
11use super::ParseContext;
12use super::find::parse_find_statement;
13use super::keyword_checks::keyword_as_identifier;
14use super::letting::parse_letting_statement;
15use super::util::{TypecheckingContext, get_tree};
16use crate::diagnostics::diagnostics_api::SymbolKind;
17use crate::diagnostics::source_map::{HoverInfo, SourceMap, span_with_hover};
18use crate::errors::{FatalParseError, ParseErrorCollection, RecoverableParseError};
19use crate::expression::parse_expression;
20use crate::field;
21use crate::syntax_errors::detect_syntactic_errors;
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)? {
60        Some((model, _source_map)) => Ok(Some(model)),
61        None => Ok(None),
62    }
63}
64
65pub fn parse_essence_with_context_and_map(
66    src: &str,
67    context: Arc<RwLock<Context<'static>>>,
68    errors: &mut Vec<RecoverableParseError>,
69) -> Result<Option<(Model, SourceMap)>, FatalParseError> {
70    let (tree, source_code) = match get_tree(src) {
71        Some(tree) => tree,
72        None => {
73            return Err(FatalParseError::TreeSitterError(
74                "Failed to parse source code".to_string(),
75            ));
76        }
77    };
78
79    if tree.root_node().has_error() {
80        detect_syntactic_errors(src, &tree, errors);
81        return Ok(None);
82    }
83
84    let mut model = Model::new(context);
85    let mut source_map = SourceMap::default();
86    let root_node = tree.root_node();
87
88    // Create a ParseContext
89    let mut ctx = ParseContext::new(
90        &source_code,
91        &root_node,
92        Some(model.symbols_ptr_unchecked().clone()),
93        errors,
94        &mut source_map,
95    );
96
97    let mut cursor = root_node.walk();
98    for statement in root_node.children(&mut cursor) {
99        /*
100           since find and letting are unnamed children
101           hover info is added here.
102           other unnamed children will be skipped.
103        */
104        if statement.kind() == "find" {
105            span_with_hover(
106                &statement,
107                ctx.source_code,
108                ctx.source_map,
109                HoverInfo {
110                    description: "Find keyword".to_string(),
111                    kind: Some(SymbolKind::Find),
112                    ty: None,
113                    decl_span: None,
114                },
115            );
116        } else if statement.kind() == "letting" {
117            span_with_hover(
118                &statement,
119                ctx.source_code,
120                ctx.source_map,
121                HoverInfo {
122                    description: "Letting keyword".to_string(),
123                    kind: Some(SymbolKind::Letting),
124                    ty: None,
125                    decl_span: None,
126                },
127            );
128        }
129
130        if !statement.is_named() {
131            continue;
132        }
133
134        match statement.kind() {
135            "single_line_comment" => {}
136            "language_declaration" => {}
137            "find_statement" => {
138                let var_hashmap = parse_find_statement(&mut ctx, statement)?;
139                for (name, domain) in var_hashmap {
140                    model
141                        .symbols_mut()
142                        .insert(DeclarationPtr::new_find(name, domain));
143                }
144            }
145            "bool_expr" | "atom" | "comparison_expr" => {
146                ctx.typechecking_context = TypecheckingContext::Boolean;
147                let Some(expr) = parse_expression(&mut ctx, statement)? else {
148                    continue;
149                };
150                model.add_constraint(expr);
151            }
152            "language_label" => {}
153            "letting_statement" => {
154                let Some(letting_vars) = parse_letting_statement(&mut ctx, statement)? else {
155                    continue;
156                };
157                model.symbols_mut().extend(letting_vars);
158            }
159            "dominance_relation" => {
160                let inner = field!(statement, "expression");
161                let Some(expr) = parse_expression(&mut ctx, inner)? else {
162                    continue;
163                };
164                let dominance = Expression::DominanceRelation(Metadata::new(), Moo::new(expr));
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                return Err(FatalParseError::internal_error(
176                    format!("Unexpected top-level statement: {}", statement.kind()),
177                    Some(statement.range()),
178                ));
179            }
180        }
181    }
182    // check for errors (keyword as identifier)
183    keyword_as_identifier(*ctx.root, ctx.source_code, ctx.errors);
184
185    // Check if there were any recoverable errors
186    if !errors.is_empty() {
187        return Ok(None);
188    }
189    // otherwise return the model
190    Ok(Some((model, source_map)))
191}
192
193pub fn parse_essence(src: &str) -> Result<(Model, SourceMap), Box<ParseErrorCollection>> {
194    let context = Arc::new(RwLock::new(Context::default()));
195    let mut errors = vec![];
196    match parse_essence_with_context_and_map(src, context, &mut errors) {
197        Ok(Some((model, source_map))) => {
198            debug_assert_model_well_formed(&model, "tree-sitter");
199            Ok((model, source_map))
200        }
201        Ok(None) => {
202            // Recoverable errors were found, return them as a ParseErrorCollection
203            Err(Box::new(ParseErrorCollection::multiple(
204                errors,
205                Some(src.to_string()),
206                None,
207            )))
208        }
209        Err(fatal) => Err(Box::new(ParseErrorCollection::fatal(fatal))),
210    }
211}
212
213mod test {
214    #[allow(unused_imports)]
215    use crate::parse_essence;
216    #[allow(unused_imports)]
217    use conjure_cp_core::ast::{Atom, Expression, Metadata, Moo, Name};
218    #[allow(unused_imports)]
219    use conjure_cp_core::{domain_int, matrix_expr, range};
220    #[allow(unused_imports)]
221    use std::ops::Deref;
222
223    #[test]
224    pub fn test_parse_xyz() {
225        let src = "
226        find x, y, z : int(1..4)
227        such that x + y + z = 4
228        such that x >= y
229        ";
230
231        let (model, _source_map) = parse_essence(src).unwrap();
232
233        let st = model.symbols();
234        let x = st.lookup(&Name::user("x")).unwrap();
235        let y = st.lookup(&Name::user("y")).unwrap();
236        let z = st.lookup(&Name::user("z")).unwrap();
237        assert_eq!(x.domain(), Some(domain_int!(1..4)));
238        assert_eq!(y.domain(), Some(domain_int!(1..4)));
239        assert_eq!(z.domain(), Some(domain_int!(1..4)));
240
241        let constraints = model.constraints();
242        assert_eq!(constraints.len(), 2);
243
244        let c1 = constraints[0].clone();
245        let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x));
246        let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y));
247        let z_e = Expression::Atomic(Metadata::new(), Atom::new_ref(z));
248        assert_eq!(
249            c1,
250            Expression::Eq(
251                Metadata::new(),
252                Moo::new(Expression::Sum(
253                    Metadata::new(),
254                    Moo::new(matrix_expr!(
255                        Expression::Sum(
256                            Metadata::new(),
257                            Moo::new(matrix_expr!(x_e.clone(), y_e.clone()))
258                        ),
259                        z_e
260                    ))
261                )),
262                Moo::new(Expression::Atomic(Metadata::new(), 4.into()))
263            )
264        );
265
266        let c2 = constraints[1].clone();
267        assert_eq!(
268            c2,
269            Expression::Geq(Metadata::new(), Moo::new(x_e), Moo::new(y_e))
270        );
271    }
272
273    #[test]
274    pub fn test_parse_letting_index() {
275        let src = "
276        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) ]
277        find b: int(1..5)
278        such that
279        b < a[-2,2],
280        allDiff(a[-2,..])
281        ";
282
283        let (model, _source_map) = parse_essence(src).unwrap();
284        let st = model.symbols();
285        let a_decl = st.lookup(&Name::user("a")).unwrap();
286        let a = a_decl.as_value_letting().unwrap().deref().clone();
287        assert_eq!(
288            a,
289            matrix_expr!(
290                matrix_expr!(1.into(), 2.into(), 3.into() ; domain_int!(1, 2, 4)),
291                matrix_expr!(1.into(), 3.into(), 2.into() ; domain_int!(1, 2, 4)),
292                matrix_expr!(3.into(), 2.into(), 1.into() ; domain_int!(1, 2, 4));
293                domain_int!(-2..0)
294            )
295        )
296    }
297}