/src/rust-cssparser/src/macros.rs
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | use std::mem::MaybeUninit; |
6 | | |
7 | | /// Expands to a `match` expression with string patterns, |
8 | | /// matching case-insensitively in the ASCII range. |
9 | | /// |
10 | | /// The patterns must not contain ASCII upper case letters. (They must be already be lower-cased.) |
11 | | /// |
12 | | /// # Example |
13 | | /// |
14 | | /// ```rust |
15 | | /// # fn main() {} // Make doctest not wrap everything in its own main |
16 | | /// # fn dummy(function_name: &String) { let _ = |
17 | | /// cssparser::match_ignore_ascii_case! { &function_name, |
18 | | /// "rgb" => parse_rgb(..), |
19 | | /// # #[cfg(not(something))] |
20 | | /// "rgba" => parse_rgba(..), |
21 | | /// "hsl" => parse_hsl(..), |
22 | | /// "hsla" => parse_hsla(..), |
23 | | /// _ => Err(format!("unknown function: {}", function_name)) |
24 | | /// } |
25 | | /// # ;} |
26 | | /// # use std::ops::RangeFull; |
27 | | /// # fn parse_rgb(_: RangeFull) -> Result<(), String> { Ok(()) } |
28 | | /// # fn parse_rgba(_: RangeFull) -> Result<(), String> { Ok(()) } |
29 | | /// # fn parse_hsl(_: RangeFull) -> Result<(), String> { Ok(()) } |
30 | | /// # fn parse_hsla(_: RangeFull) -> Result<(), String> { Ok(()) } |
31 | | /// ``` |
32 | | #[macro_export] |
33 | | macro_rules! match_ignore_ascii_case { |
34 | | ( $input:expr, |
35 | | $( |
36 | | $( #[$meta: meta] )* |
37 | | $( $pattern:literal )|+ $( if $guard: expr )? => $then: expr |
38 | | ),+ |
39 | | $(,_ => $fallback:expr)? |
40 | | $(,)? |
41 | | ) => { |
42 | | { |
43 | | #[inline(always)] |
44 | | const fn const_usize_max(a: usize, b: usize) -> usize { |
45 | | if a > b { |
46 | | a |
47 | | } else { |
48 | | b |
49 | | } |
50 | | } |
51 | | |
52 | | const MAX_LENGTH : usize = { |
53 | | let mut maxlen : usize = 0; |
54 | | $( |
55 | | $( #[$meta] )* |
56 | | // {} is necessary to work around "[E0658]: attributes on expressions are experimental" |
57 | | { |
58 | | $( maxlen = const_usize_max(maxlen, $pattern.len()); )+ |
59 | | } |
60 | | )+ |
61 | | maxlen |
62 | | }; |
63 | | |
64 | | $crate::_cssparser_internal_to_lowercase!($input, MAX_LENGTH => lowercase); |
65 | | // "A" is a short string that we know is different for every string pattern, |
66 | | // since we’ve verified that none of them include ASCII upper case letters. |
67 | | match lowercase.unwrap_or("A") { |
68 | | $( |
69 | | $( #[$meta] )* |
70 | | $( $pattern )|+ $( if $guard )? => $then, |
71 | | )+ |
72 | | $(_ => $fallback,)? |
73 | | } |
74 | | } |
75 | | }; |
76 | | } |
77 | | |
78 | | #[cfg(not(feature = "fast_match_color"))] |
79 | | #[macro_export] |
80 | | /// Define a function `$name(&str) -> Option<&'static $ValueType>` |
81 | | /// |
82 | | /// The function finds a match for the input string |
83 | | /// in a [`phf` map](https://github.com/sfackler/rust-phf) |
84 | | /// and returns a reference to the corresponding value. |
85 | | /// Matching is case-insensitive in the ASCII range. |
86 | | /// |
87 | | /// ## Example: |
88 | | /// |
89 | | /// ```rust |
90 | | /// # fn main() {} // Make doctest not wrap everything in its own main |
91 | | /// |
92 | | /// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> { |
93 | | /// cssparser::ascii_case_insensitive_map! { |
94 | | /// keywords -> (u8, u8, u8) = { |
95 | | /// "red" => (255, 0, 0), |
96 | | /// "green" => (0, 255, 0), |
97 | | /// "blue" => (0, 0, 255), |
98 | | /// } |
99 | | /// } |
100 | | /// keywords::get(input).cloned() |
101 | | /// } |
102 | | /// ``` |
103 | | /// |
104 | | /// You can also iterate over the map entries by using `keywords::entries()`. |
105 | | macro_rules! ascii_case_insensitive_map { |
106 | | ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => { |
107 | | ascii_case_insensitive_map!($name -> $ValueType = { $( $key => $value, )+ }) |
108 | | }; |
109 | | ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => { |
110 | | |
111 | | // While the obvious choice for this would be an inner module, it's not possible to |
112 | | // reference from types from there, see: |
113 | | // <https://github.com/rust-lang/rust/issues/114369> |
114 | | // |
115 | | // So we abuse a struct with static associated functions instead. |
116 | | #[allow(non_camel_case_types)] |
117 | | struct $name; |
118 | | impl $name { |
119 | | #[allow(dead_code)] |
120 | | fn entries() -> impl Iterator<Item = (&'static &'static str, &'static $ValueType)> { |
121 | | [ $((&$key, &$value),)* ].iter().copied() |
122 | | } |
123 | | |
124 | | fn get(input: &str) -> Option<&'static $ValueType> { |
125 | | $crate::match_ignore_ascii_case!(input, |
126 | | $($key => Some(&$value),)* |
127 | | _ => None, |
128 | | ) |
129 | | } |
130 | | } |
131 | | } |
132 | | } |
133 | | |
134 | | #[cfg(feature = "fast_match_color")] |
135 | | #[macro_export] |
136 | | /// Define a function `$name(&str) -> Option<&'static $ValueType>` |
137 | | /// |
138 | | /// The function finds a match for the input string |
139 | | /// in a [`phf` map](https://github.com/sfackler/rust-phf) |
140 | | /// and returns a reference to the corresponding value. |
141 | | /// Matching is case-insensitive in the ASCII range. |
142 | | /// |
143 | | /// ## Example: |
144 | | /// |
145 | | /// ```rust |
146 | | /// # fn main() {} // Make doctest not wrap everything in its own main |
147 | | /// |
148 | | /// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> { |
149 | | /// cssparser::ascii_case_insensitive_map! { |
150 | | /// keywords -> (u8, u8, u8) = { |
151 | | /// "red" => (255, 0, 0), |
152 | | /// "green" => (0, 255, 0), |
153 | | /// "blue" => (0, 0, 255), |
154 | | /// } |
155 | | /// } |
156 | | /// keywords::get(input).cloned() |
157 | | /// } |
158 | | /// ``` |
159 | | /// |
160 | | /// You can also iterate over the map entries by using `keywords::entries()`. |
161 | | macro_rules! ascii_case_insensitive_map { |
162 | | ($($any:tt)+) => { |
163 | | $crate::ascii_case_insensitive_phf_map!($($any)+); |
164 | | }; |
165 | | } |
166 | | |
167 | | /// Fast implementation of `ascii_case_insensitive_map!` using a phf map. |
168 | | /// See `ascii_case_insensitive_map!` above for docs |
169 | | #[cfg(feature = "fast_match_color")] |
170 | | #[macro_export] |
171 | | macro_rules! ascii_case_insensitive_phf_map { |
172 | | ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => { |
173 | | ascii_case_insensitive_phf_map!($name -> $ValueType = { $( $key => $value, )+ }) |
174 | | }; |
175 | | ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => { |
176 | | use $crate::_cssparser_internal_phf as phf; |
177 | | |
178 | | #[inline(always)] |
179 | | const fn const_usize_max(a: usize, b: usize) -> usize { |
180 | | if a > b { |
181 | | a |
182 | | } else { |
183 | | b |
184 | | } |
185 | | } |
186 | | |
187 | | const MAX_LENGTH : usize = { |
188 | | let mut maxlen : usize = 0; |
189 | | $( maxlen = const_usize_max(maxlen, ($key).len()); )+ |
190 | | maxlen |
191 | | }; |
192 | | |
193 | | static __MAP: phf::Map<&'static str, $ValueType> = phf::phf_map! { |
194 | | $( |
195 | | $key => $value, |
196 | | )* |
197 | | }; |
198 | | |
199 | | // While the obvious choice for this would be an inner module, it's not possible to |
200 | | // reference from types from there, see: |
201 | | // <https://github.com/rust-lang/rust/issues/114369> |
202 | | // |
203 | | // So we abuse a struct with static associated functions instead. |
204 | | #[allow(non_camel_case_types)] |
205 | | struct $name; |
206 | | impl $name { |
207 | | #[allow(dead_code)] |
208 | | fn entries() -> impl Iterator<Item = (&'static &'static str, &'static $ValueType)> { |
209 | | __MAP.entries() |
210 | | } |
211 | | |
212 | | fn get(input: &str) -> Option<&'static $ValueType> { |
213 | | $crate::_cssparser_internal_to_lowercase!(input, MAX_LENGTH => lowercase); |
214 | | __MAP.get(lowercase?) |
215 | | } |
216 | | } |
217 | | } |
218 | | } |
219 | | |
220 | | /// Create a new array of MaybeUninit<T> items, in an uninitialized state. |
221 | | #[inline(always)] |
222 | | pub fn _cssparser_internal_create_uninit_array<const N: usize>() -> [MaybeUninit<u8>; N] { |
223 | | unsafe { |
224 | | // SAFETY: An uninitialized `[MaybeUninit<_>; LEN]` is valid. |
225 | | // See: https://doc.rust-lang.org/stable/core/mem/union.MaybeUninit.html#method.uninit_array |
226 | | MaybeUninit::<[MaybeUninit<u8>; N]>::uninit().assume_init() |
227 | | } |
228 | | } |
229 | | |
230 | | /// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. |
231 | | /// |
232 | | /// **This macro is not part of the public API. It can change or be removed between any versions.** |
233 | | /// |
234 | | /// Define a local variable named `$output` |
235 | | /// and assign it the result of calling `_cssparser_internal_to_lowercase` |
236 | | /// with a stack-allocated buffer of length `$BUFFER_SIZE`. |
237 | | #[macro_export] |
238 | | #[doc(hidden)] |
239 | | macro_rules! _cssparser_internal_to_lowercase { |
240 | | ($input: expr, $BUFFER_SIZE: expr => $output: ident) => { |
241 | | let mut buffer = $crate::_cssparser_internal_create_uninit_array::<{ $BUFFER_SIZE }>(); |
242 | | let input: &str = $input; |
243 | | let $output = $crate::_cssparser_internal_to_lowercase(&mut buffer, input); |
244 | | }; |
245 | | } |
246 | | |
247 | | /// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. |
248 | | /// |
249 | | /// **This function is not part of the public API. It can change or be removed between any versions.** |
250 | | /// |
251 | | /// If `input` is larger than buffer, return `None`. |
252 | | /// Otherwise, return `input` ASCII-lowercased, using `buffer` as temporary space if necessary. |
253 | | #[doc(hidden)] |
254 | | #[allow(non_snake_case)] |
255 | | #[inline] |
256 | | pub fn _cssparser_internal_to_lowercase<'a>( |
257 | | buffer: &'a mut [MaybeUninit<u8>], |
258 | | input: &'a str, |
259 | | ) -> Option<&'a str> { |
260 | | let buffer = buffer.get_mut(..input.len())?; |
261 | | |
262 | | #[cold] |
263 | | fn make_ascii_lowercase<'a>( |
264 | | buffer: &'a mut [MaybeUninit<u8>], |
265 | | input: &'a str, |
266 | | first_uppercase: usize, |
267 | | ) -> &'a str { |
268 | | // This cast doesn't change the pointer's validity |
269 | | // since `u8` has the same layout as `MaybeUninit<u8>`: |
270 | | let input_bytes = |
271 | | unsafe { &*(input.as_bytes() as *const [u8] as *const [MaybeUninit<u8>]) }; |
272 | | |
273 | | buffer.copy_from_slice(input_bytes); |
274 | | |
275 | | // Same as above re layout, plus these bytes have been initialized: |
276 | | let buffer = unsafe { &mut *(buffer as *mut [MaybeUninit<u8>] as *mut [u8]) }; |
277 | | |
278 | | buffer[first_uppercase..].make_ascii_lowercase(); |
279 | | // `buffer` was initialized to a copy of `input` |
280 | | // (which is `&str` so well-formed UTF-8) |
281 | | // then ASCII-lowercased (which preserves UTF-8 well-formedness): |
282 | | unsafe { ::std::str::from_utf8_unchecked(buffer) } |
283 | | } |
284 | | |
285 | | Some( |
286 | 0 | match input.bytes().position(|byte| byte.is_ascii_uppercase()) { |
287 | | Some(first_uppercase) => make_ascii_lowercase(buffer, input, first_uppercase), |
288 | | // common case: input is already lower-case |
289 | | None => input, |
290 | | }, |
291 | | ) |
292 | | } |