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
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 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 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 if !errors.is_empty() {
186 return Ok((None, source_map));
187 }
188 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 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}