Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@ jobs:
- nightly
- beta
- stable
- 1.68.0
- 1.71.0
features:
-
- --features dummy_match_byte
- --no-default-features
- --features malloc_size_of
include:
- toolchain: nightly
features: --features bench
- toolchain: nightly
features: --features bench,dummy_match_byte
steps:
- uses: actions/checkout@v2

Expand Down
26 changes: 15 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,35 @@ readme = "README.md"
keywords = ["css", "syntax", "parser"]
license = "MPL-2.0"
edition = "2018"
rust-version = "1.68"
rust-version = "1.71"

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

[dev-dependencies]
serde_json = "1.0.25"
difference = "2.0"
encoding_rs = "0.8"

[dependencies]
cssparser-macros = { path = "./macros", version = "0.6.1" }
dtoa-short = "0.3"
itoa = "1.0"
phf = { version = "0.13.1", features = ["macros"] }
serde = { version = "1.0", features = ["derive"], optional = true }
malloc_size_of = { version = "0.1", default-features = false, optional = true }
smallvec = "1.0"

# Optional dependencies
cssparser-macros = { path = "./macros", version = "0.6.1", optional = true }
malloc_size_of = { version = "0.1", default-features = false, optional = true }
phf = { version = "0.13.1", features = ["macros"], optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }

[dev-dependencies]
serde_json = "1.0.25"
difference = "2.0"
encoding_rs = "0.8"

[profile.profiling]
inherits = "release"
debug = true

[features]
default = ["fast_match_byte", "fast_match_color"]
bench = []
dummy_match_byte = []
fast_match_byte = ["dep:cssparser-macros"]
fast_match_color = ["dep:phf"]
# Useful for skipping tests when execution is slow, e.g., under miri
skip_long_tests = []

Expand Down
4 changes: 3 additions & 1 deletion color/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ documentation = "https://docs.rs/cssparser-color/"
repository = "https://github.com/servo/rust-cssparser"
license = "MPL-2.0"
edition = "2021"
rust-version = "1.71"

[lib]
path = "lib.rs"

[dependencies]
cssparser = { path = "..", version = "0.36" }
cssparser = { path = "..", version = "0.36", default-features = false }
serde = { version = "1.0", features = ["derive"], optional = true }

[features]
default = ["cssparser/default"]
serde = ["cssparser/serde", "dep:serde"]

[dev-dependencies]
Expand Down
32 changes: 0 additions & 32 deletions macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,6 @@ extern crate proc_macro;

use proc_macro::TokenStream;

#[proc_macro]
pub fn _cssparser_internal_max_len(input: TokenStream) -> TokenStream {
struct Input {
max_length: usize,
}

impl syn::parse::Parse for Input {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
let mut max_length = 0;
while !input.is_empty() {
if input.peek(syn::Token![_]) {
input.parse::<syn::Token![_]>().unwrap();
continue;
}
let lit: syn::LitStr = input.parse()?;
let value = lit.value();
if value.to_ascii_lowercase() != value {
return Err(syn::Error::new(lit.span(), "must be ASCII-lowercase"));
}
max_length = max_length.max(value.len());
}
Ok(Input { max_length })
}
}

let Input { max_length } = syn::parse_macro_input!(input);
quote::quote!(
pub(super) const MAX_LENGTH: usize = #max_length;
)
.into()
}

fn get_byte_from_lit(lit: &syn::Lit) -> u8 {
if let syn::Lit::Byte(ref byte) = *lit {
byte.value()
Expand Down
2 changes: 1 addition & 1 deletion src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ pub fn parse_hash_color(value: &[u8]) -> Result<(u8, u8, u8, f32), ()> {
})
}

