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::{
16 Domain, DomainPtr, Expression, IntVal, Literal, Moo, Name, Range, ReturnType, SymbolTable,
17 Typeable, eval_constant,
18};
19
20fn value_letting_domain(expr: &Expression) -> Option<DomainPtr> {
36 match expr.return_type() {
37 ReturnType::Bool => Some(Domain::bool()),
38 ReturnType::Int => {
39 if let Some(Literal::Int(value)) = eval_constant(expr) {
40 return Some(Domain::int(vec![Range::Single(value)]));
41 }
42 IntVal::new_expr(Moo::new(expr.clone()))
43 .ok()
44 .map(|value| Domain::int(vec![Range::Single(value)]))
45 }
46 _ => expr.domain_of(),
47 }
48}
49
50pub fn parse_letting_statement(
52 ctx: &mut ParseContext,
53 letting_statement: Node,
54) -> Result<Option<SymbolTable>, FatalParseError> {
55 let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
56 return Ok(None);
57 };
58 span_with_hover(
59 &keyword,
60 ctx.source_code,
61 ctx.source_map,
62 HoverInfo {
63 description: "Letting keyword".to_string(),
64 doc_key: None,
65 kind: Some(SymbolKind::Letting),
66 ty: None,
67 decl_span: None,
68 },
69 );
70
71 let mut symbol_table = SymbolTable::new();
72
73 for variable_decl in named_children(&letting_statement) {
74 let mut temp_symbols = BTreeSet::new();
75
76 let Some(variable_list) = field!(recover, ctx, variable_decl, "variable_list") else {
77 return Ok(None);
78 };
79 for variable in named_children(&variable_list) {
80 let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
81
82 if is_keyword_identifier(variable_name) {
83 ctx.errors.push(RecoverableParseError::new(
84 format!("Keyword '{variable_name}' used as identifier"),
85 Some(variable.range()),
86 ));
87 }
89
90 if temp_symbols.contains(variable_name) {
92 ctx.errors.push(RecoverableParseError::new(
93 format!(
94 "Variable '{}' is already declared in this letting statement",
95 variable_name
96 ),
97 Some(variable.range()),
98 ));
99 continue;
101 }
102
103 let name = Name::user(variable_name);
105 if let Some(symbols) = &ctx.symbols
106 && symbols.read().lookup(&name).is_some()
107 {
108 let previous_line = ctx.lookup_decl_line(&name);
109 ctx.errors.push(RecoverableParseError::new(
110 match previous_line {
111 Some(line) => format!(
112 "Variable '{}' is already declared in a previous statement on line {}",
113 variable_name, line
114 ),
115 None => format!(
116 "Variable '{}' is already declared in a previous statement",
117 variable_name
118 ),
119 },
120 Some(variable.range()),
121 ));
122 continue;
124 }
125
126 temp_symbols.insert(variable_name);
127 let hover = HoverInfo {
128 description: format!("Letting variable: {variable_name}"),
129 doc_key: None,
130 kind: Some(SymbolKind::LettingVar),
131 ty: None,
132 decl_span: None,
133 };
134 let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
135 ctx.save_decl_span(name, span_id);
136 }
137
138 let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
139 return Ok(None);
140 };
141
142 if variable_decl.child_by_field_name("domain").is_some() {
143 for name in temp_symbols {
144 let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
145 continue;
146 };
147
148 symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
149 }
150 } else {
151 for name in temp_symbols {
152 let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
153 continue;
154 };
155 let declaration = match value_letting_domain(&expr) {
156 Some(domain) => DeclarationPtr::new_value_letting_with_domain(
157 Name::user(name),
158 expr,
159 domain,
160 ),
161 None => DeclarationPtr::new_value_letting(Name::user(name), expr),
162 };
163 symbol_table.insert(declaration);
164 }
165 }
166 }
167
168 Ok(Some(symbol_table))
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::parser::parse_model::parse_essence;
175 use conjure_cp_core::ast::{DeclarationKind, Name};
176 use std::ops::Deref;
177 use std::time::{Duration, Instant};
178
179 #[test]
182 fn arithmetic_letting_over_bare_int_givens_uses_symbolic_domain() {
183 let src = r#"
184language ESSENCE' 1.0
185given v, k, l : int
186letting b be (l*v*(v-1))/(k*(k-1))
187find x: bool
188such that x
189"#;
190 let started = Instant::now();
191 let (model, _) = parse_essence(src).expect("model should parse");
192 assert!(
193 started.elapsed() < Duration::from_secs(2),
194 "parse hung for {:?}; eager domain_of over bare int is likely back",
195 started.elapsed()
196 );
197
198 let symbols = model.symbols();
199 let decl = symbols
200 .lookup(&Name::user("b"))
201 .expect("letting b should be in the symbol table");
202 match decl.kind().deref() {
203 DeclarationKind::ValueLetting(_, Some(domain)) => {
204 let ranges = domain.as_int().expect("letting b domain should be int");
205 assert_eq!(
206 ranges.len(),
207 1,
208 "expected a singleton domain, got {ranges:?}"
209 );
210 assert!(
211 matches!(&ranges[0], Range::Single(IntVal::Expr(_))),
212 "expected symbolic IntVal::Expr singleton, got {:?}",
213 ranges[0]
214 );
215 }
216 other => panic!("expected ValueLetting with retained domain, got {other:?}"),
217 }
218 }
219
220 #[test]
222 fn constant_int_letting_keeps_ground_singleton_domain() {
223 let src = r#"
224language ESSENCE' 1.0
225letting n be 3
226find x: int(1..10**n)
227such that x = 1
228"#;
229 let (model, _) = parse_essence(src).expect("model should parse");
230 let symbols = model.symbols();
231 let decl = symbols
232 .lookup(&Name::user("n"))
233 .expect("letting n should be in the symbol table");
234 match decl.kind().deref() {
235 DeclarationKind::ValueLetting(_, Some(domain)) => {
236 let ranges = domain.as_int().expect("letting n domain should be int");
237 assert_eq!(
238 ranges.len(),
239 1,
240 "expected a singleton domain, got {ranges:?}"
241 );
242 assert!(
243 matches!(&ranges[0], Range::Single(IntVal::Const(3))),
244 "expected ground IntVal::Const(3) singleton, got {:?}",
245 ranges[0]
246 );
247 }
248 other => panic!("expected ValueLetting with retained domain, got {other:?}"),
249 }
250 }
251}