Skip to main content

conjure_cp_core/ast/domains/
attrs.rs

1use crate::ast::domains::Int;
2use crate::ast::domains::range::Range;
3use funcmap::{FuncMap, TryFuncMap};
4use itertools::Itertools;
5use polyquine::Quine;
6use serde::{Deserialize, Serialize};
7use std::fmt::{Display, Formatter};
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, FuncMap, TryFuncMap, Quine)]
10#[path_prefix(conjure_cp::ast)]
11pub struct SetAttr<A = Int> {
12    pub size: Range<A>,
13    /// Optional user-facing representation preference (short name), e.g. `"packed"`.
14    ///
15    /// Written in Essence as `set (representation packed) of …`. When present, representation selection
16    /// heuristics default to this representation if it is applicable.
17    pub representation: Option<String>,
18}
19
20impl<A> SetAttr<A> {
21    pub fn new(size: Range<A>) -> Self {
22        Self {
23            size,
24            representation: None,
25        }
26    }
27
28    pub fn new_min_max_size(min: A, max: A) -> Self {
29        Self::new(Range::Bounded(min, max))
30    }
31
32    pub fn new_min_size(min: A) -> Self {
33        Self::new(Range::UnboundedR(min))
34    }
35
36    pub fn new_max_size(max: A) -> Self {
37        Self::new(Range::UnboundedL(max))
38    }
39
40    pub fn new_size(sz: A) -> Self {
41        Self::new(Range::Single(sz))
42    }
43
44    /// Set the representation preference (Essence short name), returning the updated attributes.
45    pub fn with_representation(mut self, name: impl Into<String>) -> Self {
46        self.representation = Some(name.into());
47        self
48    }
49}
50
51impl<A> Default for SetAttr<A> {
52    fn default() -> Self {
53        SetAttr {
54            size: Range::Unbounded,
55            representation: None,
56        }
57    }
58}
59
60impl<A: Display> Display for SetAttr<A> {
61    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
62        let mut attrs = Vec::new();
63        if let Some(representation) = &self.representation {
64            attrs.push(format!("representation {representation}"));
65        }
66        let size = fmt_size("size", &self.size);
67        if !size.is_empty() {
68            attrs.push(size);
69        }
70        if attrs.is_empty() {
71            Ok(())
72        } else {
73            write!(f, "({})", attrs.join(", "))
74        }
75    }
76}
77
78#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, FuncMap, TryFuncMap, Quine)]
79#[path_prefix(conjure_cp::ast)]
80pub struct MSetAttr<A = Int> {
81    pub size: Range<A>,
82    pub occurrence: Range<A>,
83    /// Optional user-facing representation preference (short name), e.g. `"repetition"`.
84    ///
85    /// Written in Essence as `mset (representation repetition) of …`. When present, representation selection
86    /// heuristics default to this representation if it is applicable.
87    #[serde(default)]
88    pub representation: Option<String>,
89}
90
91impl<A> MSetAttr<A> {
92    pub fn new(size: Range<A>, occurrence: Range<A>) -> Self {
93        Self {
94            size,
95            occurrence,
96            representation: None,
97        }
98    }
99
100    pub fn new_min_max_size(min: A, max: A) -> Self {
101        Self::new(Range::Bounded(min, max), Range::Unbounded)
102    }
103
104    pub fn new_min_size(min: A) -> Self {
105        Self::new(Range::UnboundedR(min), Range::Unbounded)
106    }
107
108    pub fn new_max_size(max: A) -> Self {
109        Self::new(Range::UnboundedL(max), Range::Unbounded)
110    }
111
112    pub fn new_size(sz: A) -> Self {
113        Self::new(Range::Single(sz), Range::Unbounded)
114    }
115
116    /// Set the representation preference (Essence short name), returning the updated attributes.
117    pub fn with_representation(mut self, name: impl Into<String>) -> Self {
118        self.representation = Some(name.into());
119        self
120    }
121}
122
123impl<A: Display> Display for MSetAttr<A> {
124    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125        let size_str = fmt_size("size", &self.size);
126
127        // It only makes sense in terms of min and max occurrence for the essence language,
128        // so for single ranges it is still presented as min and max occurrence.
129        let occ_str = match &self.occurrence {
130            Range::Single(x) => format!("minOccur({x}), maxOccur({x})"),
131            Range::Bounded(l, r) => format!("minOccur({l}), maxOccur({r})"),
132            Range::UnboundedL(r) => format!("maxOccur({r})"),
133            Range::UnboundedR(l) => format!("minOccur({l})"),
134            Range::Unbounded => "".to_string(),
135        };
136
137        let representation_str = self
138            .representation
139            .as_ref()
140            .map(|name| format!("representation {name}"))
141            .unwrap_or_default();
142        let mut strs = [representation_str, size_str, occ_str]
143            .iter()
144            .filter(|s| !s.is_empty())
145            .join(", ");
146        if !strs.is_empty() {
147            strs = format!("({})", strs);
148        }
149        write!(f, "{strs}")
150    }
151}
152
153impl<A> Default for MSetAttr<A> {
154    fn default() -> Self {
155        MSetAttr {
156            size: Range::Unbounded,
157            occurrence: Range::Unbounded,
158            representation: None,
159        }
160    }
161}
162
163#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, FuncMap, TryFuncMap, Quine)]
164#[path_prefix(conjure_cp::ast)]
165pub struct FuncAttr<A = Int> {
166    pub size: Range<A>,
167    pub partiality: PartialityAttr,
168    pub jectivity: JectivityAttr,
169    /// Optional user-facing representation preference (short name), e.g. `"packed"`.
170    ///
171    /// Written in Essence as `function (representation packed, …)`. When present, representation
172    /// selection heuristics default to this representation if it is applicable.
173    pub representation: Option<String>,
174}
175
176impl<A> FuncAttr<A> {
177    /// Set the representation preference (Essence short name), returning the updated attributes.
178    pub fn with_representation(mut self, name: impl Into<String>) -> Self {
179        self.representation = Some(name.into());
180        self
181    }
182}
183
184impl<A> Default for FuncAttr<A> {
185    fn default() -> Self {
186        FuncAttr {
187            size: Range::Unbounded,
188            // Matches Conjure's own default (`Domain.hs`): a bare `function` with no explicit
189            // `total` attribute is partial.
190            partiality: PartialityAttr::Partial,
191            jectivity: JectivityAttr::None,
192            representation: None,
193        }
194    }
195}
196
197impl<A: Display> Display for FuncAttr<A> {
198    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
199        let size_str = fmt_size("size", &self.size);
200        let mut strs = [
201            fmt_representation(&self.representation),
202            size_str,
203            self.partiality.to_string(),
204            self.jectivity.to_string(),
205        ]
206        .iter()
207        .filter(|s| !s.is_empty())
208        .join(", ");
209        if !strs.is_empty() {
210            strs = format!("({})", strs);
211        }
212        write!(f, "{strs}")
213    }
214}
215
216#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, FuncMap, TryFuncMap, Quine)]
217#[path_prefix(conjure_cp::ast)]
218pub struct SequenceAttr<A = Int> {
219    pub size: Range<A>,
220    pub jectivity: JectivityAttr,
221    /// Optional user-facing representation preference (short name), e.g. `"packed"`.
222    ///
223    /// Written in Essence as `sequence{packed} of …`. When present, representation selection
224    /// heuristics default to this representation if it is applicable.
225    pub representation: Option<String>,
226}
227
228impl<A> SequenceAttr<A> {
229    /// Set the representation preference (Essence short name), returning the updated attributes.
230    pub fn with_representation(mut self, name: impl Into<String>) -> Self {
231        self.representation = Some(name.into());
232        self
233    }
234}
235
236impl<A> Default for SequenceAttr<A> {
237    fn default() -> Self {
238        SequenceAttr {
239            size: Range::Unbounded,
240            jectivity: JectivityAttr::None,
241            representation: None,
242        }
243    }
244}
245
246impl<A: Display> Display for SequenceAttr<A> {
247    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
248        let size_str = fmt_size("size", &self.size);
249        let mut strs = [
250            fmt_representation(&self.representation),
251            size_str,
252            self.jectivity.to_string(),
253        ]
254        .iter()
255        .filter(|s| !s.is_empty())
256        .join(", ");
257        if !strs.is_empty() {
258            strs = format!("({})", strs);
259        }
260        write!(f, "{strs}")
261    }
262}
263
264#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, FuncMap, TryFuncMap, Quine)]
265#[path_prefix(conjure_cp::ast)]
266pub struct PartitionAttr<A = Int> {
267    pub num_parts: Range<A>, // i.e. how many parts there are in the partition
268    pub part_len: Range<A>,  // i.e. the size of each constitutent part
269    pub is_regular: bool,
270    /// Optional user-facing representation preference (short name), e.g. `"packed"`.
271    ///
272    /// Written in Essence as `partition (representation packed, …)`. When present, representation
273    /// selection heuristics default to this representation if it is applicable.
274    pub representation: Option<String>,
275}
276
277impl<A> PartitionAttr<A> {
278    /// Set the representation preference (Essence short name), returning the updated attributes.
279    pub fn with_representation(mut self, name: impl Into<String>) -> Self {
280        self.representation = Some(name.into());
281        self
282    }
283}
284
285impl<A: Display> Display for PartitionAttr<A> {
286    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287        let num_parts_str = fmt_size("numParts", &self.num_parts);
288        let part_len_str = fmt_size("partSize", &self.part_len);
289
290        let regular_str = match &self.is_regular {
291            true => "regular".to_string(),
292            false => String::new(),
293        };
294
295        let mut strs = [
296            fmt_representation(&self.representation),
297            num_parts_str,
298            part_len_str,
299            regular_str,
300        ]
301        .iter()
302        .filter(|s| !s.is_empty())
303        .join(", ");
304        if !strs.is_empty() {
305            strs = format!("({})", strs);
306        }
307        write!(f, "{strs}")
308    }
309}
310
311impl<A> Default for PartitionAttr<A> {
312    fn default() -> Self {
313        PartitionAttr {
314            num_parts: Range::Unbounded,
315            part_len: Range::Unbounded,
316            is_regular: false,
317            representation: None,
318        }
319    }
320}
321
322/// A permutation is inherently total and bijective by definition, so unlike partition or
323/// function, its only attribute is a size constraint on `numMoved`, the number of moved points
324/// (i.e. the points not fixed by the permutation) -- confirmed against real Conjure's own
325/// `numMoved`/`minNumMoved`/`maxNumMoved` keywords (real Conjure does not accept
326/// `size`/`minSize`/`maxSize` here, unlike set/relation/etc).
327#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, FuncMap, TryFuncMap, Quine)]
328#[path_prefix(conjure_cp::ast)]
329pub struct PermutationAttr<A = Int> {
330    pub num_moved: Range<A>,
331    /// Optional user-facing representation preference (short name), e.g. `"packed"`.
332    ///
333    /// Written in Essence as `permutation (representation packed, …)`. When present, representation
334    /// selection heuristics default to this representation if it is applicable.
335    pub representation: Option<String>,
336}
337
338impl<A> PermutationAttr<A> {
339    /// Set the representation preference (Essence short name), returning the updated attributes.
340    pub fn with_representation(mut self, name: impl Into<String>) -> Self {
341        self.representation = Some(name.into());
342        self
343    }
344}
345
346impl<A: Display> Display for PermutationAttr<A> {
347    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
348        let size_str = fmt_size("numMoved", &self.num_moved);
349        match &self.representation {
350            // Without a preference the size is printed bare, as it always has been.
351            None => write!(f, "{size_str}"),
352            Some(representation) if size_str.is_empty() => {
353                write!(f, "(representation {representation})")
354            }
355            Some(representation) => write!(f, "(representation {representation}, {size_str})"),
356        }
357    }
358}
359
360impl<A> Default for PermutationAttr<A> {
361    fn default() -> Self {
362        PermutationAttr {
363            num_moved: Range::Unbounded,
364            representation: None,
365        }
366    }
367}
368
369#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine)]
370pub enum PartialityAttr {
371    Total,
372    Partial,
373}
374
375impl Display for PartialityAttr {
376    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
377        match self {
378            PartialityAttr::Total => write!(f, "total"),
379            PartialityAttr::Partial => write!(f, ""),
380        }
381    }
382}
383
384#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine)]
385pub enum JectivityAttr {
386    None,
387    Injective,
388    Surjective,
389    Bijective,
390}
391
392impl Display for JectivityAttr {
393    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
394        match self {
395            JectivityAttr::None => write!(f, ""),
396            JectivityAttr::Injective => write!(f, "injective"),
397            JectivityAttr::Surjective => write!(f, "surjective"),
398            JectivityAttr::Bijective => write!(f, "bijective"),
399        }
400    }
401}
402
403#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, FuncMap, TryFuncMap, Quine)]
404#[path_prefix(conjure_cp::ast)]
405pub struct RelAttr<A = Int> {
406    pub size: Range<A>,
407    pub binary: Vec<BinaryAttr>,
408    /// Optional user-facing representation preference (short name), e.g. `"packed"`.
409    ///
410    /// Written in Essence as `relation (representation packed, …)`. When present, representation
411    /// selection heuristics default to this representation if it is applicable.
412    pub representation: Option<String>,
413}
414
415impl<A> RelAttr<A> {
416    /// Set the representation preference (Essence short name), returning the updated attributes.
417    pub fn with_representation(mut self, name: impl Into<String>) -> Self {
418        self.representation = Some(name.into());
419        self
420    }
421}
422
423impl<A> Default for RelAttr<A> {
424    fn default() -> Self {
425        RelAttr {
426            size: Range::Unbounded,
427            binary: Vec::new(),
428            representation: None,
429        }
430    }
431}
432
433impl<A: Display> Display for RelAttr<A> {
434    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
435        let size_str = fmt_size("size", &self.size);
436        let mut strs = [
437            fmt_representation(&self.representation),
438            size_str,
439            self.binary.iter().join(", "),
440        ]
441        .iter()
442        .filter(|s| !s.is_empty())
443        .join(", ");
444        if !strs.is_empty() {
445            strs = format!("({})", strs);
446        }
447        write!(f, "{strs}")
448    }
449}
450
451#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine)]
452pub enum BinaryAttr {
453    Reflexive,
454    Irreflexive,
455    Coreflexive,
456    Symmetric,
457    AntiSymmetric,
458    ASymmetric,
459    Transitive,
460    Total,
461    Connex,
462    Euclidean,
463    Serial,
464    Equivalence,
465    PartialOrder,
466    LeftTotal,
467    RightTotal,
468    LinearOrder,
469    WeakOrder,
470    PreOrder,
471    StrictPartialOrder,
472}
473
474impl Display for BinaryAttr {
475    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
476        match self {
477            BinaryAttr::Reflexive => write!(f, "reflexive"),
478            BinaryAttr::Irreflexive => write!(f, "irreflexive"),
479            BinaryAttr::Coreflexive => write!(f, "coreflexive"),
480            BinaryAttr::Symmetric => write!(f, "symmetric"),
481            BinaryAttr::AntiSymmetric => write!(f, "antiSymmetric"),
482            BinaryAttr::ASymmetric => write!(f, "aSymmetric"),
483            BinaryAttr::Transitive => write!(f, "transitive"),
484            BinaryAttr::Total => write!(f, "total"),
485            BinaryAttr::Connex => write!(f, "connex"),
486            BinaryAttr::Euclidean => write!(f, "Euclidean"),
487            BinaryAttr::Serial => write!(f, "serial"),
488            BinaryAttr::Equivalence => write!(f, "equivalence"),
489            BinaryAttr::PartialOrder => write!(f, "partialOrder"),
490            BinaryAttr::LeftTotal => write!(f, "leftTotal"),
491            BinaryAttr::RightTotal => write!(f, "rightTotal"),
492            BinaryAttr::LinearOrder => write!(f, "linearOrder"),
493            BinaryAttr::WeakOrder => write!(f, "weakOrder"),
494            BinaryAttr::PreOrder => write!(f, "preOrder"),
495            BinaryAttr::StrictPartialOrder => write!(f, "strictPartialOrder"),
496        }
497    }
498}
499
500impl BinaryAttr {
501    /// Parses the Essence keyword for a binary relation attribute (the inverse of `Display`).
502    /// Used both by native domain-attribute parsing (`relation (reflexive, ...) of ...`) and by
503    /// attribute-as-constraint lifting (`reflexive(r)`).
504    pub fn from_keyword(s: &str) -> Option<Self> {
505        Some(match s {
506            "reflexive" => BinaryAttr::Reflexive,
507            "irreflexive" => BinaryAttr::Irreflexive,
508            "coreflexive" => BinaryAttr::Coreflexive,
509            "symmetric" => BinaryAttr::Symmetric,
510            "antiSymmetric" => BinaryAttr::AntiSymmetric,
511            "aSymmetric" => BinaryAttr::ASymmetric,
512            "transitive" => BinaryAttr::Transitive,
513            "total" => BinaryAttr::Total,
514            "connex" => BinaryAttr::Connex,
515            "Euclidean" => BinaryAttr::Euclidean,
516            "serial" => BinaryAttr::Serial,
517            "equivalence" => BinaryAttr::Equivalence,
518            "partialOrder" => BinaryAttr::PartialOrder,
519            "leftTotal" => BinaryAttr::LeftTotal,
520            "rightTotal" => BinaryAttr::RightTotal,
521            "linearOrder" => BinaryAttr::LinearOrder,
522            "weakOrder" => BinaryAttr::WeakOrder,
523            "preOrder" => BinaryAttr::PreOrder,
524            "strictPartialOrder" => BinaryAttr::StrictPartialOrder,
525            _ => return None,
526        })
527    }
528}
529
530/// Format a representation preference as an Essence attribute, or nothing without one.
531fn fmt_representation(representation: &Option<String>) -> String {
532    match representation {
533        Some(representation) => format!("representation {representation}"),
534        None => String::new(),
535    }
536}
537
538/// Format a range as Essence size attribute
539#[inline]
540fn fmt_size<A: Display>(suffix: &str, sz: &Range<A>) -> String {
541    let cap_suffix = capitalize(suffix);
542    match sz {
543        Range::Single(x) => format!("{suffix} {x}"),
544        Range::Bounded(l, r) => format!("min{cap_suffix} {l}, max{cap_suffix} {r}"),
545        Range::UnboundedL(r) => format!("max{cap_suffix} {r}"),
546        Range::UnboundedR(l) => format!("min{cap_suffix} {l}"),
547        Range::Unbounded => "".to_string(),
548    }
549}
550
551#[inline]
552fn capitalize(s: &str) -> String {
553    let mut c = s.chars();
554    match c.next() {
555        None => String::new(),
556        Some(f) => f.to_uppercase().to_string() + c.as_str(),
557    }
558}