refining_regex/types.rs
1//! Type-level regular expressions.
2
3pub use regex::Regex;
4
5#[doc(hidden)]
6pub mod import {
7 pub use core::marker::PhantomData;
8
9 pub use std::sync::LazyLock;
10}
11
12/// Represents static regular expressions (as returned in [`get`] of [`TypeRegex`]).
13///
14/// [`get`]: TypeRegex::get
15pub type StaticRegex = &'static Regex;
16
17/// Represents type-level regular expressions.
18pub trait TypeRegex {
19 /// Returns the compiled regular expression.
20 #[must_use]
21 fn get() -> StaticRegex;
22}
23
24/// The `invalid regex` literal.
25pub const INVALID: &str = "invalid regex";
26
27/// Lifts strings to type-level regular expressions.
28///
29/// ```
30/// use refining_regex::type_regex;
31///
32/// type_regex!(pub Natural = "^0|[1-9][0-9]*$" => "Matches non-negative integers.");
33/// ```
34///
35/// Is essentially equivalent to:
36///
37/// ```
38/// use std::{marker::PhantomData, sync::LazyLock};
39///
40/// use refining_regex::types::{Regex, StaticRegex, TypeRegex, INVALID};
41///
42/// /// Matches non-negative integers.
43/// pub struct Natural {
44/// private: PhantomData<()>,
45/// }
46///
47/// impl TypeRegex for Natural {
48/// fn get() -> StaticRegex {
49/// static REGEX: LazyLock<Regex> = LazyLock::new(|| {
50/// Regex::new("^0|[1-9][0-9]*$").expect(INVALID)
51/// });
52///
53/// LazyLock::force(®EX)
54/// }
55/// }
56/// ```
57#[macro_export]
58macro_rules! type_regex {
59 ($vis: vis $name: ident = $regex: expr $(=> $doc: expr)?) => {
60 $(
61 #[doc = $doc]
62 )?
63 $vis struct $name {
64 private: $crate::types::import::PhantomData<()>,
65 }
66
67 impl $crate::types::TypeRegex for $name {
68 fn get() -> $crate::types::StaticRegex {
69 use $crate::types::import::LazyLock;
70
71 static REGEX: LazyLock<$crate::types::Regex> = LazyLock::new(|| {
72 $crate::types::Regex::new($regex).expect($crate::types::INVALID)
73 });
74
75 LazyLock::force(®EX)
76 }
77 }
78 };
79}