Skip to main content

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            let mut buffer = [const { core::mem::MaybeUninit::<u8>::uninit() }; MAX_LENGTH];
65            let input: &str = $input; // Extend lifetime of temporaries
66            let lowercase = $crate::_cssparser_internal_to_lowercase(&mut buffer, input);
67            // "A" is a short string that we know is different for every string pattern,
68            // since we’ve verified that none of them include ASCII upper case letters.
69            match lowercase.unwrap_or("A") {
70                $(
71                    $( #[$meta] )*
72                    $( $pattern )|+ $( if $guard )? => $then,
73                )+
74                $(_ => $fallback,)?
75            }
76        }
77    };
78}
79
80#[cfg(not(feature = "fast_match_color"))]
81#[macro_export]
82/// Define a function `$name(&str) -> Option<&'static $ValueType>`
83///
84/// The function finds a match for the input string
85/// in a [`phf` map](https://github.com/sfackler/rust-phf)
86/// and returns a reference to the corresponding value.
87/// Matching is case-insensitive in the ASCII range.
88///
89/// ## Example:
90///
91/// ```rust
92/// # fn main() {}  // Make doctest not wrap everything in its own main
93///
94/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> {
95///     cssparser::ascii_case_insensitive_map! {
96///         keywords -> (u8, u8, u8) = {
97///             "red" => (255, 0, 0),
98///             "green" => (0, 255, 0),
99///             "blue" => (0, 0, 255),
100///         }
101///     }
102///     keywords::get(input).cloned()
103/// }
104/// ```
105///
106/// You can also iterate over the map entries by using `keywords::entries()`.
107macro_rules! ascii_case_insensitive_map {
108    ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => {
109        ascii_case_insensitive_map!($name -> $ValueType = { $( $key => $value, )+ })
110    };
111    ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => {
112
113        // While the obvious choice for this would be an inner module, it's not possible to
114        // reference from types from there, see:
115        // <https://github.com/rust-lang/rust/issues/114369>
116        //
117        // So we abuse a struct with static associated functions instead.
118        #[allow(non_camel_case_types)]
119        struct $name;
120        impl $name {
121            #[allow(dead_code)]
122            fn entries() -> impl Iterator<Item = (&'static &'static str, &'static $ValueType)> {
123                [ $((&$key, &$value),)* ].iter().copied()
124            }
125
126            fn get(input: &str) -> Option<&'static $ValueType> {
127                $crate::match_ignore_ascii_case!(input,
128                    $($key => Some(&$value),)*
129                    _ => None,
130                )
131            }
132        }
133    }
134}
135
136#[cfg(feature = "fast_match_color")]
137#[macro_export]
138/// Define a function `$name(&str) -> Option<&'static $ValueType>`
139///
140/// The function finds a match for the input string
141/// in a [`phf` map](https://github.com/sfackler/rust-phf)
142/// and returns a reference to the corresponding value.
143/// Matching is case-insensitive in the ASCII range.
144///
145/// ## Example:
146///
147/// ```rust
148/// # fn main() {}  // Make doctest not wrap everything in its own main
149///
150/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> {
151///     cssparser::ascii_case_insensitive_map! {
152///         keywords -> (u8, u8, u8) = {
153///             "red" => (255, 0, 0),
154///             "green" => (0, 255, 0),
155///             "blue" => (0, 0, 255),
156///         }
157///     }
158///     keywords::get(input).cloned()
159/// }
160/// ```
161///
162/// You can also iterate over the map entries by using `keywords::entries()`.
163macro_rules! ascii_case_insensitive_map {
164    ($($any:tt)+) => {
165        $crate::ascii_case_insensitive_phf_map!($($any)+);
166    };
167}
168
169/// Fast implementation of `ascii_case_insensitive_map!` using a phf map.
170/// See `ascii_case_insensitive_map!` above for docs
171#[cfg(feature = "fast_match_color")]
172#[macro_export]
173macro_rules! ascii_case_insensitive_phf_map {
174    ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => {
175        ascii_case_insensitive_phf_map!($name -> $ValueType = { $( $key => $value, )+ })
176    };
177    ($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => {
178        use $crate::_cssparser_internal_phf as phf;
179
180        #[inline(always)]
181        const fn const_usize_max(a: usize, b: usize) -> usize {
182            if a > b {
183                a
184            } else {
185                b
186            }
187        }
188
189        const MAX_LENGTH : usize = {
190            let mut maxlen : usize = 0;
191            $( maxlen = const_usize_max(maxlen, ($key).len()); )+
192            maxlen
193        };
194
195        static __MAP: phf::Map<&'static str, $ValueType> = phf::phf_map! {
196            $(
197                $key => $value,
198            )*
199        };
200
201        // While the obvious choice for this would be an inner module, it's not possible to
202        // reference from types from there, see:
203        // <https://github.com/rust-lang/rust/issues/114369>
204        //
205        // So we abuse a struct with static associated functions instead.
206        #[allow(non_camel_case_types)]
207        struct $name;
208        impl $name {
209            #[allow(dead_code)]
210            fn entries() -> impl Iterator<Item = (&'static &'static str, &'static $ValueType)> {
211                __MAP.entries()
212            }
213
214            fn get(input: &str) -> Option<&'static $ValueType> {
215                let mut buffer = [const { core::mem::MaybeUninit::<u8>::uninit() }; MAX_LENGTH];
216                let lowercase = $crate::_cssparser_internal_to_lowercase(&mut buffer, input)?;
217                __MAP.get(lowercase)
218            }
219        }
220    }
221}
222
223/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros.
224///
225/// **This function is not part of the public API. It can change or be removed between any versions.**
226///
227/// If `input` is larger than buffer, return `None`.
228/// Otherwise, return `input` ASCII-lowercased, using `buffer` as temporary space if necessary.
229#[doc(hidden)]
230#[allow(non_snake_case)]
231#[inline]
232pub fn _cssparser_internal_to_lowercase<'a>(
233    buffer: &'a mut [MaybeUninit<u8>],
234    input: &'a str,
235) -> Option<&'a str> {
236    let buffer = buffer.get_mut(..input.len())?;
237
238    #[cold]
239    fn make_ascii_lowercase<'a>(
240        buffer: &'a mut [MaybeUninit<u8>],
241        input: &'a str,
242        first_uppercase: usize,
243    ) -> &'a str {
244        // This cast doesn't change the pointer's validity
245        // since `u8` has the same layout as `MaybeUninit<u8>`:
246        let input_bytes =
247            unsafe { &*(input.as_bytes() as *const [u8] as *const [MaybeUninit<u8>]) };
248
249        buffer.copy_from_slice(input_bytes);
250
251        // Same as above re layout, plus these bytes have been initialized:
252        let buffer = unsafe { &mut *(buffer as *mut [MaybeUninit<u8>] as *mut [u8]) };
253
254        buffer[first_uppercase..].make_ascii_lowercase();
255        // `buffer` was initialized to a copy of `input`
256        // (which is `&str` so well-formed UTF-8)
257        // then ASCII-lowercased (which preserves UTF-8 well-formedness):
258        unsafe { ::std::str::from_utf8_unchecked(buffer) }
259    }
260
261    Some(
262        match input.bytes().position(|byte| byte.is_ascii_uppercase()) {
263            Some(first_uppercase) => make_ascii_lowercase(buffer, input, first_uppercase),
264            // common case: input is already lower-case
265            None => input,
266        },
267    )
268}