Skip to content

Commit 4d4568b

Browse files
authored
Make all proc macros optional (#425)
* Invert match_byte feature flag Signed-off-by: Nico Burns <nico@nicoburns.com> * Use const fn rather than proc_macro for MAX_LENGTH Signed-off-by: Nico Burns <nico@nicoburns.com> * Make cssparser-macros optional * Make phf for color matching optional Signed-off-by: Nico Burns <nico@nicoburns.com> * Group optional dependencies in Cargo.toml * Fixup CI for new features Signed-off-by: Nico Burns <nico@nicoburns.com> * Bump MSRV to 1.71 --------- Signed-off-by: Nico Burns <nico@nicoburns.com>
1 parent 76fc1b0 commit 4d4568b

File tree

11 files changed

+171
-88
lines changed

11 files changed

+171
-88
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,14 @@ jobs:
1818
- nightly
1919
- beta
2020
- stable
21-
- 1.68.0
21+
- 1.71.0
2222
features:
2323
-
24-
- --features dummy_match_byte
24+
- --no-default-features
2525
- --features malloc_size_of
2626
include:
2727
- toolchain: nightly
2828
features: --features bench
29-
- toolchain: nightly
30-
features: --features bench,dummy_match_byte
3129
steps:
3230
- uses: actions/checkout@v2
3331

Cargo.toml

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,35 @@ readme = "README.md"
1010
keywords = ["css", "syntax", "parser"]
1111
license = "MPL-2.0"
1212
edition = "2018"
13-
rust-version = "1.68"
13+
rust-version = "1.71"
1414

1515
exclude = ["src/css-parsing-tests/**", "src/big-data-url.css"]
1616

17-
[dev-dependencies]
18-
serde_json = "1.0.25"
19-
difference = "2.0"
20-
encoding_rs = "0.8"
21-
2217
[dependencies]
23-
cssparser-macros = { path = "./macros", version = "0.6.1" }
2418
dtoa-short = "0.3"
2519
itoa = "1.0"
26-
phf = { version = "0.13.1", features = ["macros"] }
27-
serde = { version = "1.0", features = ["derive"], optional = true }
28-
malloc_size_of = { version = "0.1", default-features = false, optional = true }
2920
smallvec = "1.0"
3021

22+
# Optional dependencies
23+
cssparser-macros = { path = "./macros", version = "0.6.1", optional = true }
24+
malloc_size_of = { version = "0.1", default-features = false, optional = true }
25+
phf = { version = "0.13.1", features = ["macros"], optional = true }
26+
serde = { version = "1.0", features = ["derive"], optional = true }
27+
28+
[dev-dependencies]
29+
serde_json = "1.0.25"
30+
difference = "2.0"
31+
encoding_rs = "0.8"
32+
3133
[profile.profiling]
3234
inherits = "release"
3335
debug = true
3436

3537
[features]
38+
default = ["fast_match_byte", "fast_match_color"]
3639
bench = []
37-
dummy_match_byte = []
40+
fast_match_byte = ["dep:cssparser-macros"]
41+
fast_match_color = ["dep:phf"]
3842
# Useful for skipping tests when execution is slow, e.g., under miri
3943
skip_long_tests = []
4044

color/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ documentation = "https://docs.rs/cssparser-color/"
77
repository = "https://github.com/servo/rust-cssparser"
88
license = "MPL-2.0"
99
edition = "2021"
10+
rust-version = "1.71"
1011

1112
[lib]
1213
path = "lib.rs"
1314

1415
[dependencies]
15-
cssparser = { path = "..", version = "0.36" }
16+
cssparser = { path = "..", version = "0.36", default-features = false }
1617
serde = { version = "1.0", features = ["derive"], optional = true }
1718

1819
[features]
20+
default = ["cssparser/default"]
1921
serde = ["cssparser/serde", "dep:serde"]
2022

2123
[dev-dependencies]

macros/lib.rs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,6 @@ extern crate proc_macro;
66

77
use proc_macro::TokenStream;
88

9-
#[proc_macro]
10-
pub fn _cssparser_internal_max_len(input: TokenStream) -> TokenStream {
11-
struct Input {
12-
max_length: usize,
13-
}
14-
15-
impl syn::parse::Parse for Input {
16-
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
17-
let mut max_length = 0;
18-
while !input.is_empty() {
19-
if input.peek(syn::Token![_]) {
20-
input.parse::<syn::Token![_]>().unwrap();
21-
continue;
22-
}
23-
let lit: syn::LitStr = input.parse()?;
24-
let value = lit.value();
25-
if value.to_ascii_lowercase() != value {
26-
return Err(syn::Error::new(lit.span(), "must be ASCII-lowercase"));
27-
}
28-
max_length = max_length.max(value.len());
29-
}
30-
Ok(Input { max_length })
31-
}
32-
}
33-
34-
let Input { max_length } = syn::parse_macro_input!(input);
35-
quote::quote!(
36-
pub(super) const MAX_LENGTH: usize = #max_length;
37-
)
38-
.into()
39-
}
40-
419
fn get_byte_from_lit(lit: &syn::Lit) -> u8 {
4210
if let syn::Lit::Byte(ref byte) = *lit {
4311
byte.value()

src/color.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ pub fn parse_hash_color(value: &[u8]) -> Result<(u8, u8, u8, f32), ()> {
172172
})
173173
}
174174

