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

            
4
use conjure_cp_core::Model;
5
use conjure_cp_core::ast::assertions::debug_assert_model_well_formed;
6
use conjure_cp_core::ast::{DeclarationPtr, Expression, Metadata, Moo};
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::keyword_checks::keyword_as_identifier;
14
use super::letting::parse_letting_statement;
15
use super::util::{TypecheckingContext, get_tree};
16
use crate::diagnostics::diagnostics_api::SymbolKind;
17
use crate::diagnostics::source_map::{HoverInfo, SourceMap, span_with_hover};
18
use crate::errors::{FatalParseError, ParseErrorCollection, RecoverableParseError};
19
use crate::expression::parse_expression;
20
use crate::field;
21
use crate::syntax_errors::detect_syntactic_errors;
22

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

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

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

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

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

            
79
1292
    if tree.root_node().has_error() {
80
554
        detect_syntactic_errors(src, &tree, errors);
81
554
        return Ok(None);
82
738
    }
83

            
84
738
    let mut model = Model::new(context);
85
738
    let mut source_map = SourceMap::default();
86
738
    let root_node = tree.root_node();
87

            
88
    // Create a ParseContext
89
738
    let mut ctx = ParseContext::new(
90
738
        &source_code,
91
738
        &root_node,
92
738
        Some(model.symbols_ptr_unchecked().clone()),
93
738
        errors,
94
738
        &mut source_map,
95
    );
96

            
97
738
    let mut cursor = root_node.walk();
98
3873
    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
3873
        if statement.kind() == "find" {
105
837
            span_with_hover(
106
837
                &statement,
107
837
                ctx.source_code,
108
837
                ctx.source_map,
109
837
                HoverInfo {
110
837
                    description: "Find keyword".to_string(),
111
837
                    kind: Some(SymbolKind::Find),
112
837
                    ty: None,
113
837
                    decl_span: None,
114
837
                },
115
837
            );
116
3036
        } else if statement.kind() == "letting" {
117
419
            span_with_hover(
118
419
                &statement,
119
419
                ctx.source_code,
120
419
                ctx.source_map,
121
419
                HoverInfo {
122
419
                    description: "Letting keyword".to_string(),
123
419
                    kind: Some(SymbolKind::Letting),
124
419
                    ty: None,
125
419
                    decl_span: None,
126
419
                },
127
419
            );
128
2617
        }
129

            
130
3873
        if !statement.is_named() {
131
1931
            continue;
132
1942
        }
133

            
134
1942
        match statement.kind() {
135
1942
            "single_line_comment" => {}
136
1942
            "language_declaration" => {}
137
1931
            "find_statement" => {
138
837
                let var_hashmap = parse_find_statement(&mut ctx, statement)?;
139
927
                for (name, domain) in var_hashmap {
140
927
                    model
141
927
                        .symbols_mut()
142
927
                        .insert(DeclarationPtr::new_find(name, domain));
143
927
                }
144
            }
145
1094
            "bool_expr" | "atom" | "comparison_expr" => {
146
675
                ctx.typechecking_context = TypecheckingContext::Boolean;
147
675
                let Some(expr) = parse_expression(&mut ctx, statement)? else {
148
176
                    continue;
149
                };
150
499
                model.add_constraint(expr);
151
            }
152
419
            "language_label" => {}
153
419
            "letting_statement" => {
154
419
                let Some(letting_vars) = parse_letting_statement(&mut ctx, statement)? else {
155
                    continue;
156
                };
157
419
                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
738
    keyword_as_identifier(*ctx.root, ctx.source_code, ctx.errors);
184

            
185
    // Check if there were any recoverable errors
186
738
    if !errors.is_empty() {
187
319
        return Ok(None);
188
419
    }
189
    // otherwise return the model
190
419
    Ok(Some((model, source_map)))
191
1292
}
192

            
193
2
pub fn parse_essence(src: &str) -> Result<(Model, SourceMap), Box<ParseErrorCollection>> {
194
2
    let context = Arc::new(RwLock::new(Context::default()));
195
2
    let mut errors = vec![];
196
2
    match parse_essence_with_context_and_map(src, context, &mut errors) {
197
2
        Ok(Some((model, source_map))) => {
198
2
            debug_assert_model_well_formed(&model, "tree-sitter");
199
2
            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
2
}
212

            
213
mod 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
1
    pub fn test_parse_xyz() {
225
1
        let src = "
226
1
        find x, y, z : int(1..4)
227
1
        such that x + y + z = 4
228
1
        such that x >= y
229
1
        ";
230

            
231
1
        let (model, _source_map) = parse_essence(src).unwrap();
232

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

            
241
1
        let constraints = model.constraints();
242
1
        assert_eq!(constraints.len(), 2);
243

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

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

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

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