conjure_cp_essence_parser/parser/
parse_model.rs1use 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
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)? {
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 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 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 keyword_as_identifier(*ctx.root, ctx.source_code, ctx.errors);
184
185 if !errors.is_empty() {
187 return Ok(None);
188 }
189 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 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}