conjure_cp_essence_parser/parser/
letting.rs1#![allow(clippy::legacy_numeric_constants)]
2use crate::field;
3use std::collections::BTreeSet;
4use tree_sitter::Node;
5
6use super::ParseContext;
7use super::domain::parse_domain;
8use super::keyword_checks::is_keyword_identifier;
9use super::util::named_children;
10use crate::diagnostics::diagnostics_api::SymbolKind;
11use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
12use crate::errors::{FatalParseError, RecoverableParseError};
13use crate::expression::parse_expression;
14use conjure_cp_core::ast::DeclarationPtr;
15use conjure_cp_core::ast::enumerated::{EnumVariant, EnumeratedType};
16use conjure_cp_core::ast::{Atom, Metadata};
17use conjure_cp_core::ast::{
18 Domain, DomainPtr, Expression, IntVal, Literal, Moo, Name, Range, ReturnType, SymbolTable,
19 Typeable, eval_constant,
20};
21
22fn value_letting_domain(expr: &Expression) -> Option<DomainPtr> {
37 match expr.return_type() {
38 ReturnType::Bool => Some(Domain::bool()),
39 ReturnType::Int => {
40 if let Some(Literal::Int(value)) = eval_constant(expr) {
41 return Some(Domain::int(vec![Range::Single(value)]));
42 }
43 IntVal::new_expr(Moo::new(expr.clone()))
44 .ok()
45 .map(|value| Domain::int(vec![Range::Single(value)]))
46 }
47 _ => expr.domain_of(),
48 }
49}
50
51pub fn parse_letting_statement(
53 ctx: &mut ParseContext,
54 letting_statement: Node,
55) -> Result<Option<SymbolTable>, FatalParseError> {
56 let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
57 return Ok(None);
58 };
59 span_with_hover(
60 &keyword,
61 ctx.source_code,
62 ctx.source_map,
63 HoverInfo {
64 description: "Letting keyword".to_string(),
65 doc_key: None,
66 kind: Some(SymbolKind::Letting),
67 ty: None,
68 decl_span: None,
69 },
70 );
71
72 let mut symbol_table = SymbolTable::new();
73
74 for variable_decl in named_children(&letting_statement) {
75 let mut temp_symbols = BTreeSet::new();
76
77 let Some(variable_list) = field!(recover, ctx, variable_decl, "variable_list") else {
78 return Ok(None);
79 };
80 for variable in named_children(&variable_list) {
81 let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
82
83 if is_keyword_identifier(variable_name) {
84 ctx.errors.push(RecoverableParseError::new(
85 format!("Keyword '{variable_name}' used as identifier"),
86 Some(variable.range()),
87 ));
88 }
90
91 if temp_symbols.contains(variable_name) {
93 ctx.errors.push(RecoverableParseError::new(
94 format!(
95 "Variable '{}' is already declared in this letting statement",
96 variable_name
97 ),
98 Some(variable.range()),
99 ));
100 continue;
102 }
103
104 let name = Name::user(variable_name);
106 if let Some(symbols) = &ctx.symbols
107 && symbols.read().lookup(&name).is_some()
108 {
109 let previous_line = ctx.lookup_decl_line(&name);
110 ctx.errors.push(RecoverableParseError::new(
111 match previous_line {
112 Some(line) => format!(
113 "Variable '{}' is already declared in a previous statement on line {}",
114 variable_name, line
115 ),
116 None => format!(
117 "Variable '{}' is already declared in a previous statement",
118 variable_name
119 ),
120 },
121 Some(variable.range()),
122 ));
123 continue;
125 }
126
127 temp_symbols.insert(variable_name);
128 let hover = HoverInfo {
129 description: format!("Letting variable: {variable_name}"),
130 doc_key: None,
131 kind: Some(SymbolKind::LettingVar),
132 ty: None,
133 decl_span: None,
134 };
135 let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
136 ctx.save_decl_span(name, span_id);
137 }
138
139 let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
140 return Ok(None);
141 };
142
143 if expr_or_domain.kind() == "unnamed_type_declaration" {
144 let Some(size_node) = field!(recover, ctx, expr_or_domain, "size") else {
145 return Ok(None);
146 };
147 let Some(size_expr) = parse_expression(ctx, size_node)? else {
148 return Ok(None);
149 };
150 let Some(Literal::Int(size)) = eval_constant(&size_expr) else {
151 ctx.record_error(RecoverableParseError::new(
152 "Unnamed type size must be a constant integer".to_string(),
153 Some(size_node.range()),
154 ));
155 return Ok(None);
156 };
157 if size < 1 {
158 ctx.record_error(RecoverableParseError::new(
159 "Unnamed type size must be positive".to_string(),
160 Some(size_node.range()),
161 ));
162 return Ok(None);
163 }
164 for name in temp_symbols {
165 symbol_table.insert(DeclarationPtr::new_unnamed(Name::user(name), size as u32));
166 }
167 } else if expr_or_domain.kind() == "enum_type_declaration" {
168 let Some(members) = field!(recover, ctx, expr_or_domain, "members") else {
169 return Ok(None);
170 };
171 let variants = named_children(&members)
172 .map(|node| Name::user(&ctx.source_code[node.start_byte()..node.end_byte()]))
173 .collect::<Vec<_>>();
174 for name in temp_symbols {
175 let ty = Moo::new(EnumeratedType::new(Name::user(name), variants.clone()));
176 for (index, variant) in variants.iter().enumerate() {
177 if symbol_table.lookup(variant).is_some()
178 || ctx
179 .symbols
180 .as_ref()
181 .is_some_and(|symbols| symbols.read().lookup(variant).is_some())
182 || variant == &ty.name
183 {
184 ctx.record_error(RecoverableParseError::new(
185 format!("Enum member '{variant}' is already declared"),
186 Some(members.range()),
187 ));
188 continue;
189 }
190 let value = Expression::Atomic(
191 Metadata::new(),
192 Atom::Literal(Literal::EnumVariant(EnumVariant::new(
193 ty.clone(),
194 index as u32,
195 ))),
196 );
197 symbol_table.insert(DeclarationPtr::new_value_letting(variant.clone(), value));
198 }
199 symbol_table.insert(DeclarationPtr::new_enumerated(Name::user(name), ty));
200 }
201 } else if variable_decl.child_by_field_name("domain").is_some() {
202 for name in temp_symbols {
203 let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
204 continue;
205 };
206
207 symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
208 }
209 } else {
210 for name in temp_symbols {
211 let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
212 continue;
213 };
214 let declaration = match value_letting_domain(&expr) {
215 Some(domain) => DeclarationPtr::new_value_letting_with_domain(
216 Name::user(name),
217 expr,
218 domain,
219 ),
220 None => DeclarationPtr::new_value_letting(Name::user(name), expr),
221 };
222 symbol_table.insert(declaration);
223 }
224 }
225 }
226
227 Ok(Some(symbol_table))
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::parser::parse_model::parse_essence;
234 use conjure_cp_core::ast::{DeclarationKind, Name};
235 use std::ops::Deref;
236 use std::time::{Duration, Instant};
237
238 #[test]
241 fn arithmetic_letting_over_bare_int_givens_uses_symbolic_domain() {
242 let src = r#"
243language ESSENCE' 1.0
244given v, k, l : int
245letting b be (l*v*(v-1))/(k*(k-1))
246find x: bool
247such that x
248"#;
249 let started = Instant::now();
250 let (model, _) = parse_essence(src).expect("model should parse");
251 assert!(
252 started.elapsed() < Duration::from_secs(2),
253 "parse hung for {:?}; eager domain_of over bare int is likely back",
254 started.elapsed()
255 );
256
257 let symbols = model.symbols();
258 let decl = symbols
259 .lookup(&Name::user("b"))
260 .expect("letting b should be in the symbol table");
261 match decl.kind().deref() {
262 DeclarationKind::ValueLetting(_, Some(domain)) => {
263 let ranges = domain.as_int().expect("letting b domain should be int");
264 assert_eq!(
265 ranges.len(),
266 1,
267 "expected a singleton domain, got {ranges:?}"
268 );
269 assert!(
270 matches!(&ranges[0], Range::Single(IntVal::Expr(_))),
271 "expected symbolic IntVal::Expr singleton, got {:?}",
272 ranges[0]
273 );
274 }
275 other => panic!("expected ValueLetting with retained domain, got {other:?}"),
276 }
277 }
278
279 #[test]
281 fn constant_int_letting_keeps_ground_singleton_domain() {
282 let src = r#"
283language ESSENCE' 1.0
284letting n be 3
285find x: int(1..10**n)
286such that x = 1
287"#;
288 let (model, _) = parse_essence(src).expect("model should parse");
289 let symbols = model.symbols();
290 let decl = symbols
291 .lookup(&Name::user("n"))
292 .expect("letting n should be in the symbol table");
293 match decl.kind().deref() {
294 DeclarationKind::ValueLetting(_, Some(domain)) => {
295 let ranges = domain.as_int().expect("letting n domain should be int");
296 assert_eq!(
297 ranges.len(),
298 1,
299 "expected a singleton domain, got {ranges:?}"
300 );
301 assert!(
302 matches!(&ranges[0], Range::Single(IntVal::Const(3))),
303 "expected ground IntVal::Const(3) singleton, got {:?}",
304 ranges[0]
305 );
306 }
307 other => panic!("expected ValueLetting with retained domain, got {other:?}"),
308 }
309 }
310}