conjure_cp_essence_parser/parser/
parse_model.rs1use std::fs;
2use std::sync::{Arc, RwLock};
3
4use conjure_cp_core::Model;
5use conjure_cp_core::ast::{DeclarationPtr, Expression, Metadata, Moo};
6use conjure_cp_core::context::Context;
7#[allow(unused)]
8use uniplate::Uniplate;
9
10use super::find::parse_find_statement;
11use super::letting::parse_letting_statement;
12use super::util::{get_tree, named_children};
13use crate::errors::EssenceParseError;
14use crate::expression::parse_expression;
15
16pub fn parse_essence_file_native(
18 path: &str,
19 context: Arc<RwLock<Context<'static>>>,
20) -> Result<Model, EssenceParseError> {
21 let source_code = fs::read_to_string(path)
22 .unwrap_or_else(|_| panic!("Failed to read the source code file {path}"));
23 parse_essence_with_context(&source_code, context)
24}
25
26pub fn parse_essence_with_context(
27 src: &str,
28 context: Arc<RwLock<Context<'static>>>,
29) -> Result<Model, EssenceParseError> {
30 let (tree, source_code) = match get_tree(src) {
31 Some(tree) => tree,
32 None => {
33 return Err(EssenceParseError::TreeSitterError(
34 "Failed to parse source code".to_string(),
35 ));
36 }
37 };
38
39 let mut model = Model::new(context);
40 let root_node = tree.root_node();
42 let symbols_ptr = model.as_submodel().symbols_ptr_unchecked().clone();
43 for statement in named_children(&root_node) {
44 match statement.kind() {
45 "single_line_comment" => {}
46 "language_declaration" => {}
47 "find_statement" => {
48 let var_hashmap =
49 parse_find_statement(statement, &source_code, Some(symbols_ptr.clone()))?;
50 for (name, domain) in var_hashmap {
51 model
52 .as_submodel_mut()
53 .symbols_mut()
54 .insert(DeclarationPtr::new_var(name, domain));
55 }
56 }
57 "bool_expr" | "atom" | "comparison_expr" => {
58 model.as_submodel_mut().add_constraint(parse_expression(
59 statement,
60 &source_code,
61 &statement,
62 Some(symbols_ptr.clone()),
63 )?);
64 }
65 "language_label" => {}
66 "letting_statement" => {
67 let letting_vars =
68 parse_letting_statement(statement, &source_code, Some(symbols_ptr.clone()))?;
69 model.as_submodel_mut().symbols_mut().extend(letting_vars);
70 }
71 "dominance_relation" => {
72 let inner = statement
73 .child_by_field_name("expression")
74 .expect("Expected a sub-expression inside `dominanceRelation`");
75 let expr =
76 parse_expression(inner, &source_code, &statement, Some(symbols_ptr.clone()))?;
77 let dominance = Expression::DominanceRelation(Metadata::new(), Moo::new(expr));
78 if model.dominance.is_some() {
79 return Err(EssenceParseError::syntax_error(
80 "Duplicate dominance relation".to_string(),
81 None,
82 ));
83 }
84 model.dominance = Some(dominance);
85 }
86 "ERROR" => {
87 let raw_expr = &source_code[statement.start_byte()..statement.end_byte()];
88 return Err(EssenceParseError::syntax_error(
89 format!("'{raw_expr}' is not a valid expression"),
90 Some(statement.range()),
91 ));
92 }
93 _ => {
94 let kind = statement.kind();
95 return Err(EssenceParseError::syntax_error(
96 format!("Unrecognized top level statement kind: {kind}"),
97 Some(statement.range()),
98 ));
99 }
100 }
101
102 let result = keyword_as_identifier(root_node, &source_code);
104 result?
105 }
106 Ok(model)
107}
108
109const KEYWORDS: [&str; 21] = [
110 "forall", "exists", "such", "that", "letting", "find", "minimise", "maximise", "subject", "to",
111 "where", "and", "or", "not", "if", "then", "else", "in", "sum", "product", "bool",
112];
113
114fn keyword_as_identifier(root: tree_sitter::Node, src: &str) -> Result<(), EssenceParseError> {
115 let mut stack = vec![root];
116 while let Some(node) = stack.pop() {
117 if (node.kind() == "variable" || node.kind() == "identifier" || node.kind() == "parameter")
118 && let Ok(text) = node.utf8_text(src.as_bytes())
119 {
120 let ident = text.trim();
121 if KEYWORDS.contains(&ident) {
122 let start_point = node.start_position();
123 let end_point = node.end_position();
124 return Err(EssenceParseError::syntax_error(
125 format!("Keyword '{ident}' used as identifier"),
126 Some(tree_sitter::Range {
127 start_byte: node.start_byte(),
128 end_byte: node.end_byte(),
129 start_point,
130 end_point,
131 }),
132 ));
133 }
134 }
135
136 for i in 0..node.child_count() {
138 if let Some(child) = node.child(i) {
139 stack.push(child);
140 }
141 }
142 }
143 Ok(())
144}
145
146pub fn parse_essence(src: &str) -> Result<Model, EssenceParseError> {
147 let context = Arc::new(RwLock::new(Context::default()));
148 parse_essence_with_context(src, context)
149}
150
151mod test {
152 #[allow(unused_imports)]
153 use crate::parse_essence;
154 #[allow(unused_imports)]
155 use conjure_cp_core::ast::{Atom, Expression, Metadata, Moo, Name};
156 #[allow(unused_imports)]
157 use conjure_cp_core::{domain_int, matrix_expr, range};
158 #[allow(unused_imports)]
159 use std::ops::Deref;
160
161 #[test]
162 pub fn test_parse_xyz() {
163 let src = "
164 find x, y, z : int(1..4)
165 such that x + y + z = 4
166 such that x >= y
167 ";
168
169 let model = parse_essence(src).unwrap();
170
171 let st = model.as_submodel().symbols();
172 let x = st.lookup(&Name::user("x")).unwrap();
173 let y = st.lookup(&Name::user("y")).unwrap();
174 let z = st.lookup(&Name::user("z")).unwrap();
175 assert_eq!(x.domain(), Some(domain_int!(1..4)));
176 assert_eq!(y.domain(), Some(domain_int!(1..4)));
177 assert_eq!(z.domain(), Some(domain_int!(1..4)));
178
179 let constraints = model.as_submodel().constraints();
180 assert_eq!(constraints.len(), 2);
181
182 let c1 = constraints[0].clone();
183 let x_e = Expression::Atomic(Metadata::new(), Atom::new_ref(x));
184 let y_e = Expression::Atomic(Metadata::new(), Atom::new_ref(y));
185 let z_e = Expression::Atomic(Metadata::new(), Atom::new_ref(z));
186 assert_eq!(
187 c1,
188 Expression::Eq(
189 Metadata::new(),
190 Moo::new(Expression::Sum(
191 Metadata::new(),
192 Moo::new(matrix_expr!(
193 Expression::Sum(
194 Metadata::new(),
195 Moo::new(matrix_expr!(x_e.clone(), y_e.clone()))
196 ),
197 z_e
198 ))
199 )),
200 Moo::new(Expression::Atomic(Metadata::new(), 4.into()))
201 )
202 );
203
204 let c2 = constraints[1].clone();
205 assert_eq!(
206 c2,
207 Expression::Geq(Metadata::new(), Moo::new(x_e), Moo::new(y_e))
208 );
209 }
210
211 #[test]
212 pub fn test_parse_letting_index() {
213 let src = "
214 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) ]
215 find b: int(1..5)
216 such that
217 b < a[-2,2],
218 allDiff(a[-2,..])
219 ";
220
221 let model = parse_essence(src).unwrap();
222 let st = model.as_submodel().symbols();
223 let a_decl = st.lookup(&Name::user("a")).unwrap();
224 let a = a_decl.as_value_letting().unwrap().deref().clone();
225 assert_eq!(
226 a,
227 matrix_expr!(
228 matrix_expr!(1.into(), 2.into(), 3.into() ; domain_int!(1, 2, 4)),
229 matrix_expr!(1.into(), 3.into(), 2.into() ; domain_int!(1, 2, 4)),
230 matrix_expr!(3.into(), 2.into(), 1.into() ; domain_int!(1, 2, 4));
231 domain_int!(-2..0)
232 )
233 )
234 }
235}