refinement_types/
char.rs

1//! Predicates for characters.
2
3use core::fmt;
4
5use paste::paste;
6use thiserror::Error;
7
8use crate::{core::Predicate, logic::And};
9
10/// Represents base for checks.
11pub type Base = u32;
12
13/// The default base in [`IsDigit`].
14pub const DEFAULT_BASE: Base = 10;
15
16/// Non-digit character encountered in the given base.
17#[derive(Debug, Error)]
18#[error("non-digit character in base `{base}`")]
19pub struct NonDigitError {
20    /// The base in which the non-digit character was encountered.
21    pub base: Base,
22}
23
24impl NonDigitError {
25    /// Constructs [`Self`].
26    #[must_use]
27    pub const fn new(base: Base) -> Self {
28        Self { base }
29    }
30}
31
32/// Checks whether the given character is a digit in the specified base `B`.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
34pub struct IsDigit<const B: Base = DEFAULT_BASE>;
35
36/// The octal base.
37pub const OCT_BASE: Base = 8;
38
39/// The hexadecimal base.
40pub const HEX_BASE: Base = 16;
41
42/// Checks whether the given character is an octal digit.
43pub type IsOctDigit = IsDigit<OCT_BASE>;
44
45/// Checks whether the given character is a hexadecimal digit.
46pub type IsHexDigit = IsDigit<HEX_BASE>;
47
48impl<const B: Base> Predicate<char> for IsDigit<B> {
49    type Error = NonDigitError;
50
51    fn check(value: &char) -> Result<(), Self::Error> {
52        if value.is_digit(B) {
53            Ok(())
54        } else {
55            Err(Self::Error::new(B))
56        }
57    }
58
59    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(formatter, "digit in base {B}")
61    }
62}
63
64macro_rules! predicate {
65    (
66        Name = $name: ident,
67        Check = $check: ident,
68        Doc = $doc: expr,
69        Error = $error: expr,
70        Message = $message: expr,
71        Expected = $expected: expr,
72    ) => {
73        paste! {
74            #[derive(Debug, Error, Default)]
75            #[error($message)]
76            #[doc = $error]
77            pub struct [<Non $name Error>];
78
79            impl [<Non $name Error>] {
80                /// Constructs [`Self`].
81                pub const fn new() -> Self {
82                    Self
83                }
84            }
85
86            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
87            #[doc = $doc]
88            pub struct [< Is $name >];
89
90            impl Predicate<char> for [< Is $name >] {
91                type Error = [<Non $name Error>];
92
93                fn check(value: &char) -> Result<(), Self::Error> {
94                    if value.$check() {
95                        Ok(())
96                    } else {
97                        Err(Self::Error::new())
98                    }
99                }
100
101                fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
102                    write!(formatter, $expected)
103                }
104            }
105        }
106    };
107}
108
109predicate! {
110    Name = Ascii,
111    Check = is_ascii,
112    Doc = "Checks whether the given character is within the ASCII range.",
113    Error = "Non-ASCII character encountered.",
114    Message = "non-ascii character",
115    Expected = "ascii character",
116}
117
118predicate! {
119    Name = Alphabetic,
120    Check = is_alphabetic,
121    Doc = "Checks whether the given character is alphabetic.",
122    Error = "Non-alphabetic character encountered.",
123    Message = "non-alphabetic character",
124    Expected = "alphabetic character",
125}
126
127predicate! {
128    Name = Alphanumeric,
129    Check = is_alphanumeric,
130    Doc = "Checks whether the given character is alphanumeric.",
131    Error = "Non-alphanumeric character encountered.",
132    Message = "non-alphanumeric character",
133    Expected = "alphanumeric character",
134}
135
136predicate! {
137    Name = Control,
138    Check = is_control,
139    Doc = "Checks whether the given character is control.",
140    Error = "Non-control character encountered.",
141    Message = "non-control character",
142    Expected = "control character",
143}
144
145predicate! {
146    Name = Numeric,
147    Check = is_numeric,
148    Doc = "Checks whether the given character is numeric.",
149    Error = "Non-numeric character encountered.",
150    Message = "non-numeric character",
151    Expected = "numeric character",
152}
153
154predicate! {
155    Name = Lowercase,
156    Check = is_lowercase,
157    Doc = "Checks whether the given character is lowercase.",
158    Error = "Non-lowercase character encountered.",
159    Message = "non-lowercase character",
160    Expected = "lowercase character",
161}
162
163predicate! {
164    Name = Uppercase,
165    Check = is_uppercase,
166    Doc = "Checks whether the given character is uppercase.",
167    Error = "Non-uppercase character encountered.",
168    Message = "non-uppercase character",
169    Expected = "uppercase character",
170}
171
172predicate! {
173    Name = Whitespace,
174    Check = is_whitespace,
175    Doc = "Checks whether the given character is whitespace.",
176    Error = "Non-whitespace character encountered.",
177    Message = "non-whitespace character",
178    Expected = "whitespace character",
179}
180
181/// Composition of [`IsAscii`] and [`IsDigit`].
182pub type IsAsciiAlphabetic = And<IsAscii, IsAlphabetic>;
183
184/// Composition of [`IsAscii`] and [`IsAlphanumeric`].
185pub type IsAsciiAlphanumeric = And<IsAscii, IsAlphanumeric>;
186
187/// Composition of [`IsAscii`] and [`IsControl`].
188pub type IsAsciiControl = And<IsAscii, IsControl>;
189
190/// Composition of [`IsAscii`] and [`IsNumeric`].
191pub type IsAsciiNumeric = And<IsAscii, IsNumeric>;
192
193/// Composition of [`IsAscii`] and [`IsLowercase`].
194pub type IsAsciiLowercase = And<IsAscii, IsLowercase>;
195
196/// Composition of [`IsAscii`] and [`IsUppercase`].
197pub type IsAsciiUppercase = And<IsAscii, IsUppercase>;
198
199/// Composition of [`IsAscii`] and [`IsWhitespace`].
200pub type IsAsciiWhitespace = And<IsAscii, IsWhitespace>;