percent_encoding/
ascii_set.rs1use core::{mem, ops};
10
11#[derive(Debug, PartialEq, Eq)]
28pub struct AsciiSet {
29 mask: [Chunk; ASCII_RANGE_LEN / BITS_PER_CHUNK],
30}
31
32type Chunk = u32;
33
34const ASCII_RANGE_LEN: usize = 0x80;
35
36const BITS_PER_CHUNK: usize = 8 * mem::size_of::<Chunk>();
37
38impl AsciiSet {
39 pub const EMPTY: Self = Self {
41 mask: [0; ASCII_RANGE_LEN / BITS_PER_CHUNK],
42 };
43
44 pub(crate) const fn contains(&self, byte: u8) -> bool {
47 let chunk = self.mask[byte as usize / BITS_PER_CHUNK];
48 let mask = 1 << (byte as usize % BITS_PER_CHUNK);
49 (chunk & mask) != 0
50 }
51
52 pub(crate) fn should_percent_encode(&self, byte: u8) -> bool {
53 !byte.is_ascii() || self.contains(byte)
54 }
55
56 pub const fn add(&self, byte: u8) -> Self {
57 let mut mask = self.mask;
58 mask[byte as usize / BITS_PER_CHUNK] |= 1 << (byte as usize % BITS_PER_CHUNK);
59 Self { mask }
60 }
61
62 pub const fn remove(&self, byte: u8) -> Self {
63 let mut mask = self.mask;
64 mask[byte as usize / BITS_PER_CHUNK] &= !(1 << (byte as usize % BITS_PER_CHUNK));
65 Self { mask }
66 }
67
68 pub const fn union(&self, other: Self) -> Self {
70 let mask = [
71 self.mask[0] | other.mask[0],
72 self.mask[1] | other.mask[1],
73 self.mask[2] | other.mask[2],
74 self.mask[3] | other.mask[3],
75 ];
76 Self { mask }
77 }
78
79 pub const fn complement(&self) -> Self {
81 let mask = [!self.mask[0], !self.mask[1], !self.mask[2], !self.mask[3]];
82 Self { mask }
83 }
84}
85
86impl ops::Add for AsciiSet {
87 type Output = Self;
88
89 fn add(self, other: Self) -> Self {
90 self.union(other)
91 }
92}
93
94impl ops::Not for AsciiSet {
95 type Output = Self;
96
97 fn not(self) -> Self {
98 self.complement()
99 }
100}
101
102pub const CONTROLS: &AsciiSet = &AsciiSet {
108 mask: [
109 !0_u32, 0,
111 0,
112 1 << (0x7F_u32 % 32), ],
114};
115
116macro_rules! static_assert {
117 ($( $bool: expr, )+) => {
118 fn _static_assert() {
119 $(
120 let _ = mem::transmute::<[u8; $bool as usize], u8>;
121 )+
122 }
123 }
124}
125
126static_assert! {
127 CONTROLS.contains(0x00),
128 CONTROLS.contains(0x1F),
129 !CONTROLS.contains(0x20),
130 !CONTROLS.contains(0x7E),
131 CONTROLS.contains(0x7F),
132}
133
134pub const NON_ALPHANUMERIC: &AsciiSet = &CONTROLS
138 .add(b' ')
139 .add(b'!')
140 .add(b'"')
141 .add(b'#')
142 .add(b'$')
143 .add(b'%')
144 .add(b'&')
145 .add(b'\'')
146 .add(b'(')
147 .add(b')')
148 .add(b'*')
149 .add(b'+')
150 .add(b',')
151 .add(b'-')
152 .add(b'.')
153 .add(b'/')
154 .add(b':')
155 .add(b';')
156 .add(b'<')
157 .add(b'=')
158 .add(b'>')
159 .add(b'?')
160 .add(b'@')
161 .add(b'[')
162 .add(b'\\')
163 .add(b']')
164 .add(b'^')
165 .add(b'_')
166 .add(b'`')
167 .add(b'{')
168 .add(b'|')
169 .add(b'}')
170 .add(b'~');
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn add_op() {
178 let left = AsciiSet::EMPTY.add(b'A');
179 let right = AsciiSet::EMPTY.add(b'B');
180 let expected = AsciiSet::EMPTY.add(b'A').add(b'B');
181 assert_eq!(left + right, expected);
182 }
183
184 #[test]
185 fn not_op() {
186 let set = AsciiSet::EMPTY.add(b'A').add(b'B');
187 let not_set = !set;
188 assert!(!not_set.contains(b'A'));
189 assert!(not_set.contains(b'C'));
190 }
191
192 #[test]
195 fn union() {
196 const A: AsciiSet = AsciiSet::EMPTY.add(b'A');
197 const B: AsciiSet = AsciiSet::EMPTY.add(b'B');
198 const UNION: AsciiSet = A.union(B);
199 const EXPECTED: AsciiSet = AsciiSet::EMPTY.add(b'A').add(b'B');
200 assert_eq!(UNION, EXPECTED);
201 }
202
203 #[test]
206 fn complement() {
207 const BOTH: AsciiSet = AsciiSet::EMPTY.add(b'A').add(b'B');
208 const COMPLEMENT: AsciiSet = BOTH.complement();
209 assert!(!COMPLEMENT.contains(b'A'));
210 assert!(!COMPLEMENT.contains(b'B'));
211 assert!(COMPLEMENT.contains(b'C'));
212 }
213}