tyrx 0.1.2

Typed, ergonomic regular expression library
Documentation
//! Helper types and traits, etc.

use std::hash::{Hash, Hasher};
use std::cmp::Ordering;
use std::str::FromStr;
use std::marker::PhantomData;
use std::error::Error as StdError;
use std::ops::{Range, Deref, DerefMut};
use std::borrow::{Borrow, BorrowMut};
use std::fmt::{self, Display, Debug, Formatter};

use regex::{Match, Captures};

#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};

use crate::{FromMatch, RegexPattern, Error};


/// Captures the location of a match while parsing the underlying value from the matched substring.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Spanned<T: ?Sized> {
    /// this is deliberately NOT a `Range<usize>` so we can be `Copy`
    span: (usize, usize),
    value: T,
}

impl<T> Spanned<T> {
    pub const fn new(value: T, span: Range<usize>) -> Self {
        let span = (span.start, span.end);
        Spanned { value, span }
    }

    pub fn into_value(self) -> T {
        self.value
    }
}

impl<T: ?Sized> Spanned<T> {
    pub fn value(&self) -> &T {
        &self.value
    }

    pub fn value_mut(&mut self) -> &mut T {
        &mut self.value
    }

    pub fn span(&self) -> Range<usize> {
        let (start, end) = self.span;
        start..end
    }
}

impl<T: ?Sized + RegexPattern> RegexPattern for Spanned<T> {
    fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
        <T as RegexPattern>::fmt_pattern(f)
    }
}

impl<'h, T> FromMatch<'h> for Spanned<T>
where
    T: FromMatch<'h>,
{
    fn from_match(name: &'static str, m: Match<'h>, captures: &Captures<'h>) -> Result<Self, Error> {
        T::from_match(name, m, captures).map(|value| Spanned::new(value, m.range()))
    }
}

impl<T: ?Sized + ErasedLifetime> ErasedLifetime for Spanned<T> {
    type Erased = Spanned<T::Erased>;
}

impl<T: ?Sized + PartialEq> PartialEq<T> for Spanned<T> {
    fn eq(&self, other: &T) -> bool {
        self.value.eq(other)
    }
}

impl<T: ?Sized + PartialEq> PartialEq<&T> for Spanned<T> {
    fn eq(&self, other: &&T) -> bool {
        self.value.eq(*other)
    }
}

impl<T: ?Sized + PartialEq> PartialEq<&mut T> for Spanned<T> {
    fn eq(&self, other: &&mut T) -> bool {
        self.value.eq(&**other)
    }
}

impl<T: ?Sized + PartialOrd> PartialOrd<T> for Spanned<T> {
    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
        self.value.partial_cmp(other)
    }
}

impl<T: ?Sized + PartialOrd> PartialOrd<&T> for Spanned<T> {
    fn partial_cmp(&self, other: &&T) -> Option<Ordering> {
        self.value.partial_cmp(*other)
    }
}

impl<T: ?Sized + PartialOrd> PartialOrd<&mut T> for Spanned<T> {
    fn partial_cmp(&self, other: &&mut T) -> Option<Ordering> {
        self.value.partial_cmp(&**other)
    }
}

impl<T: ?Sized + Hash> Hash for Spanned<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.value.hash(state);
    }
}

impl<T: ?Sized + Display> Display for Spanned<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.value, f)
    }
}

impl<T: ?Sized> Deref for Spanned<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl<T: ?Sized> DerefMut for Spanned<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.value
    }
}

impl<T: ?Sized> Borrow<T> for Spanned<T> {
    fn borrow(&self) -> &T {
        &self.value
    }
}

impl<T: ?Sized> BorrowMut<T> for Spanned<T> {
    fn borrow_mut(&mut self) -> &mut T {
        &mut self.value
    }
}

impl<T: ?Sized> AsRef<T> for Spanned<T> {
    fn as_ref(&self) -> &T {
        &self.value
    }
}

impl<T: ?Sized> AsMut<T> for Spanned<T> {
    fn as_mut(&mut self) -> &mut T {
        &mut self.value
    }
}

/// Adapter for `FromStr`-implementing types, necessitated by trait coherence rules.
#[derive(Clone, Copy, Eq, Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct MatchFromStr<T: ?Sized>(pub T);

impl<T: ?Sized + RegexPattern> RegexPattern for MatchFromStr<T> {
    fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result {
        <T as RegexPattern>::fmt_pattern(f)
    }
}

impl<'h, T> FromMatch<'h> for MatchFromStr<T>
where
    T: FromStr,
    T::Err: StdError + Send + Sync + 'static,
{
    fn from_match(name: &'static str, m: Match<'h>, _captures: &Captures<'h>) -> Result<Self, Error> {
        let value = T::from_str(m.as_str()).map_err(|error| {
            Error::group_from_str(name, m.range(), error)
        })?;
        Ok(MatchFromStr(value))
    }
}

impl<T: ?Sized + ErasedLifetime> ErasedLifetime for MatchFromStr<T> {
    type Erased = MatchFromStr<T::Erased>;
}

