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: pat )|+ $( if $guard: expr )? => $then: expr
38        ),+
39        $(,)?
40    ) => {
41        {
42            // This dummy module works around the feature gate
43            // `error[E0658]: procedural macros cannot be expanded to statements`
44            // by forcing the macro to be in an item context
45            // rather than expression/statement context,
46            // even though the macro only expands to items.
47            mod cssparser_internal {
48                $crate::_cssparser_internal_max_len! {
49                    $( $( $pattern )+ )+
50                }
51            }
52            $crate::_cssparser_internal_to_lowercase!($input, cssparser_internal::MAX_LENGTH => lowercase);
53            // "A" is a short string that we know is different for every string pattern,
54            // since we’ve verified that none of them include ASCII upper case letters.
55            match lowercase.unwrap_or("A") {
56                $(
57                    $( #[$meta] )*
58                    $( $pattern )|+ $( if $guard )? => $then,
59                )+
60            }
61        }
62    };
63}
64
65/// Define a function `$name(&str) -> Option<&'static $ValueType>`
66///
67/// The function finds a match for the input string
68/// in a [`phf` map](https://github.com/sfackler/rust-phf)
69/// and returns a reference to the corresponding value.
70/// Matching is case-insensitive in the ASCII range.
71///
72/// ## Example:
73///
74/// ```rust
75/// # fn main() {}  // Make doctest not wrap everything in its own main
76///
77/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> {
78///     cssparser::ascii_case_insensitive_phf_map! {
79///         keywords -> (u8, u8, u8) = {
80///             "red" => (255, 0, 0),
81///             "green" => (0, 255, 0),
82///             "blue" => (0, 0, 255),
83///         }
84///     }
85///     keywords::get(input).cloned()
86/// }
87/// ```
88///
89/// You can also iterate over the map entries by using `keywords::entries()`.
90#[macro_export]
91macro_rules! ascii_case_insensitive_phf_map {
92    ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => {
93        ascii_case_insensitive_phf_map!($name -> $ValueType = { $( $key => $value, )+ })
94    };
95    ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => {
96        use $crate::_cssparser_internal_phf as phf;
97
98        // See macro above for context.
99        mod cssparser_internal {
100            $crate::_cssparser_internal_max_len! {
101                $( $key )+
102            }
103        }
104
105        static MAP: phf::Map<&'static str, $ValueType> = phf::phf_map! {
106            $(
107                $key => $value,
108            )*
109        };
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                MAP.entries()
122            }
123
124            fn get(input: &str) -> Option<&'static $ValueType> {
125                $crate::_cssparser_internal_to_lowercase!(input, cssparser_internal::MAX_LENGTH => lowercase);
126                MAP.get(lowercase?)
127            }
128        }
129    }
130}
131
132/// Create a new array of MaybeUninit<T> items, in an uninitialized state.
133#[inline(always)]
134pub fn _cssparser_internal_create_uninit_array<const N: usize>() -> [MaybeUninit<u8>; N] {
135    unsafe {
136        // SAFETY: An uninitialized `[MaybeUninit<_>; LEN]` is valid.
137        // See: https://doc.rust-lang.org/stable/core/mem/union.MaybeUninit.html#method.uninit_array
138        MaybeUninit::<[MaybeUninit<u8>; N]>::uninit().assume_init()
139    }
140}
141
142/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros.
143///
144/// **This macro is not part of the public API. It can change or be removed between any versions.**
145///
146/// Define a local variable named `$output`
147/// and assign it the result of calling `_cssparser_internal_to_lowercase`
148/// with a stack-allocated buffer of length `$BUFFER_SIZE`.
149#[macro_export]
150#[doc(hidden)]
151macro_rules! _cssparser_internal_to_lowercase {
152    ($input: expr, $BUFFER_SIZE: expr => $output: ident) => {
153        let mut buffer = $crate::_cssparser_internal_create_uninit_array::<{ $BUFFER_SIZE }>();
154        let input: &str = $input;
155        let $output = $crate::_cssparser_internal_to_lowercase(&mut buffer, input);
156    };
157}
158
159/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros.
160///
161/// **This function is not part of the public API. It can change or be removed between any versions.**
162///
163/// If `input` is larger than buffer, return `None`.
164/// Otherwise, return `input` ASCII-lowercased, using `buffer` as temporary space if necessary.
165#[doc(hidden)]
166#[allow(non_snake_case)]
167#[inline]
168pub fn _cssparser_internal_to_lowercase<'a>(
169    buffer: &'a mut [MaybeUninit<u8>],
170    input: &'a str,
171) -> Option<&'a str> {
172    let buffer = buffer.get_mut(..input.len())?;
173
174    #[cold]
175    fn make_ascii_lowercase<'a>(
176        buffer: &'a mut [MaybeUninit<u8>],
177        input: &'a str,
178        first_uppercase: usize,
179    ) -> &'a str {
180        // This cast doesn't change the pointer's validity
181        // since `u8` has the same layout as `MaybeUninit<u8>`:
182        let input_bytes =
183            unsafe { &*(input.as_bytes() as *const [u8] as *const [MaybeUninit<u8>]) };
184
185        buffer.copy_from_slice(input_bytes);
186
187        // Same as above re layout, plus these bytes have been initialized:
188        let buffer = unsafe { &mut *(buffer as *mut [MaybeUninit<u8>] as *mut [u8]) };
189
190        buffer[first_uppercase..].make_ascii_lowercase();
191        // `buffer` was initialized to a copy of `input`
192        // (which is `&str` so well-formed UTF-8)
193        // then ASCII-lowercased (which preserves UTF-8 well-formedness):
194        unsafe { ::std::str::from_utf8_unchecked(buffer) }
195    }
196
197    Some(
198        match input.bytes().position(|byte| byte.is_ascii_uppercase()) {
199            Some(first_uppercase) => make_ascii_lowercase(buffer, input, first_uppercase),
200            // common case: input is already lower-case
201            None => input,
202        },
203    )
204}