Skip to content

Commit

Permalink
Refactor bound types.
Browse files Browse the repository at this point in the history
This change renames "non-empty" bound types to "variant" bound types and
generalizes `BoundedVariantRange`'s `union` and `extension` operations.
This has a nice result of composing well with disjunction for naturals:
there is no need to explicitly check for non-zero invariants. This
arguably "fixes" the `Disjunction` implementation for `Variance` too,
since the disjunction of bounded ranges ought to produce a potentially
unbounded output.
  • Loading branch information
olson-sean-k committed Feb 13, 2024
1 parent 01c893d commit d5f20cf
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 157 deletions.
1 change: 1 addition & 0 deletions src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ where
}

// TODO: Implement this iteratively.
// TODO: Encode expressions using the HIR in `regex-syntax` rather than text.
// TODO: Some versions of `const_format` in `^0.2.0` fail this lint in `formatcp`. See
// https://github.com/rodrimati1992/const_format_crates/issues/38
#[allow(clippy::double_parens)]
Expand Down
20 changes: 20 additions & 0 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,26 @@ impl From<bool> for When {
}
}

impl From<Option<bool>> for When {
fn from(when: Option<bool>) -> Self {
match when {
Some(true) => When::Always,
None => When::Sometimes,
Some(false) => When::Never,
}
}
}

