use crate::range::Range;
use crate::version::Version;
use std::fmt;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Term<V: Version> {
Positive(Range<V>),
Negative(Range<V>),
}
impl<V: Version> Term<V> {
pub(crate) fn any() -> Self {
Self::Negative(Range::none())
}
pub(crate) fn empty() -> Self {
Self::Positive(Range::none())
}
pub(crate) fn exact(version: V) -> Self {
Self::Positive(Range::exact(version))
}
pub(crate) fn is_positive(&self) -> bool {
match self {
Self::Positive(_) => true,
Self::Negative(_) => false,
}
}
pub(crate) fn negate(&self) -> Self {
match self {
Self::Positive(range) => Self::Negative(range.clone()),
Self::Negative(range) => Self::Positive(range.clone()),
}
}
pub(crate) fn contains(&self, v: &V) -> bool {
match self {
Self::Positive(range) => range.contains(v),
Self::Negative(range) => !(range.contains(v)),
}
}
pub(crate) fn unwrap_positive(&self) -> &Range<V> {
match self {
Self::Positive(range) => range,
_ => panic!("Negative term cannot unwrap positive range"),
}
}
}
impl<V: Version> Term<V> {
pub(crate) fn intersection(&self, other: &Term<V>) -> Term<V> {
match (self, other) {
(Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(r2)),
(Self::Positive(r1), Self::Negative(r2)) => {
Self::Positive(r1.intersection(&r2.negate()))
}
(Self::Negative(r1), Self::Positive(r2)) => {
Self::Positive(r1.negate().intersection(r2))
}
(Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.union(r2)),
}
}
pub(crate) fn union(&self, other: &Term<V>) -> Term<V> {
(self.negate().intersection(&other.negate())).negate()
}
pub(crate) fn subset_of(&self, other: &Term<V>) -> bool {
self == &self.intersection(other)
}
}
pub(crate) enum Relation {
Satisfied,
Contradicted,
Inconclusive,
}
impl<'a, V: 'a + Version> Term<V> {
#[cfg(test)]
fn satisfied_by(&self, terms_intersection: &Term<V>) -> bool {
terms_intersection.subset_of(self)
}
#[cfg(test)]
fn contradicted_by(&self, terms_intersection: &Term<V>) -> bool {
terms_intersection.intersection(self) == Self::empty()
}
pub(crate) fn relation_with(&self, other_terms_intersection: &Term<V>) -> Relation {
let full_intersection = self.intersection(other_terms_intersection.as_ref());
if &full_intersection == other_terms_intersection {
Relation::Satisfied
} else if full_intersection == Self::empty() {
Relation::Contradicted
} else {
Relation::Inconclusive
}
}
}
impl<V: Version> AsRef<Term<V>> for Term<V> {
fn as_ref(&self) -> &Term<V> {
&self
}
}
impl<V: Version + fmt::Display> fmt::Display for Term<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Positive(range) => write!(f, "{}", range),
Self::Negative(range) => write!(f, "Not ( {} )", range),
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::version::NumberVersion;
use proptest::prelude::*;
pub fn strategy() -> impl Strategy<Value = Term<NumberVersion>> {
prop_oneof![
crate::range::tests::strategy().prop_map(Term::Positive),
crate::range::tests::strategy().prop_map(Term::Negative),
]
}
proptest! {
#[test]
fn relation_with(term1 in strategy(), term2 in strategy()) {
match term1.relation_with(&term2) {
Relation::Satisfied => assert!(term1.satisfied_by(&term2)),
Relation::Contradicted => assert!(term1.contradicted_by(&term2)),
Relation::Inconclusive => {
assert!(!term1.satisfied_by(&term2));
assert!(!term1.contradicted_by(&term2));
}
}
}
}
}