ascii_case_insensitive_phf_map! {
ascii_case_insensitive_map! {
named_colors -> (u8, u8, u8) = {
"black" => (0, 0, 0),
"silver" => (192, 192, 192),
Expand Down
36 changes: 35 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,41 @@ pub use crate::serializer::{serialize_identifier, serialize_name, serialize_stri
pub use crate::serializer::{CssStringWriter, ToCss, TokenSerializationType};
pub use crate::tokenizer::{SourceLocation, SourcePosition, Token};
pub use crate::unicode_range::UnicodeRange;
pub use cssparser_macros::*;

#[cfg(feature = "fast_match_byte")]
pub use cssparser_macros::match_byte;

#[cfg(not(feature = "fast_match_byte"))]
#[macro_use]
mod mac {
/// Expand a TokenStream corresponding to the `match_byte` macro.
///
/// ## Example
///
/// ```rust,ignore
/// match_byte! { tokenizer.next_byte_unchecked(),
/// b'a'..b'z' => { ... }
/// b'0'..b'9' => { ... }
/// b'\n' | b'\\' => { ... }
/// foo => { ... }
/// }
/// ```
///
#[macro_export]
macro_rules! match_byte {
($value:expr, $($rest:tt)* ) => {
match $value {
$(
$rest
)+
}
};
}
}

// Re-exporting phf here means that the crate using the ascii_case_insensitive_phf_map macro do
// do not have to depend on phf directly.
#[cfg(feature = "fast_match_color")]
#[doc(hidden)]
pub use phf as _cssparser_internal_phf;

Expand Down
126 changes: 107 additions & 19 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,105 @@ macro_rules! match_ignore_ascii_case {
( $input:expr,
$(
$( #[$meta: meta] )*
$( $pattern: pat )|+ $( if $guard: expr )? => $then: expr
$( $pattern:literal )|+ $( if $guard: expr )? => $then: expr
),+
$(,_ => $fallback:expr)?
$(,)?
) => {
{
// This dummy module works around the feature gate
// `error[E0658]: procedural macros cannot be expanded to statements`
// by forcing the macro to be in an item context
// rather than expression/statement context,
// even though the macro only expands to items.
mod cssparser_internal {
$crate::_cssparser_internal_max_len! {
$( $( $pattern )+ )+
#[inline(always)]
const fn const_usize_max(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}
$crate::_cssparser_internal_to_lowercase!($input, cssparser_internal::MAX_LENGTH => lowercase);

const MAX_LENGTH : usize = {
let mut maxlen : usize = 0;
$(
$( #[$meta] )*
// {} is necessary to work around "[E0658]: attributes on expressions are experimental"
{
$( maxlen = const_usize_max(maxlen, $pattern.len()); )+
}
)+
maxlen
};

$crate::_cssparser_internal_to_lowercase!($input, MAX_LENGTH => lowercase);
// "A" is a short string that we know is different for every string pattern,
// since we’ve verified that none of them include ASCII upper case letters.
match lowercase.unwrap_or("A") {
$(
$( #[$meta] )*
$( $pattern )|+ $( if $guard )? => $then,
)+
$(_ => $fallback,)?
}
}
};
}

#[cfg(not(feature = "fast_match_color"))]
#[macro_export]
/// Define a function `$name(&str) -> Option<&'static $ValueType>`
///
/// The function finds a match for the input string
/// in a [`phf` map](https://github.com/sfackler/rust-phf)
/// and returns a reference to the corresponding value.
/// Matching is case-insensitive in the ASCII range.
///
/// ## Example:
///
/// ```rust
/// # fn main() {} // Make doctest not wrap everything in its own main
///
/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> {
/// cssparser::ascii_case_insensitive_map! {
/// keywords -> (u8, u8, u8) = {
/// "red" => (255, 0, 0),
/// "green" => (0, 255, 0),
/// "blue" => (0, 0, 255),
/// }
/// }
/// keywords::get(input).cloned()
/// }
/// ```
///
/// You can also iterate over the map entries by using `keywords::entries()`.
macro_rules! ascii_case_insensitive_map {
($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => {
ascii_case_insensitive_map!($name -> $ValueType = { $( $key => $value, )+ })
};
($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => {

// While the obvious choice for this would be an inner module, it's not possible to
// reference from types from there, see:
// <https://github.com/rust-lang/rust/issues/114369>
//
// So we abuse a struct with static associated functions instead.
#[allow(non_camel_case_types)]
struct $name;
impl $name {
#[allow(dead_code)]
fn entries() -> impl Iterator<Item = (&'static &'static str, &'static $ValueType)> {
[ $((&$key, &$value),)* ].iter().copied()
}

fn get(input: &str) -> Option<&'static $ValueType> {
$crate::match_ignore_ascii_case!(input,
$($key => Some(&$value),)*
_ => None,
)
}
}
}
}

#[cfg(feature = "fast_match_color")]
#[macro_export]
/// Define a function `$name(&str) -> Option<&'static $ValueType>`
///
/// The function finds a match for the input string
Expand All @@ -75,7 +146,7 @@ macro_rules! match_ignore_ascii_case {
/// # fn main() {} // Make doctest not wrap everything in its own main
///
/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> {
/// cssparser::ascii_case_insensitive_phf_map! {
/// cssparser::ascii_case_insensitive_map! {
/// keywords -> (u8, u8, u8) = {
/// "red" => (255, 0, 0),
/// "green" => (0, 255, 0),
Expand All @@ -87,6 +158,15 @@ macro_rules! match_ignore_ascii_case {
/// ```
///
/// You can also iterate over the map entries by using `keywords::entries()`.
macro_rules! ascii_case_insensitive_map {
($($any:tt)+) => {
$crate::ascii_case_insensitive_phf_map!($($any)+);
};
}

/// Fast implementation of `ascii_case_insensitive_map!` using a phf map.
/// See `ascii_case_insensitive_map!` above for docs
#[cfg(feature = "fast_match_color")]
#[macro_export]
macro_rules! ascii_case_insensitive_phf_map {
($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr ),+ }) => {
Expand All @@ -95,14 +175,22 @@ macro_rules! ascii_case_insensitive_phf_map {
($name: ident -> $ValueType: ty = { $( $key: tt => $value: expr, )+ }) => {
use $crate::_cssparser_internal_phf as phf;

// See macro above for context.
mod cssparser_internal {
$crate::_cssparser_internal_max_len! {
$( $key )+
#[inline(always)]
const fn const_usize_max(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}

static MAP: phf::Map<&'static str, $ValueType> = phf::phf_map! {
const MAX_LENGTH : usize = {
let mut maxlen : usize = 0;
$( maxlen = const_usize_max(maxlen, ($key).len()); )+
maxlen
};

static __MAP: phf::Map<&'static str, $ValueType> = phf::phf_map! {
$(
$key => $value,
)*
Expand All @@ -118,12 +206,12 @@ macro_rules! ascii_case_insensitive_phf_map {
impl $name {
#[allow(dead_code)]
fn entries() -> impl Iterator<Item = (&'static &'static str, &'static $ValueType)> {
MAP.entries()
__MAP.entries()
}

fn get(input: &str) -> Option<&'static $ValueType> {
$crate::_cssparser_internal_to_lowercase!(input, cssparser_internal::MAX_LENGTH => lowercase);
MAP.get(lowercase?)
$crate::_cssparser_internal_to_lowercase!(input, MAX_LENGTH => lowercase);
__MAP.get(lowercase?)
}
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,9 +659,7 @@ impl<'i: 't, 't> Parser<'i, 't> {
.input
.cached_token
.as_ref()
.map_or(false, |cached_token| {
cached_token.start_position == token_start_position
});
.is_some_and(|cached_token| cached_token.start_position == token_start_position);
let token = if using_cached_token {
let cached_token = self.input.cached_token.as_ref().unwrap();
self.input.tokenizer.reset(&cached_token.end_state);
Expand Down
4 changes: 3 additions & 1 deletion src/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::match_byte;
use std::fmt::{self, Write};
use std::str;

#[cfg(feature = "fast_match_byte")]
pub use crate::match_byte;

use super::Token;

/// Trait for things the can serialize themselves in CSS syntax.
Expand Down
Loading
Loading