impl From<When> for Option<bool> {
fn from(when: When) -> Self {
match when {
When::Always => Some(true),
When::Sometimes => None,
When::Never => Some(false),
}
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Composition<C, D> {
Conjunctive(C),
Expand Down
188 changes: 81 additions & 107 deletions src/token/variance/bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,23 @@ pub trait OpenedUpperBound: Sized {
fn opened_upper_bound(self) -> Bound<Self>;
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Operand<T> {
pub lhs: T,
pub rhs: T,
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct LowerUpper<L, U> {
pub lower: L,
pub upper: U,
}

pub type LowerUpperBound<B> = LowerUpper<Lower<B>, Upper<B>>;
pub type LowerUpperOperand<B> = LowerUpper<Operand<Lower<B>>, Operand<Upper<B>>>;

pub type NaturalBound = Variance<Zero, NonZeroBound>;
pub type NaturalRange = Variance<usize, NonEmptyRange>;
pub type NaturalRange = Variance<usize, VariantRange>;

impl Conjunction for NaturalBound {
type Output = Self;
Expand Down Expand Up @@ -125,7 +140,7 @@ impl NaturalRange {
};
match (lower, upper) {
(0, None) => Variance::Variant(Bound::Unbounded),
(lower, upper) => BoundedNonEmptyRange::try_from_lower_and_upper(lower, upper)
(lower, upper) => BoundedVariantRange::try_from_lower_and_upper(lower, upper)
.ok()
.map(Bound::Bounded)
.map_or_else(|| Variance::Invariant(lower), Variance::Variant),
Expand All @@ -142,6 +157,24 @@ impl NaturalRange {
Self::from_closed_and_open(lower.into_usize(), upper.into_usize())
}

fn by_lower_and_upper_with<F>(self, rhs: Self, mut f: F) -> Self
where
F: FnMut(LowerUpperOperand<NaturalBound>) -> LowerUpperBound<NaturalBound>,
{
let lhs = self;
let LowerUpper { lower, upper } = f(LowerUpper {
lower: Operand {
lhs: lhs.lower(),
rhs: rhs.lower(),
},
upper: Operand {
lhs: lhs.upper(),
rhs: rhs.upper(),
},
});
Self::from_closed_and_open(lower.into_usize(), upper.into_usize())
}

pub fn lower(&self) -> Lower<NaturalBound> {
match self {
Variance::Invariant(ref n) => NaturalBound::from(*n).into_lower(),
Expand All @@ -157,24 +190,24 @@ impl NaturalRange {
}
}

impl From<BoundedNonEmptyRange> for NaturalRange {
fn from(range: BoundedNonEmptyRange) -> Self {
impl From<BoundedVariantRange> for NaturalRange {
fn from(range: BoundedVariantRange) -> Self {
Variance::Variant(range.into())
}
}

impl From<NonEmptyRange> for NaturalRange {
fn from(range: NonEmptyRange) -> Self {
Variance::Variant(range)
}
}

impl From<usize> for NaturalRange {
fn from(n: usize) -> Self {
Variance::Invariant(n)
}
}

impl From<VariantRange> for NaturalRange {
fn from(range: VariantRange) -> Self {
Variance::Variant(range)
}
}

// NOTE: Given the naturals X and Y where X < Y, this defines an unconventional meaning for the
// range [Y,X] and repetitions like `<_:10,1>`: the bounds are reordered, so `<_:10,1>` and
// `<_:1,10>` are the same.
Expand Down Expand Up @@ -215,7 +248,7 @@ pub enum Bound<T> {
}

pub type NonZeroBound = Bound<NonZeroUsize>;
pub type NonEmptyRange = Bound<BoundedNonEmptyRange>;
pub type VariantRange = Bound<BoundedVariantRange>;

impl Bound<UnitBound> {
pub const BOUNDED: Self = Bound::Bounded(UnitBound);
Expand Down Expand Up @@ -273,23 +306,21 @@ impl From<usize> for NonZeroBound {
}
}

impl NonEmptyRange {
impl VariantRange {
pub fn lower(&self) -> Lower<NonZeroBound> {
self.as_ref().bounded().map_or_else(
|| Bound::Unbounded.into_lower(),
BoundedNonEmptyRange::lower,
)
self.as_ref()
.bounded()
.map_or_else(|| Bound::Unbounded.into_lower(), BoundedVariantRange::lower)
}

pub fn upper(&self) -> Upper<NonZeroBound> {
self.as_ref().bounded().map_or_else(
|| Bound::Unbounded.into_upper(),
BoundedNonEmptyRange::upper,
)
self.as_ref()
.bounded()
.map_or_else(|| Bound::Unbounded.into_upper(), BoundedVariantRange::upper)
}
}

impl Product<NonZeroUsize> for NonEmptyRange {
impl Product<NonZeroUsize> for VariantRange {
type Output = Self;

fn product(self, rhs: NonZeroUsize) -> Self::Output {
Expand Down Expand Up @@ -460,29 +491,23 @@ impl NonZeroUpper {
}
}

// TODO: Consider using the term "variant" rather than "non-empty".
// TODO: This implementation uses a non-trivial amount of unsafe code. Try to reduce this as much
// as possible to as few constructors as possible or consider `expect`ing outputs instead.
// TODO: Especially considering the unsafe code, implement tests for this type.
// NOTE: By design, this cannot represent an invariant range, such as exactly three. This is what
// "empty" refers to here.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum BoundedNonEmptyRange {
pub enum BoundedVariantRange {
Lower(NonZeroUsize),
Upper(NonZeroUsize),
Both {
lower: NonZeroUsize,
// The extent must also not be zero, as this represents an invariant (empty) range.
// The extent must also be non-zero, as that represents an invariant range.
extent: NonZeroUsize,
},
}

impl BoundedNonEmptyRange {
impl BoundedVariantRange {
pub fn try_from_lower_and_upper(
lower: usize,
upper: impl Into<Option<usize>>,
) -> Result<Self, ()> {
use BoundedNonEmptyRange::{Both, Lower, Upper};
use BoundedVariantRange::{Both, Lower, Upper};

// SAFETY: The invariant that the value used to construct a `NonZeroUsize` is not zero is
// explicitly checked here.
Expand All @@ -500,56 +525,22 @@ impl BoundedNonEmptyRange {
}
}

unsafe fn from_non_empty_lower_and_upper(lower: NonZeroBound, upper: NonZeroBound) -> Self {
use Bound::{Bounded, Unbounded};
use BoundedNonEmptyRange::{Lower, Upper};

match (lower, upper) {
(Bounded(lower), Bounded(upper)) => Self::from_bounded_lower_and_upper(lower, upper),
(Bounded(lower), Unbounded) => Lower(lower),
(Unbounded, Bounded(upper)) => Upper(upper),
(Unbounded, Unbounded) => unreachable!(),
}
}

// SAFETY: `upper` must be greater than `lower` so that their difference is non-zero.
unsafe fn from_bounded_lower_and_upper(lower: NonZeroUsize, upper: NonZeroUsize) -> Self {
BoundedNonEmptyRange::Both {
lower,
extent: NonZeroUsize::new_unchecked(upper.get() - lower.get()),
}
}

pub fn union(self, other: Self) -> Self {
let lower = cmp::min(self.lower(), other.lower()).into_bound();
let upper = cmp::max(self.upper(), other.upper()).into_bound();
// SAFETY: The difference between `upper` and `lower` can only become larger here and so
// cannot become zero.
unsafe { Self::from_non_empty_lower_and_upper(lower, upper) }
}

pub fn extension(self, point: NonZeroUsize) -> Self {
use BoundedNonEmptyRange::{Both, Lower, Upper};

let upper = self.upper().into_bound().bounded();
match self {
Both { lower, .. } => {
let lower = cmp::min(lower, point);
let upper = cmp::max(upper.unwrap(), point);
Both {
lower,
// SAFETY: The difference between `upper` and `lower` can only become larger
// here and so cannot become zero.
extent: unsafe { NonZeroUsize::new_unchecked(upper.get() - lower.get()) },
}
pub fn union(self, other: impl Into<NaturalRange>) -> VariantRange {
match NaturalRange::by_lower_and_upper_with(
self.into(),
other.into(),
|LowerUpper { lower, upper }| LowerUpper {
lower: cmp::min(lower.lhs, lower.rhs),
upper: cmp::max(upper.lhs, upper.rhs),
},
Lower(lower) => Lower(cmp::min(lower, point)),
Upper(upper) => Lower(cmp::max(upper, point)),
) {
Variance::Variant(range) => range,
_ => unreachable!(),
}
}

pub fn translation(self, vector: usize) -> Self {
use BoundedNonEmptyRange::{Both, Lower, Upper};
use BoundedVariantRange::{Both, Lower, Upper};

let expect_add = move |m: NonZeroUsize| {
m.checked_add(vector)
Expand All @@ -565,25 +556,8 @@ impl BoundedNonEmptyRange {
}
}

pub fn repeated(self, n: NonZeroUsize) -> Self {
use BoundedNonEmptyRange::{Both, Lower, Upper};

let expect_mul = move |m: NonZeroUsize| {
m.checked_mul(n)
.expect("overflow determining repetition of range")
};
match self {
Both { lower, extent } => Both {
lower: expect_mul(lower),
extent: expect_mul(extent),
},
Lower(lower) => Lower(expect_mul(lower)),
Upper(upper) => Lower(expect_mul(upper)),
}
}

pub fn opened_lower_bound(self) -> NonEmptyRange {
use BoundedNonEmptyRange::{Both, Lower, Upper};
pub fn opened_lower_bound(self) -> VariantRange {
use BoundedVariantRange::{Both, Lower, Upper};

match &self {
Both { .. } => Upper(
Expand All @@ -593,13 +567,13 @@ impl BoundedNonEmptyRange {
.expect("closed range has no upper bound"),
)
.into(),
Lower(_) => NonEmptyRange::Unbounded,
Lower(_) => VariantRange::Unbounded,
_ => self.into(),
}
}

pub fn lower(&self) -> Lower<NonZeroBound> {
use BoundedNonEmptyRange::{Both, Lower, Upper};
use BoundedVariantRange::{Both, Lower, Upper};

match self {
Lower(ref lower) | Both { ref lower, .. } => Bound::Bounded(*lower),
Expand All @@ -609,7 +583,7 @@ impl BoundedNonEmptyRange {
}

pub fn upper(&self) -> Upper<NonZeroBound> {
use BoundedNonEmptyRange::{Both, Lower, Upper};
use BoundedVariantRange::{Both, Lower, Upper};

match self {
Both {
Expand All @@ -629,7 +603,7 @@ impl BoundedNonEmptyRange {
}
}

impl Conjunction for BoundedNonEmptyRange {
impl Conjunction for BoundedVariantRange {
type Output = Self;

fn conjunction(self, rhs: Self) -> Self::Output {
Expand All @@ -640,17 +614,17 @@ impl Conjunction for BoundedNonEmptyRange {
}
}

impl Disjunction for BoundedNonEmptyRange {
type Output = Self;
impl Disjunction for BoundedVariantRange {
type Output = VariantRange;

fn disjunction(self, rhs: Self) -> Self::Output {
self.union(rhs)
}
}

impl OpenedUpperBound for BoundedNonEmptyRange {
impl OpenedUpperBound for BoundedVariantRange {
fn opened_upper_bound(self) -> Bound<Self> {
use BoundedNonEmptyRange::{Both, Lower, Upper};
use BoundedVariantRange::{Both, Lower, Upper};

match &self {
Both { .. } => Lower(
Expand All @@ -660,14 +634,14 @@ impl OpenedUpperBound for BoundedNonEmptyRange {
.expect("closed range has no lower bound"),
)
.into(),
Upper(_) => NonEmptyRange::Unbounded,
Upper(_) => VariantRange::Unbounded,
_ => self.into(),
}
}
}

impl Product for BoundedNonEmptyRange {
type Output = NonEmptyRange;
impl Product for BoundedVariantRange {
type Output = VariantRange;

fn product(self, rhs: Self) -> Self::Output {
match NaturalRange::by_bound_with(self.into(), rhs.into(), ops::product) {
Expand All @@ -677,7 +651,7 @@ impl Product for BoundedNonEmptyRange {
}
}

impl Product<NonZeroUsize> for BoundedNonEmptyRange {
impl Product<NonZeroUsize> for BoundedVariantRange {
type Output = Self;

fn product(self, rhs: NonZeroUsize) -> Self::Output {
Expand Down
Loading

0 comments on commit d5f20cf

Please sign in to comment.