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
23pub 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 Err(Box::new(ParseErrorCollection::multiple(
42 errors,
43 Some(source_code),
44 Some(path.to_string()),
45 )))
46 }
47 Err(fatal) => {
48 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
65pub 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 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 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 if !errors.is_empty() {
185 return Ok((None, source_map));
186 }
187 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 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}