#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#![doc(html_logo_url = "https://narnium.com/tyrx_icons/tyrx_icon-512.png")]
use std::iter::FusedIterator;
use std::hash::{DefaultHasher, BuildHasherDefault};
use std::fmt::{self, Formatter};
use std::collections::HashMap;
use std::sync::Mutex;
use std::any::{type_name, TypeId};
use thiserror::Error;
use crate::util::PatternDisplay;
pub use regex::{Regex, Match, Captures, CaptureMatches};
pub use crate::util::{Spanned, MatchFromStr, ErasedLifetime};
pub use crate::error::{Error, Result};
#[cfg(feature = "derive")]
pub use tyrx_macros::*;
pub mod builder;
pub mod util;
#[doc(hidden)]
pub mod impls;
pub mod error;
pub trait TyRx<'h>: Sized + RegexPattern + FromMatch<'h> + ErasedLifetime {
fn from_captures(captures: &Captures<'h>) -> Result<Self, Error> {
Self::from_match("$root", captures.get_match(), captures)
}
fn from_str(haystack: &'h str) -> Result<Self, Error> {
let regex = build_regex::<Self>();
let captures = regex.captures(haystack).ok_or(Error::NoMatch)?;
Self::from_captures(&captures)
}
fn iter_from_str(haystack: &'h str) -> IterFromStr<'h, Self> {
let regex = build_regex::<Self>();
let iter = regex.captures_iter(haystack);
let phantom = std::marker::PhantomData;
IterFromStr { iter, phantom }
}
}
impl<'h, T> TyRx<'h> for T
where
T: RegexPattern + FromMatch<'h> + ErasedLifetime
{
}
pub trait RegexPattern {
fn fmt_pattern(f: &mut Formatter<'_>) -> fmt::Result;
fn pattern_display() -> PatternDisplay<Self> {
PatternDisplay::default()
}
}
pub trait FromMatch<'h>: Sized {
fn from_match(name: &'static str, m: Match<'h>, captures: &Captures<'h>) -> Result<Self, Error>;
}
static RX_CACHE: Mutex<HashMap<TypeId, &'static Regex, BuildHasherDefault<DefaultHasher>>> = Mutex::new(
HashMap::with_hasher(BuildHasherDefault::new())
);
pub fn build_regex<T>() -> &'static Regex
where
T: RegexPattern + ErasedLifetime
{
RX_CACHE
.lock()
.expect("TyRx regex cache mutex poisoned")
.entry(TypeId::of::<T::Erased>())
.or_insert_with(|| {
let pattern = T::pattern_display().to_string();
let rx = match Regex::new(&pattern) {
Ok(rx) => rx,
Err(err) => panic!("syntax error in regex pattern for `{ty}`: {err}", ty = type_name::<T>()),
};
Box::leak(Box::new(rx))
})
}
#[derive(Debug)]
pub struct IterFromStr<'h, T> {
iter: CaptureMatches<'static, 'h>,
phantom: std::marker::PhantomData<fn() -> T>,
}
impl<'h, T> Iterator for IterFromStr<'h, T>
where
T: TyRx<'h>,
{
type Item = Result<T, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().as_ref().map(T::from_captures)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
fn count(self) -> usize {
self.iter.count()
}
}
impl<'h, T: TyRx<'h>> FusedIterator for IterFromStr<'h, T> {}
pub trait CapturesExt<'h> {
fn group<T: FromMatch<'h>>(&self, group: &'static str) -> Result<T, Error>;
}
impl<'h> CapturesExt<'h> for Captures<'h> {
fn group<T: FromMatch<'h>>(&self, name: &'static str) -> Result<T, Error> {
let m = self.name(name).ok_or(Error::NoGroup(name))?;
T::from_match(name, m, self)
}
}