cssparser/macros.rs
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
5use 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]
33macro_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()`.
105macro_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()`.
161macro_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]
171macro_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)]
222pub 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)]
239macro_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]
256pub 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 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}