Lines
93.59 %
Functions
78.57 %
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use super::{types::Typeable, Name, ReturnType};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Range<A>
where
A: Ord,
{
Single(A),
Bounded(A, A),
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Domain {
BoolDomain,
IntDomain(Vec<Range<i32>>),
DomainReference(Name),
impl Domain {
/// Return a list of all possible i32 values in the domain if it is an IntDomain.
pub fn values_i32(&self) -> Option<Vec<i32>> {
match self {
Domain::IntDomain(ranges) => Some(
ranges
.iter()
.flat_map(|r| match r {
Range::Single(i) => vec![*i],
Range::Bounded(i, j) => (*i..=*j).collect(),
})
.collect(),
),
_ => None,
/// Return an unoptimised domain that is the result of applying a binary i32 operation to two domains.
///
/// The given operator may return None if the operation is not defined for its arguments.
/// Undefined values will not be included in the resulting domain.
/// Returns None if the domains are not valid for i32 operations.
pub fn apply_i32(&self, op: fn(i32, i32) -> Option<i32>, other: &Domain) -> Option<Domain> {
if let (Some(vs1), Some(vs2)) = (self.values_i32(), other.values_i32()) {
// TODO: (flm8) Optimise to use smarter, less brute-force methods
let mut new_ranges = vec![];
for (v1, v2) in itertools::iproduct!(vs1, vs2) {
if let Some(v) = op(v1, v2) {
new_ranges.push(Range::Single(v))
return Some(Domain::IntDomain(new_ranges));
None
impl Display for Domain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Domain::BoolDomain => {
write!(f, "bool")
Domain::IntDomain(vec) => {
let mut domain_ranges: Vec<String> = vec![];
for range in vec {
domain_ranges.push(match range {
Range::Single(a) => a.to_string(),
Range::Bounded(a, b) => format!("{}..{}", a, b),
});
if domain_ranges.is_empty() {
write!(f, "int")
} else {
write!(f, "int({})", domain_ranges.join(","))
Domain::DomainReference(name) => write!(f, "{}", name),
impl Typeable for Domain {
fn return_type(&self) -> Option<ReturnType> {
todo!()
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_negative_product() {
let d1 = Domain::IntDomain(vec![Range::Bounded(-2, 1)]);
let d2 = Domain::IntDomain(vec![Range::Bounded(-2, 1)]);
let res = d1.apply_i32(|a, b| Some(a * b), &d2).unwrap();
assert!(matches!(res, Domain::IntDomain(_)));
if let Domain::IntDomain(ranges) = res {
assert!(!ranges.contains(&Range::Single(-4)));
assert!(!ranges.contains(&Range::Single(-3)));
assert!(ranges.contains(&Range::Single(-2)));
assert!(ranges.contains(&Range::Single(-1)));
assert!(ranges.contains(&Range::Single(0)));
assert!(ranges.contains(&Range::Single(1)));
assert!(ranges.contains(&Range::Single(2)));
assert!(!ranges.contains(&Range::Single(3)));
assert!(ranges.contains(&Range::Single(4)));
fn test_negative_div() {
let res = d1
.apply_i32(|a, b| if b != 0 { Some(a / b) } else { None }, &d2)
.unwrap();
assert!(!ranges.contains(&Range::Single(4)));