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::util::TypecheckingContext;
9use crate::{RecoverableParseError, child};
10use conjure_cp_core::ast::{
11    Atom, BinaryAttr, DeclarationPtr, Domain, DomainPtr, Expression, Field, FuncAttr, IntVal,
12    JectivityAttr, Literal, MSetAttr, Moo, Name, PartialityAttr, PartitionAttr, PermutationAttr,
13    Range, Reference, RelAttr, SequenceAttr, SetAttr,
14};
15use tree_sitter::Node;
16
17use crate::field;
18
19/// Parse an Essence variable domain into its Conjure AST representation.
20pub fn parse_domain(
21    ctx: &mut ParseContext,
22    domain: Node,
23) -> Result<Option<DomainPtr>, FatalParseError> {
24    match domain.kind() {
25        "domain" | "annotation_domain" => {
26            let inner = match domain.child(0) {
27                Some(node) => node,
28                None => {
29                    ctx.record_error(RecoverableParseError::new(
30                        format!("{} in expression of kind '{}'", "domain", domain.kind()),
31                        Some(domain.range()),
32                    ));
33                    return Ok(None);
34                }
35            };
36            parse_domain(ctx, inner)
37        }
38        "bool_domain" => {
39            ctx.add_span_and_doc_hover(&domain, "L_bool", SymbolKind::Domain, None, None);
40            Ok(Some(Domain::bool()))
41        }
42        "int_domain" | "annotation_int_domain" => parse_int_domain(ctx, domain),
43        "identifier" => {
44            let Some(decl) = get_declaration_ptr_from_identifier(ctx, domain)? else {
45                return Ok(None);
46            };
47            let Some(dom) = Domain::reference(decl) else {
48                ctx.record_error(crate::errors::RecoverableParseError::new(
49                    format!(
50                        "The identifier '{}' is not a valid domain",
51                        &ctx.source_code[domain.start_byte()..domain.end_byte()]
52                    ),
53                    Some(domain.range()),
54                ));
55                return Ok(None);
56            };
57            let name = &ctx.source_code[domain.start_byte()..domain.end_byte()];
58
59            // Not form docs, because we need context specific hover info
60            span_with_hover(
61                &domain,
62                ctx.source_code,
63                ctx.source_map,
64                HoverInfo {
65                    description: format!("Domain reference: {name}"),
66                    doc_key: None,
67                    kind: Some(SymbolKind::Variable),
68                    ty: None,
69                    decl_span: None, // could link to the declaration span if we wanted
70                },
71            );
72            Ok(Some(dom))
73        }
74        "tuple_domain" | "annotation_tuple_domain" => parse_tuple_domain(ctx, domain),
75        "matrix_domain" | "annotation_matrix_domain" => parse_matrix_domain(ctx, domain),
76        "record_domain" | "annotation_record_domain" => parse_record_domain(ctx, domain),
77        "variant_domain" | "annotation_variant_domain" => parse_variant_domain(ctx, domain),
78        "set_domain" | "annotation_set_domain" => parse_set_domain(ctx, domain),
79        "mset_domain" | "annotation_mset_domain" => parse_mset_domain(ctx, domain),
80        "sequence_domain" | "annotation_sequence_domain" => parse_sequence_domain(ctx, domain),
81        "function_domain" | "annotation_function_domain" => parse_function_domain(ctx, domain),
82        "relation_domain" => parse_relation_domain(ctx, domain),
83        "partition_domain" => parse_partition_domain(ctx, domain),
84        "permutation_domain" => parse_permutation_domain(ctx, domain),
85        _ => {
86            ctx.record_error(RecoverableParseError::new(
87                format!("{} is not a supported domain type", domain.kind()),
88                Some(domain.range()),
89            ));
90            Ok(None)
91        }
92    }
93}
94
95fn parse_mset_domain(
96    ctx: &mut ParseContext,
97    mset_domain: Node,
98) -> Result<Option<DomainPtr>, FatalParseError> {
99    let mut representation = None;
100    let mut size = Range::Unbounded;
101    let mut occurrence = Range::Unbounded;
102    let mut min_size = None;
103    let mut max_size = None;
104    let mut min_occurrence = None;
105    let mut max_occurrence = None;
106    let mut value_domain = None;
107
108    for child in named_children(&mset_domain) {
109        match child.kind() {
110            "identifier" => {}
111            "mset_attributes" => {
112                for attribute in named_children(&child) {
113                    let Some(value_node) = attribute.child_by_field_name("value") else {
114                        return Ok(None);
115                    };
116                    let name = attribute
117                        .child_by_field_name("attribute")
118                        .map(|node| &ctx.source_code[node.start_byte()..node.end_byte()])
119                        .unwrap_or_default();
120                    if name == "representation" {
121                        representation = Some(
122                            ctx.source_code[value_node.start_byte()..value_node.end_byte()]
123                                .to_string(),
124                        );
125                        continue;
126                    }
127                    // Attribute values may be expressions over givens, e.g. `maxSize |nums|`,
128                    // which only become ground once the parameters are instantiated.
129                    let Some(value) = parse_int_val(ctx, value_node)? else {
130                        return Ok(None);
131                    };
132                    match name {
133                        "size" => size = Range::Single(value),
134                        "minSize" => min_size = Some(value),
135                        "maxSize" => max_size = Some(value),
136                        "minOccur" => min_occurrence = Some(value),
137                        "maxOccur" => max_occurrence = Some(value),
138                        _ => return Ok(None),
139                    }
140                }
141            }
142            "domain" | "annotation_domain" => value_domain = parse_domain(ctx, child)?,
143            _ => {}
144        }
145    }
146
147    if !matches!(size, Range::Single(_)) {
148        size = match (min_size, max_size) {
149            (Some(min), Some(max)) => Range::Bounded(min, max),
150            (Some(min), None) => Range::UnboundedR(min),
151            (None, Some(max)) => Range::UnboundedL(max),
152            (None, None) => Range::Unbounded,
153        };
154    }
155    occurrence = match (min_occurrence, max_occurrence) {
156        (Some(min), Some(max)) => Range::Bounded(min, max),
157        (Some(min), None) => Range::UnboundedR(min),
158        (None, Some(max)) => Range::UnboundedL(max),
159        (None, None) => occurrence,
160    };
161
162    let Some(value_domain) = value_domain else {
163        ctx.record_error(RecoverableParseError::new(
164            "Multiset domain must have a value domain".to_string(),
165            Some(mset_domain.range()),
166        ));
167        return Ok(None);
168    };
169    let mut attrs = MSetAttr::new(size, occurrence);
170    if let Some(representation) = representation {
171        attrs = attrs.with_representation(representation);
172    }
173    Ok(Some(Domain::mset(attrs, value_domain)))
174}
175
176fn get_declaration_ptr_from_identifier(
177    ctx: &mut ParseContext,
178    identifier: Node,
179) -> Result<Option<DeclarationPtr>, FatalParseError> {
180    let name = Name::user(&ctx.source_code[identifier.start_byte()..identifier.end_byte()]);
181    let decl = ctx.symbols.as_ref().unwrap().read().lookup(&name);
182
183    if decl.is_none() {
184        ctx.record_error(crate::errors::RecoverableParseError::new(
185            format!("The identifier '{}' is not defined", name),
186            Some(identifier.range()),
187        ));
188        return Ok(None);
189    }
190    match decl {
191        Some(decl) => Ok(Some(decl)),
192        None => {
193            ctx.record_error(crate::errors::RecoverableParseError::new(
194                format!("The identifier '{}' is not defined", name),
195                Some(identifier.range()),
196            ));
197            Ok(None)
198        }
199    }
200}
201
202/// Parse an integer domain. Can be a single integer or a range.
203fn parse_int_domain(
204    ctx: &mut ParseContext,
205    int_domain: Node,
206) -> Result<Option<DomainPtr>, FatalParseError> {
207    let int_keyword_node = child!(int_domain, 0, "int");
208
209    let Some(range_list) = int_domain.child_by_field_name("ranges") else {
210        ctx.add_span_and_doc_hover(&int_keyword_node, "L_int", SymbolKind::Domain, None, None);
211        let int_text = ctx.source_code[int_domain.start_byte()..int_domain.end_byte()].trim();
212        return if int_text == "int" {
213            Ok(Some(Domain::int(vec![Range::Bounded(
214                conjure_cp_core::ast::OXIDE_INT_MIN,
215                conjure_cp_core::ast::OXIDE_INT_MAX,
216            )])))
217        } else {
218            Ok(Some(Domain::int_ground(vec![])))
219        };
220    };
221    // `int(<collection>)` takes its values from the collection rather than listing ranges, as in
222    // `int([i | i <- nums])`. One component that is collection-valued means this form.
223    let components: Vec<_> = named_children(&range_list).collect();
224    if let [only] = components.as_slice()
225        && matches!(only.kind(), "atom" | "arithmetic_expr")
226        && let Some(expr) = parse_collection_valued_expression(ctx, *only)?
227    {
228        ctx.add_span_and_doc_hover(&int_keyword_node, "int", SymbolKind::Domain, None, None);
229        return Ok(Some(Domain::int_from_values(expr)));
230    }
231
232    let mut ranges_unresolved: Vec<Range<IntVal>> = Vec::new();
233    let mut all_resolved = true;
234
235    for domain_component in named_children(&range_list) {
236        match domain_component.kind() {
237            "atom" | "arithmetic_expr" | "integer" | "identifier" => {
238                let Some(int_val) = parse_int_val(ctx, domain_component)? else {
239                    return Ok(None);
240                };
241
242                if !matches!(int_val, IntVal::Const(_)) {
243                    all_resolved = false;
244                }
245                ranges_unresolved.push(Range::Single(int_val));
246            }
247            "int_range" | "annotation_int_range" => {
248                let lower_bound = match domain_component.child_by_field_name("lower") {
249                    Some(node) => {
250                        match parse_int_val(ctx, node)? {
251                            Some(val) => Some(val),
252                            None => return Ok(None), // semantic error occurred
253                        }
254                    }
255                    None => None,
256                };
257                let upper_bound = match domain_component.child_by_field_name("upper") {
258                    Some(node) => {
259                        match parse_int_val(ctx, node)? {
260                            Some(val) => Some(val),
261                            None => return Ok(None), // semantic error occurred
262                        }
263                    }
264                    None => None,
265                };
266
267                match (lower_bound, upper_bound) {
268                    (Some(lower), Some(upper)) => {
269                        if !matches!((&lower, &upper), (IntVal::Const(_), IntVal::Const(_))) {
270                            all_resolved = false;
271                        }
272                        ranges_unresolved.push(Range::Bounded(lower, upper));
273                    }
274                    (Some(lower), None) => {
275                        if !matches!(lower, IntVal::Const(_)) {
276                            all_resolved = false;
277                        }
278                        ranges_unresolved.push(Range::UnboundedR(lower));
279                    }
280                    (None, Some(upper)) => {
281                        if !matches!(upper, IntVal::Const(_)) {
282                            all_resolved = false;
283                        }
284                        ranges_unresolved.push(Range::UnboundedL(upper));
285                    }
286                    _ => {
287                        ctx.record_error(RecoverableParseError::new(
288                            "Invalid int range: must have at least a lower or upper bound"
289                                .to_string(),
290                            Some(domain_component.range()),
291                        ));
292                        return Ok(None);
293                    }
294                }
295            }
296            _ => {
297                ctx.record_error(RecoverableParseError::new(
298                    format!(
299                        "Unexpected int domain component: {}",
300                        domain_component.kind()
301                    ),
302                    Some(domain_component.range()),
303                ));
304                return Ok(None);
305            }
306        }
307    }
308
309    // If all values are resolved constants, convert IntVals to raw integers
310    if all_resolved {
311        let ranges: Vec<Range<i32>> = ranges_unresolved
312            .into_iter()
313            .map(|r| r.resolve())
314            .collect::<Result<Vec<_>, _>>()
315            .map_err(|e| {
316                FatalParseError::internal_error(
317                    format!("could not resolve range: {e}"),
318                    Some(int_domain.range()),
319                )
320            })?
321            .into_iter()
322            .filter(|range| !matches!(range, Range::Bounded(lower, upper) if lower > upper))
323            .collect();
324
325        ctx.add_span_and_doc_hover(&int_keyword_node, "L_int", SymbolKind::Domain, None, None);
326        Ok(Some(Domain::int(ranges)))
327    } else {
328        // Otherwise, keep as an expression-based domain
329
330        // Adding int keyword to the source map with hover info from documentation
331        ctx.add_span_and_doc_hover(&int_keyword_node, "L_int", SymbolKind::Domain, None, None);
332        Ok(Some(Domain::int(ranges_unresolved)))
333    }
334}
335
336// Helper function to parse a node into an IntVal
337// Handles constants, references, and arbitrary expressions
338fn parse_int_val(ctx: &mut ParseContext, node: Node) -> Result<Option<IntVal>, FatalParseError> {
339    if matches!(node.kind(), "atom" | "integer") {
340        let text = &ctx.source_code[node.start_byte()..node.end_byte()];
341        if let Ok(integer) = text.parse::<i32>() {
342            return Ok(Some(IntVal::new_const(integer)));
343        }
344    }
345
346    if node.kind() == "identifier" {
347        let Some(decl) = get_declaration_ptr_from_identifier(ctx, node)? else {
348            // If identifier isn't defined, it's a semantic error.
349            return Ok(None);
350        };
351        return Ok(Some(IntVal::Reference(Reference::new(decl))));
352    }
353
354    let saved_context = ctx.typechecking_context;
355    let saved_inner_context = ctx.inner_typechecking_context;
356    ctx.typechecking_context = TypecheckingContext::Arithmetic;
357    ctx.inner_typechecking_context = TypecheckingContext::Arithmetic;
358    let expr = parse_expression(ctx, node)?;
359    ctx.typechecking_context = saved_context;
360    ctx.inner_typechecking_context = saved_inner_context;
361
362    let Some(expr) = expr else {
363        return Ok(None);
364    };
365
366    if let Expression::Atomic(_, Atom::Reference(reference)) = &expr
367        && let Ok(reference_val) = IntVal::new_ref(reference)
368    {
369        return Ok(Some(reference_val));
370    }
371
372    if let Expression::Atomic(_, Atom::Literal(Literal::Int(value))) = expr {
373        return Ok(Some(IntVal::new_const(value)));
374    }
375
376    Ok(IntVal::new_expr(Moo::new(expr)).ok())
377}
378
379fn parse_tuple_domain(
380    ctx: &mut ParseContext,
381    tuple_domain: Node,
382) -> Result<Option<DomainPtr>, FatalParseError> {
383    let mut domains: Vec<DomainPtr> = Vec::new();
384    for domain in named_children(&tuple_domain) {
385        let Some(parsed_domain) = parse_domain(ctx, domain)? else {
386            return Ok(None);
387        };
388        domains.push(parsed_domain);
389    }
390
391    // extract the first child node which should be the 'tuple' keyword for hover info
392    if let Some(first) = tuple_domain.child(0)
393        && first.kind() == "tuple"
394    {
395        // Adding tuple to the source map with hover info from documentation
396        ctx.add_span_and_doc_hover(&first, "L_tuple", SymbolKind::Domain, None, None);
397    }
398
399    Ok(Some(Domain::tuple(domains)))
400}
401
402fn parse_matrix_domain(
403    ctx: &mut ParseContext,
404    matrix_domain: Node,
405) -> Result<Option<DomainPtr>, FatalParseError> {
406    let mut domains: Vec<DomainPtr> = Vec::new();
407    let Some(index_domain_list) = field!(recover, ctx, matrix_domain, "index_domain_list") else {
408        return Ok(None);
409    };
410    for domain in named_children(&index_domain_list) {
411        let Some(parsed_domain) = parse_domain(ctx, domain)? else {
412            return Ok(None);
413        };
414        domains.push(parsed_domain);
415    }
416    let Some(value_domain_node) = field!(recover, ctx, matrix_domain, "value_domain") else {
417        return Ok(None);
418    };
419    let Some(value_domain) = parse_domain(ctx, value_domain_node)? else {
420        return Ok(None);
421    };
422
423    // Adding matrix to the source map with hover info from documentation
424    let matrix_keyword_node = child!(matrix_domain, 0, "matrix");
425    ctx.add_span_and_doc_hover(
426        &matrix_keyword_node,
427        "matrix",
428        SymbolKind::Domain,
429        None,
430        None,
431    );
432    Ok(Some(Domain::matrix(value_domain, domains)))
433}
434
435fn parse_record_domain(
436    ctx: &mut ParseContext,
437    record_domain: Node,
438) -> Result<Option<DomainPtr>, FatalParseError> {
439    let mut record_entries: Vec<Field<DomainPtr>> = Vec::new();
440    for record_entry in named_children(&record_domain) {
441        let Some(name_node) = field!(recover, ctx, record_entry, "name") else {
442            return Ok(None);
443        };
444        let name = Name::user(&ctx.source_code[name_node.start_byte()..name_node.end_byte()]);
445        let Some(domain_node) = field!(recover, ctx, record_entry, "domain") else {
446            return Ok(None);
447        };
448        let Some(value) = parse_domain(ctx, domain_node)? else {
449            return Ok(None);
450        };
451        record_entries.push(Field { name, value });
452    }
453
454    // Adding record keyword to the source map with hover info from documentation
455    let record_keyword_node = child!(record_domain, 0, "record");
456    ctx.add_span_and_doc_hover(
457        &record_keyword_node,
458        "L_record",
459        SymbolKind::Domain,
460        None,
461        None,
462    );
463    Ok(Some(Domain::record(record_entries)))
464}
465
466fn parse_variant_domain(
467    ctx: &mut ParseContext,
468    variant_domain: Node,
469) -> Result<Option<DomainPtr>, FatalParseError> {
470    let mut entries = Vec::new();
471    for entry in named_children(&variant_domain) {
472        let Some(name_node) = field!(recover, ctx, entry, "name") else {
473            return Ok(None);
474        };
475        let name = Name::user(&ctx.source_code[name_node.start_byte()..name_node.end_byte()]);
476        let Some(domain_node) = field!(recover, ctx, entry, "domain") else {
477            return Ok(None);
478        };
479        let Some(value) = parse_domain(ctx, domain_node)? else {
480            return Ok(None);
481        };
482        entries.push(Field { name, value });
483    }
484
485    let keyword = child!(variant_domain, 0, "variant");
486    ctx.add_span_and_doc_hover(&keyword, "variant", SymbolKind::Domain, None, None);
487    Ok(Some(Domain::variant(entries)))
488}
489
490fn parse_sequence_domain(
491    ctx: &mut ParseContext,
492    sequence_domain: Node,
493) -> Result<Option<DomainPtr>, FatalParseError> {
494    let mut size = Range::Unbounded;
495    let mut min_size = None;
496    let mut max_size = None;
497    let mut jectivity = JectivityAttr::None;
498    let mut value_domain: Option<DomainPtr> = None;
499
500    for child in named_children(&sequence_domain) {
501        match child.kind() {
502            "sequence_attributes" => {
503                for attribute in named_children(&child) {
504                    let name = attribute
505                        .child_by_field_name("attribute")
506                        .map(|node| &ctx.source_code[node.start_byte()..node.end_byte()])
507                        .unwrap_or_default();
508                    match name {
509                        "size" | "minSize" | "maxSize" => {
510                            let Some(value_node) = attribute.child_by_field_name("value") else {
511                                return Ok(None);
512                            };
513                            // Attribute values may be expressions over givens, e.g.
514                            // `maxSize n`, which only become ground once the parameters are
515                            // instantiated.
516                            let Some(value) = parse_int_val(ctx, value_node)? else {
517                                return Ok(None);
518                            };
519                            match name {
520                                "size" => size = Range::Single(value),
521                                "minSize" => min_size = Some(value),
522                                "maxSize" => max_size = Some(value),
523                                _ => unreachable!(),
524                            }
525                        }
526                        "injective" => jectivity = JectivityAttr::Injective,
527                        "surjective" => jectivity = JectivityAttr::Surjective,
528                        "bijective" => jectivity = JectivityAttr::Bijective,
529                        _ => return Ok(None),
530                    }
531                }
532            }
533            "domain" | "annotation_domain" => value_domain = parse_domain(ctx, child)?,
534            _ => {}
535        }
536    }
537
538    if !matches!(size, Range::Single(_)) {
539        size = match (min_size, max_size) {
540            (Some(min), Some(max)) => Range::Bounded(min, max),
541            (Some(min), None) => Range::UnboundedR(min),
542            (None, Some(max)) => Range::UnboundedL(max),
543            (None, None) => Range::Unbounded,
544        };
545    }
546
547    let Some(value_domain) = value_domain else {
548        ctx.record_error(RecoverableParseError::new(
549            "Sequence domain must have a value domain".to_string(),
550            Some(sequence_domain.range()),
551        ));
552        return Ok(None);
553    };
554
555    // Adding sequence keyword to the source map with hover info from documentation
556    let sequence_keyword_node = child!(sequence_domain, 0, "sequence");
557    ctx.add_span_and_doc_hover(
558        &sequence_keyword_node,
559        "sequence",
560        SymbolKind::Domain,
561        None,
562        None,
563    );
564
565    let attrs = SequenceAttr {
566        size,
567        jectivity,
568        representation: None,
569    };
570    Ok(Some(Domain::sequence(attrs, value_domain)))
571}
572
573// e.g. function int(1..3) --> int(1..3), function (total) bool --> int(13,17),
574// function (minSize 1) int(1..2) --> set (size 1) of int(1..2). Mirrors
575// parse_sequence_domain, plus the extra `partiality` field FuncAttr has.
576fn parse_function_domain(
577    ctx: &mut ParseContext,
578    function_domain: Node,
579) -> Result<Option<DomainPtr>, FatalParseError> {
580    let mut size = Range::Unbounded;
581    let mut min_size = None;
582    let mut max_size = None;
583    let mut partiality = PartialityAttr::Partial;
584    let mut jectivity = JectivityAttr::None;
585
586    for child in named_children(&function_domain) {
587        if child.kind() == "function_attributes" {
588            for attribute in named_children(&child) {
589                let name = attribute
590                    .child_by_field_name("attribute")
591                    .map(|node| &ctx.source_code[node.start_byte()..node.end_byte()])
592                    .unwrap_or_default();
593                match name {
594                    "size" | "minSize" | "maxSize" => {
595                        let Some(value_node) = attribute.child_by_field_name("value") else {
596                            return Ok(None);
597                        };
598                        let Some(value) = parse_int(ctx, &value_node) else {
599                            return Ok(None);
600                        };
601                        match name {
602                            "size" => size = Range::Single(value),
603                            "minSize" => min_size = Some(value),
604                            "maxSize" => max_size = Some(value),
605                            _ => unreachable!(),
606                        }
607                    }
608                    "total" => partiality = PartialityAttr::Total,
609                    "injective" => jectivity = JectivityAttr::Injective,
610                    "surjective" => jectivity = JectivityAttr::Surjective,
611                    "bijective" => jectivity = JectivityAttr::Bijective,
612                    _ => return Ok(None),
613                }
614            }
615        }
616    }
617
618    if !matches!(size, Range::Single(_)) {
619        size = match (min_size, max_size) {
620            (Some(min), Some(max)) => Range::Bounded(min, max),
621            (Some(min), None) => Range::UnboundedR(min),
622            (None, Some(max)) => Range::UnboundedL(max),
623            (None, None) => Range::Unbounded,
624        };
625    }
626
627    let Some(domain_from_node) = function_domain.child_by_field_name("domain_from") else {
628        ctx.record_error(RecoverableParseError::new(
629            "Function domain must have a domain".to_string(),
630            Some(function_domain.range()),
631        ));
632        return Ok(None);
633    };
634    let Some(domain_from) = parse_domain(ctx, domain_from_node)? else {
635        return Ok(None);
636    };
637
638    let Some(domain_to_node) = function_domain.child_by_field_name("domain_to") else {
639        ctx.record_error(RecoverableParseError::new(
640            "Function domain must have a codomain".to_string(),
641            Some(function_domain.range()),
642        ));
643        return Ok(None);
644    };
645    let Some(domain_to) = parse_domain(ctx, domain_to_node)? else {
646        return Ok(None);
647    };
648
649    // Adding function keyword to the source map with hover info from documentation
650    let function_keyword_node = child!(function_domain, 0, "function");
651    ctx.add_span_and_doc_hover(
652        &function_keyword_node,
653        "function",
654        SymbolKind::Domain,
655        None,
656        None,
657    );
658
659    let attrs = FuncAttr {
660        size,
661        partiality,
662        jectivity,
663    };
664    Ok(Some(Domain::function(attrs, domain_from, domain_to)))
665}
666
667// e.g. relation of (int(1..3) * bool), relation (size 2, irreflexive) of (int(0..5) * int(0..5)).
668// Binary relation attributes only make sense for a binary relation whose two columns share a
669// domain; that constraint is enforced by the representations (`init`), not the parser.
670fn parse_relation_domain(
671    ctx: &mut ParseContext,
672    relation_domain: Node,
673) -> Result<Option<DomainPtr>, FatalParseError> {
674    let mut size = Range::Unbounded;
675    let mut min_size = None;
676    let mut max_size = None;
677    let mut binary = Vec::new();
678
679    for child in named_children(&relation_domain) {
680        if child.kind() == "relation_attributes" {
681            for attribute in named_children(&child) {
682                let name = attribute
683                    .child_by_field_name("attribute")
684                    .map(|node| &ctx.source_code[node.start_byte()..node.end_byte()])
685                    .unwrap_or_default();
686                match name {
687                    "size" | "minSize" | "maxSize" => {
688                        let Some(value_node) = attribute.child_by_field_name("value") else {
689                            return Ok(None);
690                        };
691                        let Some(value) = parse_int(ctx, &value_node) else {
692                            return Ok(None);
693                        };
694                        match name {
695                            "size" => size = Range::Single(value),
696                            "minSize" => min_size = Some(value),
697                            "maxSize" => max_size = Some(value),
698                            _ => unreachable!(),
699                        }
700                    }
701                    _ => {
702                        let Some(bin_attr) = BinaryAttr::from_keyword(name) else {
703                            return Ok(None);
704                        };
705                        binary.push(bin_attr);
706                    }
707                }
708            }
709        }
710    }
711
712    if !matches!(size, Range::Single(_)) {
713        size = match (min_size, max_size) {
714            (Some(min), Some(max)) => Range::Bounded(min, max),
715            (Some(min), None) => Range::UnboundedR(min),
716            (None, Some(max)) => Range::UnboundedL(max),
717            (None, None) => Range::Unbounded,
718        };
719    }
720
721    let Some(column_list) = field!(recover, ctx, relation_domain, "relation_domain_list") else {
722        return Ok(None);
723    };
724    let mut columns: Vec<DomainPtr> = Vec::new();
725    for column in named_children(&column_list) {
726        let Some(parsed) = parse_domain(ctx, column)? else {
727            return Ok(None);
728        };
729        columns.push(parsed);
730    }
731
732    // Adding relation keyword to the source map with hover info from documentation
733    let relation_keyword_node = child!(relation_domain, 0, "relation");
734    ctx.add_span_and_doc_hover(
735        &relation_keyword_node,
736        "relation",
737        SymbolKind::Domain,
738        None,
739        None,
740    );
741
742    let attrs = RelAttr { size, binary };
743    Ok(Some(Domain::relation(attrs, columns)))
744}
745
746fn parse_partition_domain(
747    ctx: &mut ParseContext,
748    partition_domain: Node,
749) -> Result<Option<DomainPtr>, FatalParseError> {
750    let mut num_parts = Range::Unbounded;
751    let mut min_num_parts = None;
752    let mut max_num_parts = None;
753    let mut part_len = Range::Unbounded;
754    let mut min_part_len = None;
755    let mut max_part_len = None;
756    let mut is_regular = false;
757
758    for child in named_children(&partition_domain) {
759        if child.kind() == "partition_attributes" {
760            for attribute in named_children(&child) {
761                let name = attribute
762                    .child_by_field_name("attribute")
763                    .map(|node| &ctx.source_code[node.start_byte()..node.end_byte()])
764                    .unwrap_or_default();
765                match name {
766                    "numParts" | "minNumParts" | "maxNumParts" | "partSize" | "minPartSize"
767                    | "maxPartSize" => {
768                        let Some(value_node) = attribute.child_by_field_name("value") else {
769                            return Ok(None);
770                        };
771                        let Some(value) = parse_int(ctx, &value_node) else {
772                            return Ok(None);
773                        };
774                        match name {
775                            "numParts" => num_parts = Range::Single(value),
776                            "minNumParts" => min_num_parts = Some(value),
777                            "maxNumParts" => max_num_parts = Some(value),
778                            "partSize" => part_len = Range::Single(value),
779                            "minPartSize" => min_part_len = Some(value),
780                            "maxPartSize" => max_part_len = Some(value),
781                            _ => unreachable!(),
782                        }
783                    }
784                    "regular" => is_regular = true,
785                    _ => return Ok(None),
786                }
787            }
788        }
789    }
790
791    if !matches!(num_parts, Range::Single(_)) {
792        num_parts = match (min_num_parts, max_num_parts) {
793            (Some(min), Some(max)) => Range::Bounded(min, max),
794            (Some(min), None) => Range::UnboundedR(min),
795            (None, Some(max)) => Range::UnboundedL(max),
796            (None, None) => Range::Unbounded,
797        };
798    }
799    if !matches!(part_len, Range::Single(_)) {
800        part_len = match (min_part_len, max_part_len) {
801            (Some(min), Some(max)) => Range::Bounded(min, max),
802            (Some(min), None) => Range::UnboundedR(min),
803            (None, Some(max)) => Range::UnboundedL(max),
804            (None, None) => Range::Unbounded,
805        };
806    }
807
808    let Some(value_domain) = field!(recover, ctx, partition_domain, "value_domain") else {
809        return Ok(None);
810    };
811    let Some(inner) = parse_domain(ctx, value_domain)? else {
812        return Ok(None);
813    };
814
815    let partition_keyword_node = child!(partition_domain, 0, "partition");
816    ctx.add_span_and_doc_hover(
817        &partition_keyword_node,
818        "partition",
819        SymbolKind::Domain,
820        None,
821        None,
822    );
823
824    let attrs = PartitionAttr {
825        num_parts,
826        part_len,
827        is_regular,
828    };
829    Ok(Some(Domain::partition(attrs, inner)))
830}
831
832fn parse_permutation_domain(
833    ctx: &mut ParseContext,
834    permutation_domain: Node,
835) -> Result<Option<DomainPtr>, FatalParseError> {
836    let mut num_moved = Range::Unbounded;
837    let mut min_num_moved = None;
838    let mut max_num_moved = None;
839
840    for child in named_children(&permutation_domain) {
841        if child.kind() == "permutation_attributes" {
842            for attribute in named_children(&child) {
843                let name = attribute
844                    .child_by_field_name("attribute")
845                    .map(|node| &ctx.source_code[node.start_byte()..node.end_byte()])
846                    .unwrap_or_default();
847                let Some(value_node) = attribute.child_by_field_name("value") else {
848                    return Ok(None);
849                };
850                let Some(value) = parse_int(ctx, &value_node) else {
851                    return Ok(None);
852                };
853                match name {
854                    "numMoved" => num_moved = Range::Single(value),
855                    "minNumMoved" => min_num_moved = Some(value),
856                    "maxNumMoved" => max_num_moved = Some(value),
857                    _ => return Ok(None),
858                }
859            }
860        }
861    }
862
863    if !matches!(num_moved, Range::Single(_)) {
864        num_moved = match (min_num_moved, max_num_moved) {
865            (Some(min), Some(max)) => Range::Bounded(min, max),
866            (Some(min), None) => Range::UnboundedR(min),
867            (None, Some(max)) => Range::UnboundedL(max),
868            (None, None) => Range::Unbounded,
869        };
870    }
871
872    let Some(value_domain) = field!(recover, ctx, permutation_domain, "value_domain") else {
873        return Ok(None);
874    };
875    let Some(inner) = parse_domain(ctx, value_domain)? else {
876        return Ok(None);
877    };
878
879    let permutation_keyword_node = child!(permutation_domain, 0, "permutation");
880    ctx.add_span_and_doc_hover(
881        &permutation_keyword_node,
882        "permutation",
883        SymbolKind::Domain,
884        None,
885        None,
886    );
887
888    let attrs = PermutationAttr { num_moved };
889    Ok(Some(Domain::permutation(attrs, inner)))
890}
891
892pub fn parse_set_domain(
893    ctx: &mut ParseContext,
894    set_domain: Node,
895) -> Result<Option<DomainPtr>, FatalParseError> {
896    let mut size = Range::Unbounded;
897    let mut min_size = None;
898    let mut max_size = None;
899    let mut representation = None;
900    let mut value_domain: Option<DomainPtr> = None;
901
902    for child in named_children(&set_domain) {
903        match child.kind() {
904            "set_attributes" => {
905                for attribute in named_children(&child) {
906                    let Some(value_node) = attribute.child_by_field_name("value") else {
907                        return Ok(None);
908                    };
909                    let name = attribute
910                        .child_by_field_name("attribute")
911                        .map(|node| &ctx.source_code[node.start_byte()..node.end_byte()])
912                        .unwrap_or_default();
913                    if name == "representation" {
914                        representation = Some(
915                            ctx.source_code[value_node.start_byte()..value_node.end_byte()]
916                                .to_string(),
917                        );
918                        continue;
919                    }
920                    // Attribute values may be expressions over givens, e.g. `maxSize |nums|`,
921                    // which only become ground once the parameters are instantiated.
922                    let Some(value) = parse_int_val(ctx, value_node)? else {
923                        return Ok(None);
924                    };
925                    match name {
926                        "size" => size = Range::Single(value),
927                        "minSize" => min_size = Some(value),
928                        "maxSize" => max_size = Some(value),
929                        _ => return Ok(None),
930                    }
931                }
932            }
933            "domain" | "annotation_domain" => {
934                let Some(parsed_domain) = parse_domain(ctx, child)? else {
935                    return Ok(None);
936                };
937                value_domain = Some(parsed_domain);
938            }
939            _ => {
940                ctx.record_error(RecoverableParseError::new(
941                    format!("Unrecognized set domain child kind: {}", child.kind()),
942                    Some(child.range()),
943                ));
944                return Ok(None);
945            }
946        }
947    }
948
949    if let Some(domain) = value_domain {
950        // Adding set to the source map with hover info from documentation
951        let set_keyword_node = child!(set_domain, 0, "set");
952        // No documentation available for set domain, using fallback description
953        ctx.add_span_and_doc_hover(&set_keyword_node, "set", SymbolKind::Domain, None, None);
954        if !matches!(size, Range::Single(_)) {
955            size = match (min_size, max_size) {
956                (Some(min), Some(max)) => Range::Bounded(min, max),
957                (Some(min), None) => Range::UnboundedR(min),
958                (None, Some(max)) => Range::UnboundedL(max),
959                (None, None) => Range::Unbounded,
960            };
961        }
962        let mut attr = SetAttr::new(size);
963        if let Some(repr) = representation {
964            attr = attr.with_representation(repr);
965        }
966        Ok(Some(Domain::set(attr, domain)))
967    } else {
968        ctx.record_error(RecoverableParseError::new(
969            "Set domain must have a value domain".to_string(),
970            Some(set_domain.range()),
971        ));
972        Ok(None)
973    }
974}
975
976/// Parses `node` when it denotes a collection of integers, for `int(<collection>)`.
977///
978/// Returns `None` without recording an error when it is something else, so the caller can carry on
979/// reading the node as an ordinary range bound.
980fn parse_collection_valued_expression(
981    ctx: &mut ParseContext,
982    node: Node,
983) -> Result<Option<Expression>, FatalParseError> {
984    let saved_context = ctx.typechecking_context;
985    let saved_inner_context = ctx.inner_typechecking_context;
986    ctx.typechecking_context = TypecheckingContext::Unknown;
987    ctx.inner_typechecking_context = TypecheckingContext::Unknown;
988    let parsed = parse_expression(ctx, node);
989    ctx.typechecking_context = saved_context;
990    ctx.inner_typechecking_context = saved_inner_context;
991
992    let Some(expr) = parsed? else {
993        return Ok(None);
994    };
995    use conjure_cp_core::ast::Typeable;
996
997    // A comprehension's return type is its *element* type, so the variant has to be recognised
998    // directly rather than asked for its type.
999    if matches!(expr, Expression::Comprehension(_, _)) {
1000        return Ok(Some(expr));
1001    }
1002
1003    Ok(matches!(
1004        expr.return_type(),
1005        conjure_cp_core::ast::ReturnType::Set(_)
1006            | conjure_cp_core::ast::ReturnType::MSet(_)
1007            | conjure_cp_core::ast::ReturnType::Matrix(_)
1008            | conjure_cp_core::ast::ReturnType::Sequence(_)
1009    )
1010    .then_some(expr))
1011}