Skip to main content

conjure_cp_essence_parser/parser/
domain.rs

1use super::atom::parse_int;
2use super::util::named_children;
3use crate::diagnostics::diagnostics_api::SymbolKind;
4use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
5use crate::errors::FatalParseError;
6use crate::expression::parse_expression;
7use crate::parser::ParseContext;
8use crate::{RecoverableParseError, child};
9use conjure_cp_core::ast::{
10    DeclarationPtr, Domain, DomainPtr, IntVal, Moo, Name, Range, RecordEntry, Reference, SetAttr,
11};
12use tree_sitter::Node;
13
14use crate::field;
15
16/// Parse an Essence variable domain into its Conjure AST representation.
17pub fn parse_domain(
18    ctx: &mut ParseContext,
19    domain: Node,
20) -> Result<Option<DomainPtr>, FatalParseError> {
21    match domain.kind() {
22        "domain" => {
23            let inner = match domain.child(0) {
24                Some(node) => node,
25                None => {
26                    ctx.record_error(RecoverableParseError::new(
27                        format!("{} in expression of kind '{}'", "domain", domain.kind()),
28                        Some(domain.range()),
29                    ));
30                    return Ok(None);
31                }
32            };
33            parse_domain(ctx, inner)
34        }
35        "bool_domain" => {
36            ctx.add_span_and_doc_hover(&domain, "L_bool", SymbolKind::Domain, None, None);
37            Ok(Some(Domain::bool()))
38        }
39        "int_domain" => parse_int_domain(ctx, domain),
40        "identifier" => {
41            let Some(decl) = get_declaration_ptr_from_identifier(ctx, domain)? else {
42                return Ok(None);
43            };
44            let Some(dom) = Domain::reference(decl) else {
45                ctx.record_error(crate::errors::RecoverableParseError::new(
46                    format!(
47                        "The identifier '{}' is not a valid domain",
48                        &ctx.source_code[domain.start_byte()..domain.end_byte()]
49                    ),
50                    Some(domain.range()),
51                ));
52                return Ok(None);
53            };
54            let name = &ctx.source_code[domain.start_byte()..domain.end_byte()];
55
56            // Not form docs, because we need context specific hover info
57            span_with_hover(
58                &domain,
59                ctx.source_code,
60                ctx.source_map,
61                HoverInfo {
62                    description: format!("Domain reference: {name}"),
63                    kind: Some(SymbolKind::Variable),
64                    ty: None,
65                    decl_span: None, // could link to the declaration span if we wanted
66                },
67            );
68            Ok(Some(dom))
69        }
70        "tuple_domain" => parse_tuple_domain(ctx, domain),
71        "matrix_domain" => parse_matrix_domain(ctx, domain),
72        "record_domain" => parse_record_domain(ctx, domain),
73        "set_domain" => parse_set_domain(ctx, domain),
74        _ => {
75            ctx.record_error(RecoverableParseError::new(
76                format!("{} is not a supported domain type", domain.kind()),
77                Some(domain.range()),
78            ));
79            Ok(None)
80        }
81    }
82}
83
84fn get_declaration_ptr_from_identifier(
85    ctx: &mut ParseContext,
86    identifier: Node,
87) -> Result<Option<DeclarationPtr>, FatalParseError> {
88    let name = Name::user(&ctx.source_code[identifier.start_byte()..identifier.end_byte()]);
89    let decl = ctx.symbols.as_ref().unwrap().read().lookup(&name);
90
91    if decl.is_none() {
92        ctx.record_error(crate::errors::RecoverableParseError::new(
93            format!("The identifier '{}' is not defined", name),
94            Some(identifier.range()),
95        ));
96        return Ok(None);
97    }
98    match decl {
99        Some(decl) => Ok(Some(decl)),
100        None => {
101            ctx.record_error(crate::errors::RecoverableParseError::new(
102                format!("The identifier '{}' is not defined", name),
103                Some(identifier.range()),
104            ));
105            Ok(None)
106        }
107    }
108}
109
110/// Parse an integer domain. Can be a single integer or a range.
111fn parse_int_domain(
112    ctx: &mut ParseContext,
113    int_domain: Node,
114) -> Result<Option<DomainPtr>, FatalParseError> {
115    let int_keyword_node = child!(int_domain, 0, "int");
116    if int_domain.child_count() == 1 {
117        // for domains of just 'int' with no range
118        ctx.add_span_and_doc_hover(&int_keyword_node, "L_int", SymbolKind::Domain, None, None);
119        return Ok(Some(Domain::int(vec![Range::Bounded(i32::MIN, i32::MAX)])));
120    }
121
122    let Some(range_list) = field!(recover, ctx, int_domain, "ranges") else {
123        return Ok(None);
124    };
125    let mut ranges_unresolved: Vec<Range<IntVal>> = Vec::new();
126    let mut all_resolved = true;
127
128    for domain_component in named_children(&range_list) {
129        match domain_component.kind() {
130            "atom" | "arithmetic_expr" => {
131                let Some(int_val) = parse_int_val(ctx, domain_component)? else {
132                    return Ok(None);
133                };
134
135                if !matches!(int_val, IntVal::Const(_)) {
136                    all_resolved = false;
137                }
138                ranges_unresolved.push(Range::Single(int_val));
139            }
140            "int_range" => {
141                let lower_bound = match domain_component.child_by_field_name("lower") {
142                    Some(node) => {
143                        match parse_int_val(ctx, node)? {
144                            Some(val) => Some(val),
145                            None => return Ok(None), // semantic error occurred
146                        }
147                    }
148                    None => None,
149                };
150                let upper_bound = match domain_component.child_by_field_name("upper") {
151                    Some(node) => {
152                        match parse_int_val(ctx, node)? {
153                            Some(val) => Some(val),
154                            None => return Ok(None), // semantic error occurred
155                        }
156                    }
157                    None => None,
158                };
159
160                match (lower_bound, upper_bound) {
161                    (Some(lower), Some(upper)) => {
162                        // Check if both bounds are constants and validate lower <= upper
163                        if let (IntVal::Const(l), IntVal::Const(u)) = (&lower, &upper) {
164                            if l > u {
165                                ctx.record_error(crate::errors::RecoverableParseError::new(
166                                    format!(
167                                        "Invalid integer range: lower bound {} is greater than upper bound {}",
168                                        l, u
169                                    ),
170                                    Some(domain_component.range()),
171                                ));
172                            }
173                        } else {
174                            all_resolved = false;
175                        }
176                        ranges_unresolved.push(Range::Bounded(lower, upper));
177                    }
178                    (Some(lower), None) => {
179                        if !matches!(lower, IntVal::Const(_)) {
180                            all_resolved = false;
181                        }
182                        ranges_unresolved.push(Range::UnboundedR(lower));
183                    }
184                    (None, Some(upper)) => {
185                        if !matches!(upper, IntVal::Const(_)) {
186                            all_resolved = false;
187                        }
188                        ranges_unresolved.push(Range::UnboundedL(upper));
189                    }
190                    _ => {
191                        ctx.record_error(RecoverableParseError::new(
192                            "Invalid int range: must have at least a lower or upper bound"
193                                .to_string(),
194                            Some(domain_component.range()),
195                        ));
196                        return Ok(None);
197                    }
198                }
199            }
200            _ => {
201                ctx.record_error(RecoverableParseError::new(
202                    format!(
203                        "Unexpected int domain component: {}",
204                        domain_component.kind()
205                    ),
206                    Some(domain_component.range()),
207                ));
208                return Ok(None);
209            }
210        }
211    }
212
213    // If all values are resolved constants, convert IntVals to raw integers
214    if all_resolved {
215        let ranges: Vec<Range<i32>> = ranges_unresolved
216            .into_iter()
217            .map(|r| match r {
218                Range::Single(IntVal::Const(v)) => Range::Single(v),
219                Range::Bounded(IntVal::Const(l), IntVal::Const(u)) => Range::Bounded(l, u),
220                Range::UnboundedR(IntVal::Const(l)) => Range::UnboundedR(l),
221                Range::UnboundedL(IntVal::Const(u)) => Range::UnboundedL(u),
222                Range::Unbounded => Range::Unbounded,
223                _ => unreachable!("all_resolved should be true only if all are Const"),
224            })
225            .collect();
226
227        ctx.add_span_and_doc_hover(&int_keyword_node, "L_int", SymbolKind::Domain, None, None);
228        Ok(Some(Domain::int(ranges)))
229    } else {
230        // Otherwise, keep as an expression-based domain
231
232        // Adding int keyword to the source map with hover info from documentation
233        ctx.add_span_and_doc_hover(&int_keyword_node, "L_int", SymbolKind::Domain, None, None);
234        Ok(Some(Domain::int(ranges_unresolved)))
235    }
236}
237
238// Helper function to parse a node into an IntVal
239// Handles constants, references, and arbitrary expressions
240fn parse_int_val(ctx: &mut ParseContext, node: Node) -> Result<Option<IntVal>, FatalParseError> {
241    // For atoms, try to parse as a constant integer first
242    if node.kind() == "atom" {
243        let text = &ctx.source_code[node.start_byte()..node.end_byte()];
244        if let Ok(integer) = text.parse::<i32>() {
245            return Ok(Some(IntVal::Const(integer)));
246        }
247        // Otherwise, check if it's an identifier reference
248        let Some(decl) = get_declaration_ptr_from_identifier(ctx, node)? else {
249            // If identifier isn't defined, its a semantic error
250            return Ok(None);
251        };
252        return Ok(Some(IntVal::Reference(Reference::new(decl))));
253    }
254
255    // For anything else, parse as an expression
256    let Some(expr) = parse_expression(ctx, node)? else {
257        return Ok(None);
258    };
259    Ok(Some(IntVal::Expr(Moo::new(expr))))
260}
261
262fn parse_tuple_domain(
263    ctx: &mut ParseContext,
264    tuple_domain: Node,
265) -> Result<Option<DomainPtr>, FatalParseError> {
266    let mut domains: Vec<DomainPtr> = Vec::new();
267    for domain in named_children(&tuple_domain) {
268        let Some(parsed_domain) = parse_domain(ctx, domain)? else {
269            return Ok(None);
270        };
271        domains.push(parsed_domain);
272    }
273
274    // extract the first child node which should be the 'tuple' keyword for hover info
275    if let Some(first) = tuple_domain.child(0)
276        && first.kind() == "tuple"
277    {
278        // Adding tuple to the source map with hover info from documentation
279        ctx.add_span_and_doc_hover(&first, "L_tuple", SymbolKind::Domain, None, None);
280    }
281
282    Ok(Some(Domain::tuple(domains)))
283}
284
285fn parse_matrix_domain(
286    ctx: &mut ParseContext,
287    matrix_domain: Node,
288) -> Result<Option<DomainPtr>, FatalParseError> {
289    let mut domains: Vec<DomainPtr> = Vec::new();
290    let Some(index_domain_list) = field!(recover, ctx, matrix_domain, "index_domain_list") else {
291        return Ok(None);
292    };
293    for domain in named_children(&index_domain_list) {
294        let Some(parsed_domain) = parse_domain(ctx, domain)? else {
295            return Ok(None);
296        };
297        domains.push(parsed_domain);
298    }
299    let Some(value_domain_node) = field!(recover, ctx, matrix_domain, "value_domain") else {
300        return Ok(None);
301    };
302    let Some(value_domain) = parse_domain(ctx, value_domain_node)? else {
303        return Ok(None);
304    };
305
306    // Adding matrix to the source map with hover info from documentation
307    let matrix_keyword_node = child!(matrix_domain, 0, "matrix");
308    ctx.add_span_and_doc_hover(
309        &matrix_keyword_node,
310        "matrix",
311        SymbolKind::Domain,
312        None,
313        None,
314    );
315    Ok(Some(Domain::matrix(value_domain, domains)))
316}
317
318fn parse_record_domain(
319    ctx: &mut ParseContext,
320    record_domain: Node,
321) -> Result<Option<DomainPtr>, FatalParseError> {
322    let mut record_entries: Vec<RecordEntry> = Vec::new();
323    for record_entry in named_children(&record_domain) {
324        let Some(name_node) = field!(recover, ctx, record_entry, "name") else {
325            return Ok(None);
326        };
327        let name = Name::user(&ctx.source_code[name_node.start_byte()..name_node.end_byte()]);
328        let Some(domain_node) = field!(recover, ctx, record_entry, "domain") else {
329            return Ok(None);
330        };
331        let Some(domain) = parse_domain(ctx, domain_node)? else {
332            return Ok(None);
333        };
334        record_entries.push(RecordEntry { name, domain });
335    }
336
337    // Adding record keyword to the source map with hover info from documentation
338    let record_keyword_node = child!(record_domain, 0, "record");
339    ctx.add_span_and_doc_hover(
340        &record_keyword_node,
341        "L_record",
342        SymbolKind::Domain,
343        None,
344        None,
345    );
346    Ok(Some(Domain::record(record_entries)))
347}
348
349pub fn parse_set_domain(
350    ctx: &mut ParseContext,
351    set_domain: Node,
352) -> Result<Option<DomainPtr>, FatalParseError> {
353    let mut set_attribute: Option<SetAttr> = None;
354    let mut value_domain: Option<DomainPtr> = None;
355
356    for child in named_children(&set_domain) {
357        match child.kind() {
358            "set_attributes" => {
359                // Check if we have both minSize and maxSize (minMax case)
360                let min_value_node = child.child_by_field_name("min_value");
361                let max_value_node = child.child_by_field_name("max_value");
362                let size_value_node = child.child_by_field_name("size_value");
363
364                if let (Some(min_node), Some(max_node)) = (min_value_node, max_value_node) {
365                    // MinMax case
366                    let Some(min_val) = parse_int(ctx, &min_node) else {
367                        return Ok(None);
368                    };
369                    let Some(max_val) = parse_int(ctx, &max_node) else {
370                        return Ok(None);
371                    };
372
373                    set_attribute = Some(SetAttr::new_min_max_size(min_val, max_val));
374                } else if let Some(size_node) = size_value_node {
375                    // Size case
376                    let Some(size_val) = parse_int(ctx, &size_node) else {
377                        return Ok(None);
378                    };
379                    set_attribute = Some(SetAttr::new_size(size_val));
380                } else if let Some(min_node) = min_value_node {
381                    // MinSize only case
382                    let Some(min_val) = parse_int(ctx, &min_node) else {
383                        return Ok(None);
384                    };
385                    set_attribute = Some(SetAttr::new_min_size(min_val));
386                } else if let Some(max_node) = max_value_node {
387                    // MaxSize only case
388                    let Some(max_val) = parse_int(ctx, &max_node) else {
389                        return Ok(None);
390                    };
391                    set_attribute = Some(SetAttr::new_max_size(max_val));
392                }
393            }
394            "domain" => {
395                let Some(parsed_domain) = parse_domain(ctx, child)? else {
396                    return Ok(None);
397                };
398                value_domain = Some(parsed_domain);
399            }
400            _ => {
401                ctx.record_error(RecoverableParseError::new(
402                    format!("Unrecognized set domain child kind: {}", child.kind()),
403                    Some(child.range()),
404                ));
405                return Ok(None);
406            }
407        }
408    }
409
410    if let Some(domain) = value_domain {
411        // Adding set to the source map with hover info from documentation
412        let set_keyword_node = child!(set_domain, 0, "set");
413        // No documentation available for set domain, using fallback description
414        ctx.add_span_and_doc_hover(&set_keyword_node, "set", SymbolKind::Domain, None, None);
415        Ok(Some(Domain::set(set_attribute.unwrap_or_default(), domain)))
416    } else {
417        ctx.record_error(RecoverableParseError::new(
418            "Set domain must have a value domain".to_string(),
419            Some(set_domain.range()),
420        ));
421        Ok(None)
422    }
423}