impl<T: ?Sized + PartialEq> PartialEq for MatchFromStr<T> {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl<T: ?Sized + PartialEq> PartialEq<T> for MatchFromStr<T> {
    fn eq(&self, other: &T) -> bool {
        self.0.eq(other)
    }
}

impl<T: ?Sized + PartialEq> PartialEq<&T> for MatchFromStr<T> {
    fn eq(&self, other: &&T) -> bool {
        self.0.eq(*other)
    }
}

impl<T: ?Sized + PartialEq> PartialEq<&mut T> for MatchFromStr<T> {
    fn eq(&self, other: &&mut T) -> bool {
        self.0.eq(&**other)
    }
}

impl<T: ?Sized + PartialOrd> PartialOrd for MatchFromStr<T> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl<T: ?Sized + PartialOrd> PartialOrd<T> for MatchFromStr<T> {
    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}

impl<T: ?Sized + PartialOrd> PartialOrd<&T> for MatchFromStr<T> {
    fn partial_cmp(&self, other: &&T) -> Option<Ordering> {
        self.0.partial_cmp(*other)
    }
}

impl<T: ?Sized + PartialOrd> PartialOrd<&mut T> for MatchFromStr<T> {
    fn partial_cmp(&self, other: &&mut T) -> Option<Ordering> {
        self.0.partial_cmp(&**other)
    }
}

impl<T: ?Sized + Ord> Ord for MatchFromStr<T> {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.cmp(&other.0)
    }
}

impl<T: ?Sized + Hash> Hash for MatchFromStr<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl<T> From<T> for MatchFromStr<T> {
    fn from(value: T) -> Self {
        MatchFromStr(value)
    }
}

impl<T: ?Sized + Display> Display for MatchFromStr<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

impl<T: ?Sized> Deref for MatchFromStr<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: ?Sized> DerefMut for MatchFromStr<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl<T: ?Sized> Borrow<T> for MatchFromStr<T> {
    fn borrow(&self) -> &T {
        &self.0
    }
}

impl<T: ?Sized> BorrowMut<T> for MatchFromStr<T> {
    fn borrow_mut(&mut self) -> &mut T {
        &mut self.0
    }
}

impl<T: ?Sized> AsRef<T> for MatchFromStr<T> {
    fn as_ref(&self) -> &T {
        &self.0
    }
}

impl<T: ?Sized> AsMut<T> for MatchFromStr<T> {
    fn as_mut(&mut self) -> &mut T {
        &mut self.0
    }
}

/// Internal helper for writing a pattern into an `std::fmt::Formatter`.
pub struct PatternDisplay<T: ?Sized>(PhantomData<fn(&T)>);

impl<T: ?Sized> Clone for PatternDisplay<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T: ?Sized> Copy for PatternDisplay<T> {}

impl<T: ?Sized> Default for PatternDisplay<T> {
    fn default() -> Self {
        PatternDisplay(Default::default())
    }
}

impl<T: ?Sized + RegexPattern> Debug for PatternDisplay<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "PatternDisplay<{ty}>(\"{self}\")",
            ty = std::any::type_name::<T>(),
        )
    }
}

impl<T: ?Sized + RegexPattern> Display for PatternDisplay<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        T::fmt_pattern(f)
    }
}

/// A trait for replacing all lifetime parameters of a type with `'static`,
/// so it can be used for obtaining a `TypeId` regardless of lifetimes.
///
/// This is used by the [`crate::build_regex()`] function for caching
/// regular expressions for each type.
///
/// This trait can be `#[derive]`d on struct and enum types.
pub trait ErasedLifetime {
    type Erased: ?Sized + 'static;
}

#[cfg(test)]
mod tests {
    #[test]
    fn spanned_eq_hash_borrow_consistency() {
        use std::hash::BuildHasher as _;
        use std::collections::HashMap;
        use super::Spanned;

        let sp_a = Spanned::new(String::from("hello world"), 1..12);
        let sp_b = Spanned::new(String::from("hello world"), 6..17);
        let sp_x = Spanned::new(String::from("nope, nope!"), 1..12);

        assert_eq!(sp_a.value(), sp_b.value());
        assert_eq!(sp_a, sp_a.value());
        assert_eq!(sp_b, sp_b.value());

        assert_eq!(sp_a, sp_b.value());
        assert_eq!(sp_b, sp_a.value());

        assert_ne!(sp_a.value(), sp_x.value());
        assert_ne!(sp_b.value(), sp_x.value());

        assert_ne!(sp_a, sp_b);
        assert_ne!(sp_a, sp_x);
        assert_ne!(sp_b, sp_x);

        let hasher = std::hash::RandomState::new();

        let hs_a = hasher.hash_one(&sp_a);
        let hs_a_val = hasher.hash_one(sp_a.value());

        let hs_b = hasher.hash_one(&sp_b);
        let hs_b_val = hasher.hash_one(sp_b.value());

        assert_eq!(hs_a, hs_a_val);
        assert_eq!(hs_b, hs_b_val);

        // hash values are consistent even though the instances are
        // not equal as a whole due to differing ranges -- but this
        // is fine actually, as hashing based on a subset of fields
        // is OK (it just degrades the quality of the hash somewhat)
        // and it allows hashes to be consistent with `PartialEq<T>`
        assert_eq!(hs_a, hs_b);
        assert_eq!(hs_a, hs_b_val);
        assert_eq!(hs_b, hs_a_val);

        let map = HashMap::from([
            (sp_a, 0),
            (sp_b, 0),
            (sp_x, 1),
        ]);
        assert_eq!(map.len(), 3);

        assert_eq!(map.get(&"hello world".to_owned()), Some(&0));
        assert_eq!(map.get(&"nope, nope!".to_owned()), Some(&1));
        assert_eq!(map.get(&"gibberish".to_owned()), None);
    }
}