Coverage Report

Created: 2026-03-31 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}