175-
ascii_case_insensitive_phf_map! {
175+
ascii_case_insensitive_map! {
176176
named_colors -> (u8, u8, u8) = {
177177
"black" => (0, 0, 0),
178178
"silver" => (192, 192, 192),

src/lib.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,41 @@ pub use crate::serializer::{serialize_identifier, serialize_name, serialize_stri
8484
pub use crate::serializer::{CssStringWriter, ToCss, TokenSerializationType};
8585
pub use crate::tokenizer::{SourceLocation, SourcePosition, Token};
8686
pub use crate::unicode_range::UnicodeRange;
87-
pub use cssparser_macros::*;
87+
88+
#[cfg(feature = "fast_match_byte")]
89+
pub use cssparser_macros::match_byte;
90+
91+
#[cfg(not(feature = "fast_match_byte"))]
92+
#[macro_use]
93+
mod mac {
94+
/// Expand a TokenStream corresponding to the `match_byte` macro.
95+
///
96+
/// ## Example
97+
///
98+
/// ```rust,ignore
99+
/// match_byte! { tokenizer.next_byte_unchecked(),
100+
/// b'a'..b'z' => { ... }
101+
/// b'0'..b'9' => { ... }
102+
/// b'\n' | b'\\' => { ... }
103+
/// foo => { ... }
104+
/// }
105+
/// ```
106+
///
107+
#[macro_export]
108+
macro_rules! match_byte {
109+
($value:expr, $($rest:tt)* ) => {
110+
match $value {
111+
$(
112+
$rest
113+
)+
114+
}
115+
};
116+
}
117+
}
118+
119+
// Re-exporting phf here means that the crate using the ascii_case_insensitive_phf_map macro do
120+
// do not have to depend on phf directly.
121+
#[cfg(feature = "fast_match_color")]
88122
#[doc(hidden)]
89123
pub use phf as _cssparser_internal_phf;
90124

src/macros.rs

Lines changed: 107 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,34 +34,105 @@ macro_rules! match_ignore_ascii_case {
3434
( $input:expr,
3535
$(
3636
$( #[$meta: meta] )*
37-
$( $pattern: pat )|+ $( if $guard: expr )? => $then: expr
37+
$( $pattern:literal )|+ $( if $guard: expr )? => $then: expr
3838
),+
39+
$(,_ => $fallback:expr)?
3940
$(,)?
4041
) => {
4142
{
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 )+ )+
43+
#[inline(always)]
44+
const fn const_usize_max(a: usize, b: usize) -> usize {
45+
if a > b {
46+
a
47+
} else {
48+
b
5049
}
5150
}
52-
$crate::_cssparser_internal_to_lowercase!($input, cssparser_internal::MAX_LENGTH => lowercase);
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);
5365
// "A" is a short string that we know is different for every string pattern,
5466
// since we’ve verified that none of them include ASCII upper case letters.
5567
match lowercase.unwrap_or("A") {
5668
$(
5769
$( #[$meta] )*
5870
$( $pattern )|+ $( if $guard )? => $then,
5971
)+
72+
$(_ => $fallback,)?
6073
}
6174
}
6275
};
6376
}
6477

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]
65136
/// Define a function `$name(&str) -> Option<&'static $ValueType>`
66137
///
67138
/// The function finds a match for the input string
@@ -75,7 +146,7 @@ macro_rules! match_ignore_ascii_case {
75146
/// # fn main() {} // Make doctest not wrap everything in its own main
76147
///
77148
/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> {
78-
/// cssparser::ascii_case_insensitive_phf_map! {
149+
/// cssparser::ascii_case_insensitive_map! {
79150
/// keywords -> (u8, u8, u8) = {
80151
/// "red" => (255, 0, 0),
81152
/// "green" => (0, 255, 0),
@@ -87,6 +158,15 @@ macro_rules! match_ignore_ascii_case {
87158
/// ```
88159
///
89160
/// 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")]
90170
#[macro_export]
91171
macro_rules! ascii_case_insensitive_phf_map {
92172
($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => {
@@ -95,14 +175,22 @@ macro_rules! ascii_case_insensitive_phf_map {
95175
($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => {
96176
use $crate::_cssparser_internal_phf as phf;
97177

98-
// See macro above for context.
99-
mod cssparser_internal {
100-
$crate::_cssparser_internal_max_len! {
101-
$( $key )+
178+
#[inline(always)]
179+
const fn const_usize_max(a: usize, b: usize) -> usize {
180+
if a > b {
181+
a
182+
} else {
183+
b
102184
}
103185
}
104186

105-
static MAP: phf::Map<&'static str, $ValueType> = phf::phf_map! {
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! {
106194
$(
107195
$key => $value,
108196
)*
@@ -118,12 +206,12 @@ macro_rules! ascii_case_insensitive_phf_map {
118206
impl $name {
119207
#[allow(dead_code)]
120208
fn entries() -> impl Iterator<Item = (&'static &'static str, &'static $ValueType)> {
121-
MAP.entries()
209+
__MAP.entries()
122210
}
123211

124212
fn get(input: &str) -> Option<&'static $ValueType> {
125-
$crate::_cssparser_internal_to_lowercase!(input, cssparser_internal::MAX_LENGTH => lowercase);
126-
MAP.get(lowercase?)
213+
$crate::_cssparser_internal_to_lowercase!(input, MAX_LENGTH => lowercase);
214+
__MAP.get(lowercase?)
127215
}
128216
}
129217
}

src/parser.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -659,9 +659,7 @@ impl<'i: 't, 't> Parser<'i, 't> {
659659
.input
660660
.cached_token
661661
.as_ref()
662-
.map_or(false, |cached_token| {
663-
cached_token.start_position == token_start_position
664-
});
662+
.is_some_and(|cached_token| cached_token.start_position == token_start_position);
665663
let token = if using_cached_token {
666664
let cached_token = self.input.cached_token.as_ref().unwrap();
667665
self.input.tokenizer.reset(&cached_token.end_state);

src/serializer.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
use crate::match_byte;
65
use std::fmt::{self, Write};
76
use std::str;
87

8+
#[cfg(feature = "fast_match_byte")]
9+
pub use crate::match_byte;
10+
911
use super::Token;
1012

1113
/// Trait for things the can serialize themselves in CSS syntax.

0 commit comments

Comments
 (0)