diff --git a/Cargo.toml b/Cargo.toml index 211221fe..ecc71727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cssparser" -version = "0.11.0" +version = "0.12.0" authors = [ "Simon Sapin " ] description = "Rust implementation of CSS Syntax Level 3" @@ -20,10 +20,11 @@ tempdir = "0.3" encoding_rs = "0.5" [dependencies] -cssparser-macros = {path = "./macros", version = "0.1"} +cssparser-macros = {path = "./macros", version = "0.2"} heapsize = {version = "0.3", optional = true} matches = "0.1" phf = "0.7" +procedural-masquerade = {path = "./procedural-masquerade", version = "0.1"} serde = {version = "0.9", optional = true} [build-dependencies] @@ -35,4 +36,4 @@ bench = [] dummy_match_byte = [] [workspace] -members = [".", "./macros"] +members = [".", "./macros", "./procedural-masquerade"] diff --git a/build.rs b/build.rs index 30e3d8b1..322c1790 100644 --- a/build.rs +++ b/build.rs @@ -16,18 +16,18 @@ mod codegen { } #[cfg(not(feature = "dummy_match_byte"))] -#[path = "src/macros/mod.rs"] -mod macros; +#[path = "build/match_byte.rs"] +mod match_byte; #[cfg(not(feature = "dummy_match_byte"))] mod codegen { - use macros; + use match_byte; use std::env; use std::path::Path; pub fn main(tokenizer_rs: &Path) { - macros::match_byte::expand(tokenizer_rs, - &Path::new(&env::var("OUT_DIR").unwrap()).join("tokenizer.rs")); + match_byte::expand(tokenizer_rs, + &Path::new(&env::var("OUT_DIR").unwrap()).join("tokenizer.rs")); } } diff --git a/src/macros/match_byte.rs b/build/match_byte.rs similarity index 100% rename from src/macros/match_byte.rs rename to build/match_byte.rs diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 2ca733c4..3dd61a72 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cssparser-macros" -version = "0.1.0" +version = "0.2.0" authors = ["Simon Sapin "] description = "Procedural macros for cssparser" documentation = "https://docs.rs/cssparser-macros/" @@ -12,7 +12,7 @@ path = "lib.rs" proc-macro = true [dependencies] +procedural-masquerade = {path = "../procedural-masquerade", version = "0.1"} phf_codegen = "0.7" -quote = "0.3" -syn = "0.11" - +quote = "0.3.14" +syn = {version = "0.11.8", features = ["full"]} diff --git a/macros/lib.rs b/macros/lib.rs index a783147a..1e6f3590 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -2,6 +2,7 @@ * 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/. */ +#[macro_use] extern crate procedural_masquerade; extern crate phf_codegen; extern crate proc_macro; #[macro_use] extern crate quote; @@ -9,114 +10,96 @@ extern crate syn; use std::ascii::AsciiExt; -/// Find a `#[cssparser__assert_ascii_lowercase__data(string = "…", string = "…")]` attribute, -/// and panic if any string contains ASCII uppercase letters. -#[proc_macro_derive(cssparser__assert_ascii_lowercase, - attributes(cssparser__assert_ascii_lowercase__data))] -pub fn assert_ascii_lowercase(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = syn::parse_macro_input(&input.to_string()).unwrap(); - let data = list_attr(&input, "cssparser__assert_ascii_lowercase__data"); - - for sub_attr in data { - let string = sub_attr_value(sub_attr, "string"); - assert_eq!(*string, string.to_ascii_lowercase(), - "the expected strings must be given in ASCII lowercase"); +define_proc_macros! { + /// Input: the arms of a `match` expression. + /// + /// Output: a `MAX_LENGTH` constant with the length of the longest string pattern. + /// + /// Panic if the arms contain non-string patterns, + /// or string patterns that contains ASCII uppercase letters. + #[allow(non_snake_case)] + pub fn cssparser_internal__assert_ascii_lowercase__max_len(input: &str) -> String { + let expr = syn::parse_expr(&format!("match x {{ {} }}", input)).unwrap(); + let arms = match expr { + syn::Expr { node: syn::ExprKind::Match(_, ref arms), .. } => arms, + _ => panic!("expected a match expression, got {:?}", expr) + }; + max_len(arms.iter().flat_map(|arm| &arm.pats).filter_map(|pattern| { + let expr = match *pattern { + syn::Pat::Lit(ref expr) => expr, + syn::Pat::Wild | + syn::Pat::Ident(_, _, None) => return None, + syn::Pat::Ident(_, _, Some(ref sub_pattern)) => { + match **sub_pattern { + syn::Pat::Lit(ref expr) => expr, + syn::Pat::Wild => return None, + _ => panic!("expected string or wildcard pattern, got {:?}", pattern) + } + } + _ => panic!("expected string or wildcard pattern, got {:?}", pattern) + }; + match **expr { + syn::Expr { node: syn::ExprKind::Lit(syn::Lit::Str(ref string, _)), .. } => { + assert_eq!(*string, string.to_ascii_lowercase(), + "string patterns must be given in ASCII lowercase"); + Some(string.len()) + } + _ => panic!("expected string pattern, got {:?}", expr) + } + })) } - "".parse().unwrap() -} - -/// Find a `#[cssparser__max_len__data(string = "…", string = "…")]` attribute, -/// and emit a `MAX_LENGTH` constant with the length of the longest string. -#[proc_macro_derive(cssparser__max_len, - attributes(cssparser__max_len__data))] -pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = syn::parse_macro_input(&input.to_string()).unwrap(); - let data = list_attr(&input, "cssparser__max_len__data"); - - let lengths = data.iter().map(|sub_attr| sub_attr_value(sub_attr, "string").len()); - let max_length = lengths.max().expect("expected at least one string"); - - let tokens = quote! { - const MAX_LENGTH: usize = #max_length; - }; - - tokens.as_str().parse().unwrap() -} - -/// On `struct $Name($ValueType)`, add a new static method -/// `fn map() -> &'static ::phf::Map<&'static str, $ValueType>`. -/// The map’s content is given as: -/// `#[cssparser__phf_map__kv_pairs(key = "…", value = "…", key = "…", value = "…")]`. -/// Keys are ASCII-lowercased. -#[proc_macro_derive(cssparser__phf_map, - attributes(cssparser__phf_map__kv_pairs))] -pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = syn::parse_macro_input(&input.to_string()).unwrap(); - let name = &input.ident; - let value_type = match input.body { - syn::Body::Struct(syn::VariantData::Tuple(ref fields)) if fields.len() == 1 => { - &fields[0].ty - } - _ => panic!("expected tuple struct newtype, got {:?}", input.body) - }; - - let pairs: Vec<_> = list_attr(&input, "cssparser__phf_map__kv_pairs").chunks(2).map(|chunk| { - let key = sub_attr_value(&chunk[0], "key"); - let value = sub_attr_value(&chunk[1], "value"); - (key.to_ascii_lowercase(), value) - }).collect(); - - let mut map = phf_codegen::Map::new(); - for &(ref key, value) in &pairs { - map.entry(&**key, value); + /// Input: string literals with no separator + /// + /// Output: a `MAX_LENGTH` constant with the length of the longest string. + #[allow(non_snake_case)] + pub fn cssparser_internal__max_len(input: &str) -> String { + max_len(syn::parse_token_trees(input).unwrap().iter().map(|tt| string_literal(tt).len())) } - let mut initializer_bytes = Vec::::new(); - let mut initializer_tokens = quote::Tokens::new(); - map.build(&mut initializer_bytes).unwrap(); - initializer_tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap()); - - let tokens = quote! { - impl #name { - #[inline] - fn map() -> &'static ::phf::Map<&'static str, #value_type> { - static MAP: ::phf::Map<&'static str, #value_type> = #initializer_tokens; - &MAP - } + /// Input: parsed as token trees. The first TT is a type. (Can be wrapped in parens.) + /// following TTs are grouped in pairs, each pair being a key as a string literal + /// and the corresponding value as a const expression. + /// + /// Output: a rust-phf map, with keys ASCII-lowercased: + /// ``` + /// static MAP: &'static ::cssparser::phf::Map<&'static str, $ValueType> = …; + /// ``` + #[allow(non_snake_case)] + pub fn cssparser_internal__phf_map(input: &str) -> String { + let token_trees = syn::parse_token_trees(input).unwrap(); + let value_type = &token_trees[0]; + let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| { + let key = string_literal(&chunk[0]); + let value = &chunk[1]; + (key.to_ascii_lowercase(), quote!(#value).to_string()) + }).collect(); + + let mut map = phf_codegen::Map::new(); + map.phf_path("::cssparser::_internal__phf"); + for &(ref key, ref value) in &pairs { + map.entry(&**key, &**value); } - }; - tokens.as_str().parse().unwrap() + let mut tokens = quote! { + static MAP: ::cssparser::_internal__phf::Map<&'static str, #value_type> = + }; + let mut initializer_bytes = Vec::new(); + map.build(&mut initializer_bytes).unwrap(); + tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap()); + tokens.append(";"); + tokens.into_string() + } } -/// Panic if the first attribute isn’t `#[foo(…)]` with the given name, -/// or return the parameters. -fn list_attr<'a>(input: &'a syn::DeriveInput, expected_name: &str) -> &'a [syn::NestedMetaItem] { - for attr in &input.attrs { - match attr.value { - syn::MetaItem::List(ref name, ref nested) if name == expected_name => { - return nested - } - _ => {} - } - } - panic!("expected a {} attribute", expected_name) +fn max_len>(lengths: I) -> String { + let max_length = lengths.max().expect("expected at least one string"); + quote!( const MAX_LENGTH: usize = #max_length; ).into_string() } -/// Panic if `sub_attr` is not a name-value like `foo = "…"` with the given name, -/// or return the value. -fn sub_attr_value<'a>(sub_attr: &'a syn::NestedMetaItem, expected_name: &str) -> &'a str { - match *sub_attr { - syn::NestedMetaItem::MetaItem( - syn::MetaItem::NameValue(ref name, syn::Lit::Str(ref value, _)) - ) - if name == expected_name => { - value - } - _ => { - panic!("expected a `{} = \"…\"` parameter to the attribute, got {:?}", - expected_name, sub_attr) - } +fn string_literal(token: &syn::TokenTree) -> &str { + match *token { + syn::TokenTree::Token(syn::Token::Literal(syn::Lit::Str(ref string, _))) => string, + _ => panic!("expected string literal, got {:?}", token) } } diff --git a/procedural-masquerade/Cargo.toml b/procedural-masquerade/Cargo.toml new file mode 100644 index 00000000..d1cdccc4 --- /dev/null +++ b/procedural-masquerade/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "procedural-masquerade" +version = "0.1.1" +authors = ["Simon Sapin "] +description = "macro_rules for making proc_macro_derive pretending to be proc_macro" +documentation = "https://docs.rs/procedural-masquerade/" +repository = "https://github.com/servo/rust-cssparser" +license = "MIT/Apache-2.0" + +[lib] +path = "lib.rs" +doctest = false diff --git a/procedural-masquerade/lib.rs b/procedural-masquerade/lib.rs new file mode 100644 index 00000000..c2bd6b48 --- /dev/null +++ b/procedural-masquerade/lib.rs @@ -0,0 +1,250 @@ +//! # Custom `derive` pretending to be functional procedural macros on Rust 1.15 +//! +//! This crate enables creating function-like macros (invoked as `foo!(...)`) +//! with a procedural component, +//! based on both custom `derive` (a.k.a. *Macros 1.1*) and `macro_rules!`. +//! +//! This convoluted mechanism enables such macros to run on stable Rust 1.15, +//! even though functional procedural macros (a.k.a. *Macros 2.0*) are not available yet. +//! +//! A library defining such a macro needs two crates: a “normal” one, and a `proc-macro` one. +//! In the example below we’ll call them `libfoo` and `libfoo-macros`, respectively. +//! +//! # Credits +//! +//! The trick that makes this crate work +//! is based on an idea from [David Tolnay](https://github.com/dtolnay). +//! Many thanks! +//! +//! # Example +//! +//! As a simple example, we’re going to re-implement the `stringify!` macro. +//! This is useless since `stringify!` already exists in the standard library, +//! and a bit absurd since this crate uses `stringify!` internally. +//! +//! Nevertheless, it serves as a simple example to demonstrate the use of this crate. +//! +//! ## The `proc-macro` crate +//! +//! The minimal `Cargo.toml` file is typical for Macros 1.1: +//! +//! ```toml +//! [package] +//! name = "libfoo-macros" +//! version = "1.0.0" +//! +//! [lib] +//! proc-macro = true +//! ``` +//! +//! In the code, we define the procedural part of our macro in a function. +//! This function will not be used directly by end users, +//! but it still needs to be re-exported to them +//! (because of limitations in `macro_rules!`). +//! +//! To avoid name collisions, we and a long and explicit prefix in the function’s name. +//! +//! The function takes a string containing arbitrary Rust tokens, +//! and returns a string that is parsed as *items*. +//! The returned string can contain constants, statics, functions, `impl`s, etc., +//! but not expressions directly. +//! +//! ```rust +//! #[macro_use] extern crate procedural_masquerade; +//! extern crate proc_macro; +//! +//! define_proc_macros! { +//! #[allow(non_snake_case)] +//! pub fn foo_internal__stringify_const(input: &str) -> String { +//! format!("const STRINGIFIED: &'static str = {:?};", input) +//! } +//! } +//! ``` +//! +//! A less trivial macro would probably use +//! the [`syn`](https://github.com/dtolnay/syn/) crate to parse its input +//! and the [`quote`](https://github.com/dtolnay/quote) crate to generate its output. +//! +//! ## The library crate +//! +//! ```toml +//! [package] +//! name = "libfoo" +//! version = "1.0.0" +//! +//! [dependencies] +//! cssparser-macros = {path = "./macros", version = "1.0"} +//! ``` +//! +//! ```rust +//! #[macro_use] extern crate libfoo_macros; // (1) +//! +//! pub use libfoo_macros::*; // (2) +//! +//! define_invoke_proc_macro!(libfoo__invoke_proc_macro); // (3) +//! +//! #[macro_export] +//! macro_rules! foo_stringify { // (4) +//! ( $( $tts: tt ) ) => { +//! { // (5) +//! libfoo__invoke_proc_macro! { // (6) +//! foo_internal__stringify_const!( $( $tts ) ) // (7) +//! } +//! STRINGIFIED // (8) +//! } +//! } +//! } +//! ``` +//! +//! Let’s go trough the numbered lines one by one: +//! +//! 1. `libfoo` depends on the other `libfoo-macros`, and imports its macros. +//! 2. Everything exported by `libfoo-macros` (which is one custom `derive`) +//! is re-exported to users of `libfoo`. +//! They’re not expected to use it directly, +//! but expansion of the `foo_stringify` macro needs it. +//! 3. This macro invocation defines yet another macro, called `libfoo__invoke_proc_macro`, +//! which is also exported. +//! This indirection is necessary +//! because re-exporting `macro_rules!` macros doesn’t work currently, +//! and once again it is used by the expansion of `foo_stringify`. +//! Again, we use a long prefix to avoid name collisions. +//! 4. Finally, we define the macro that we really want. +//! This one has a name that users will use. +//! 5. The expansion of this macro will define some items, +//! whose names are not hygienic in `macro_rules`. +//! So we wrap everything in an extra `{…}` block to prevent these names for leaking. +//! 6. Here we use the macro defined in (3), +//! which allows us to write something that look like invoking a functional procedural macro, +//! but really uses a custom `derive`. +//! This will define a type called `ProceduralMasqueradeDummyType`, +//! as a placeholder to use `derive`. +//! If `libfoo__invoke_proc_macro!` is to be used more than once, +//! each use needs to be nested in another block +//! so that the names of multiple dummy types don’t collide. +//! 7. In addition to the dummy type, +//! the items returned by our procedural component are inserted here. +//! (In this case the `STRINGIFIED` constant.) +//! 8. Finally, we write the expression that we want the macro to evaluate to. +//! This expression can use parts of `foo_stringify`’s input, +//! it can contain control-flow statements like `return` or `continue`, +//! and of course refer to procedurally-defined items. +//! +//! This macro can be used in an expression context. +//! It expands to a block-expression that contains some items (as an implementation detail) +//! and ends with another expression. +//! +//! ## For users +//! +//! Users of `libfoo` don’t need to worry about any of these implementation details. +//! They can use the `foo_stringify` macro as if it were a simle `macro_rules` macro: +//! +//! ```rust +//! #[macro_use] extern crate libfoo; +//! +//! fn main() { +//! do_something(foo_stringify!(1 + 2)); +//! } +//! +//! fn do_something(_: &str) { /* ... */ } +//! ``` +//! +//! # More +//! +//! To see a more complex example, look at +//! [`cssparser`’s `src/macros.rs](https://github.com/servo/rust-cssparser/blob/master/src/macros.rs) +//! and +//! [`cssparser-macros`’s `macros/lib.rs](https://github.com/servo/rust-cssparser/blob/master/macros/lib.rs). + +/// This macro wraps `&str -> String` functions +/// in custom `derive` implementations with `#[proc_macro_derive]`. +/// +/// See crate documentation for details. +#[macro_export] +macro_rules! define_proc_macros { + ( + $( + $( #[$attr:meta] )* + pub fn $proc_macro_name: ident ($input: ident : &str) -> String + $body: block + )+ + ) => { + $( + $( #[$attr] )* + #[proc_macro_derive($proc_macro_name)] + pub fn $proc_macro_name(derive_input: ::proc_macro::TokenStream) + -> ::proc_macro::TokenStream { + let $input = derive_input.to_string(); + let $input = $crate::_extract_input(&$input); + $body.parse().unwrap() + } + )+ + } +} + +/// Implementation detail of `define_proc_macros!`. +/// +/// **This function is not part of the public API. It can change or be removed between any versions.** +#[doc(hidden)] +pub fn _extract_input(derive_input: &str) -> &str { + let mut input = derive_input; + + for expected in &["#[allow(unused)]", "enum", "ProceduralMasqueradeDummyType", "{", + "Input", "=", "(0,", "stringify!", "("] { + input = input.trim_left(); + assert!(input.starts_with(expected), + "expected prefix {:?} not found in {:?}", expected, derive_input); + input = &input[expected.len()..]; + } + + for expected in [")", ").0,", "}"].iter().rev() { + input = input.trim_right(); + assert!(input.ends_with(expected), + "expected suffix {:?} not found in {:?}", expected, derive_input); + let end = input.len() - expected.len(); + input = &input[..end]; + } + + input +} + +/// This macro expands to the definition of another macro (whose name is given as a parameter). +/// +/// See crate documentation for details. +#[macro_export] +macro_rules! define_invoke_proc_macro { + ($macro_name: ident) => { + /// Implementation detail of other macros in this crate. + #[doc(hidden)] + #[macro_export] + macro_rules! $macro_name { + ($proc_macro_name: ident ! $paren: tt) => { + #[derive($proc_macro_name)] + #[allow(unused)] + enum ProceduralMasqueradeDummyType { + // The magic happens here. + // + // We use an `enum` with an explicit discriminant + // because that is the only case where a type definition + // can contain a (const) expression. + // + // `(0, "foo").0` evalutes to 0, with the `"foo"` part ignored. + // + // By the time the `#[proc_macro_derive]` function + // implementing `#[derive($proc_macro_name)]` is called, + // `$paren` has already been replaced with the input of this inner macro, + // but `stringify!` has not been expanded yet. + // + // This how arbitrary tokens can be inserted + // in the input to the `#[proc_macro_derive]` function. + // + // Later, `stringify!(...)` is expanded into a string literal + // which is then ignored. + // Using `stringify!` enables passing arbitrary tokens + // rather than only what can be parsed as a const expression. + Input = (0, stringify! $paren ).0 + } + } + } + } +} diff --git a/src/color.rs b/src/color.rs index 85ae102a..ea65f549 100644 --- a/src/color.rs +++ b/src/color.rs @@ -180,162 +180,162 @@ pub fn parse_color_keyword(ident: &str) -> Result { } } ascii_case_insensitive_phf_map! { - KEYWORDS: Map = { - "black" => "rgb!(0, 0, 0)", - "silver" => "rgb!(192, 192, 192)", - "gray" => "rgb!(128, 128, 128)", - "white" => "rgb!(255, 255, 255)", - "maroon" => "rgb!(128, 0, 0)", - "red" => "rgb!(255, 0, 0)", - "purple" => "rgb!(128, 0, 128)", - "fuchsia" => "rgb!(255, 0, 255)", - "green" => "rgb!(0, 128, 0)", - "lime" => "rgb!(0, 255, 0)", - "olive" => "rgb!(128, 128, 0)", - "yellow" => "rgb!(255, 255, 0)", - "navy" => "rgb!(0, 0, 128)", - "blue" => "rgb!(0, 0, 255)", - "teal" => "rgb!(0, 128, 128)", - "aqua" => "rgb!(0, 255, 255)", - - "aliceblue" => "rgb!(240, 248, 255)", - "antiquewhite" => "rgb!(250, 235, 215)", - "aquamarine" => "rgb!(127, 255, 212)", - "azure" => "rgb!(240, 255, 255)", - "beige" => "rgb!(245, 245, 220)", - "bisque" => "rgb!(255, 228, 196)", - "blanchedalmond" => "rgb!(255, 235, 205)", - "blueviolet" => "rgb!(138, 43, 226)", - "brown" => "rgb!(165, 42, 42)", - "burlywood" => "rgb!(222, 184, 135)", - "cadetblue" => "rgb!(95, 158, 160)", - "chartreuse" => "rgb!(127, 255, 0)", - "chocolate" => "rgb!(210, 105, 30)", - "coral" => "rgb!(255, 127, 80)", - "cornflowerblue" => "rgb!(100, 149, 237)", - "cornsilk" => "rgb!(255, 248, 220)", - "crimson" => "rgb!(220, 20, 60)", - "cyan" => "rgb!(0, 255, 255)", - "darkblue" => "rgb!(0, 0, 139)", - "darkcyan" => "rgb!(0, 139, 139)", - "darkgoldenrod" => "rgb!(184, 134, 11)", - "darkgray" => "rgb!(169, 169, 169)", - "darkgreen" => "rgb!(0, 100, 0)", - "darkgrey" => "rgb!(169, 169, 169)", - "darkkhaki" => "rgb!(189, 183, 107)", - "darkmagenta" => "rgb!(139, 0, 139)", - "darkolivegreen" => "rgb!(85, 107, 47)", - "darkorange" => "rgb!(255, 140, 0)", - "darkorchid" => "rgb!(153, 50, 204)", - "darkred" => "rgb!(139, 0, 0)", - "darksalmon" => "rgb!(233, 150, 122)", - "darkseagreen" => "rgb!(143, 188, 143)", - "darkslateblue" => "rgb!(72, 61, 139)", - "darkslategray" => "rgb!(47, 79, 79)", - "darkslategrey" => "rgb!(47, 79, 79)", - "darkturquoise" => "rgb!(0, 206, 209)", - "darkviolet" => "rgb!(148, 0, 211)", - "deeppink" => "rgb!(255, 20, 147)", - "deepskyblue" => "rgb!(0, 191, 255)", - "dimgray" => "rgb!(105, 105, 105)", - "dimgrey" => "rgb!(105, 105, 105)", - "dodgerblue" => "rgb!(30, 144, 255)", - "firebrick" => "rgb!(178, 34, 34)", - "floralwhite" => "rgb!(255, 250, 240)", - "forestgreen" => "rgb!(34, 139, 34)", - "gainsboro" => "rgb!(220, 220, 220)", - "ghostwhite" => "rgb!(248, 248, 255)", - "gold" => "rgb!(255, 215, 0)", - "goldenrod" => "rgb!(218, 165, 32)", - "greenyellow" => "rgb!(173, 255, 47)", - "grey" => "rgb!(128, 128, 128)", - "honeydew" => "rgb!(240, 255, 240)", - "hotpink" => "rgb!(255, 105, 180)", - "indianred" => "rgb!(205, 92, 92)", - "indigo" => "rgb!(75, 0, 130)", - "ivory" => "rgb!(255, 255, 240)", - "khaki" => "rgb!(240, 230, 140)", - "lavender" => "rgb!(230, 230, 250)", - "lavenderblush" => "rgb!(255, 240, 245)", - "lawngreen" => "rgb!(124, 252, 0)", - "lemonchiffon" => "rgb!(255, 250, 205)", - "lightblue" => "rgb!(173, 216, 230)", - "lightcoral" => "rgb!(240, 128, 128)", - "lightcyan" => "rgb!(224, 255, 255)", - "lightgoldenrodyellow" => "rgb!(250, 250, 210)", - "lightgray" => "rgb!(211, 211, 211)", - "lightgreen" => "rgb!(144, 238, 144)", - "lightgrey" => "rgb!(211, 211, 211)", - "lightpink" => "rgb!(255, 182, 193)", - "lightsalmon" => "rgb!(255, 160, 122)", - "lightseagreen" => "rgb!(32, 178, 170)", - "lightskyblue" => "rgb!(135, 206, 250)", - "lightslategray" => "rgb!(119, 136, 153)", - "lightslategrey" => "rgb!(119, 136, 153)", - "lightsteelblue" => "rgb!(176, 196, 222)", - "lightyellow" => "rgb!(255, 255, 224)", - "limegreen" => "rgb!(50, 205, 50)", - "linen" => "rgb!(250, 240, 230)", - "magenta" => "rgb!(255, 0, 255)", - "mediumaquamarine" => "rgb!(102, 205, 170)", - "mediumblue" => "rgb!(0, 0, 205)", - "mediumorchid" => "rgb!(186, 85, 211)", - "mediumpurple" => "rgb!(147, 112, 219)", - "mediumseagreen" => "rgb!(60, 179, 113)", - "mediumslateblue" => "rgb!(123, 104, 238)", - "mediumspringgreen" => "rgb!(0, 250, 154)", - "mediumturquoise" => "rgb!(72, 209, 204)", - "mediumvioletred" => "rgb!(199, 21, 133)", - "midnightblue" => "rgb!(25, 25, 112)", - "mintcream" => "rgb!(245, 255, 250)", - "mistyrose" => "rgb!(255, 228, 225)", - "moccasin" => "rgb!(255, 228, 181)", - "navajowhite" => "rgb!(255, 222, 173)", - "oldlace" => "rgb!(253, 245, 230)", - "olivedrab" => "rgb!(107, 142, 35)", - "orange" => "rgb!(255, 165, 0)", - "orangered" => "rgb!(255, 69, 0)", - "orchid" => "rgb!(218, 112, 214)", - "palegoldenrod" => "rgb!(238, 232, 170)", - "palegreen" => "rgb!(152, 251, 152)", - "paleturquoise" => "rgb!(175, 238, 238)", - "palevioletred" => "rgb!(219, 112, 147)", - "papayawhip" => "rgb!(255, 239, 213)", - "peachpuff" => "rgb!(255, 218, 185)", - "peru" => "rgb!(205, 133, 63)", - "pink" => "rgb!(255, 192, 203)", - "plum" => "rgb!(221, 160, 221)", - "powderblue" => "rgb!(176, 224, 230)", - "rebeccapurple" => "rgb!(102, 51, 153)", - "rosybrown" => "rgb!(188, 143, 143)", - "royalblue" => "rgb!(65, 105, 225)", - "saddlebrown" => "rgb!(139, 69, 19)", - "salmon" => "rgb!(250, 128, 114)", - "sandybrown" => "rgb!(244, 164, 96)", - "seagreen" => "rgb!(46, 139, 87)", - "seashell" => "rgb!(255, 245, 238)", - "sienna" => "rgb!(160, 82, 45)", - "skyblue" => "rgb!(135, 206, 235)", - "slateblue" => "rgb!(106, 90, 205)", - "slategray" => "rgb!(112, 128, 144)", - "slategrey" => "rgb!(112, 128, 144)", - "snow" => "rgb!(255, 250, 250)", - "springgreen" => "rgb!(0, 255, 127)", - "steelblue" => "rgb!(70, 130, 180)", - "tan" => "rgb!(210, 180, 140)", - "thistle" => "rgb!(216, 191, 216)", - "tomato" => "rgb!(255, 99, 71)", - "turquoise" => "rgb!(64, 224, 208)", - "violet" => "rgb!(238, 130, 238)", - "wheat" => "rgb!(245, 222, 179)", - "whitesmoke" => "rgb!(245, 245, 245)", - "yellowgreen" => "rgb!(154, 205, 50)", - - "transparent" => "Color::RGBA(RGBA { red: 0, green: 0, blue: 0, alpha: 0 })", - "currentcolor" => "Color::CurrentColor", + keyword -> Color = { + "black" => rgb!(0, 0, 0), + "silver" => rgb!(192, 192, 192), + "gray" => rgb!(128, 128, 128), + "white" => rgb!(255, 255, 255), + "maroon" => rgb!(128, 0, 0), + "red" => rgb!(255, 0, 0), + "purple" => rgb!(128, 0, 128), + "fuchsia" => rgb!(255, 0, 255), + "green" => rgb!(0, 128, 0), + "lime" => rgb!(0, 255, 0), + "olive" => rgb!(128, 128, 0), + "yellow" => rgb!(255, 255, 0), + "navy" => rgb!(0, 0, 128), + "blue" => rgb!(0, 0, 255), + "teal" => rgb!(0, 128, 128), + "aqua" => rgb!(0, 255, 255), + + "aliceblue" => rgb!(240, 248, 255), + "antiquewhite" => rgb!(250, 235, 215), + "aquamarine" => rgb!(127, 255, 212), + "azure" => rgb!(240, 255, 255), + "beige" => rgb!(245, 245, 220), + "bisque" => rgb!(255, 228, 196), + "blanchedalmond" => rgb!(255, 235, 205), + "blueviolet" => rgb!(138, 43, 226), + "brown" => rgb!(165, 42, 42), + "burlywood" => rgb!(222, 184, 135), + "cadetblue" => rgb!(95, 158, 160), + "chartreuse" => rgb!(127, 255, 0), + "chocolate" => rgb!(210, 105, 30), + "coral" => rgb!(255, 127, 80), + "cornflowerblue" => rgb!(100, 149, 237), + "cornsilk" => rgb!(255, 248, 220), + "crimson" => rgb!(220, 20, 60), + "cyan" => rgb!(0, 255, 255), + "darkblue" => rgb!(0, 0, 139), + "darkcyan" => rgb!(0, 139, 139), + "darkgoldenrod" => rgb!(184, 134, 11), + "darkgray" => rgb!(169, 169, 169), + "darkgreen" => rgb!(0, 100, 0), + "darkgrey" => rgb!(169, 169, 169), + "darkkhaki" => rgb!(189, 183, 107), + "darkmagenta" => rgb!(139, 0, 139), + "darkolivegreen" => rgb!(85, 107, 47), + "darkorange" => rgb!(255, 140, 0), + "darkorchid" => rgb!(153, 50, 204), + "darkred" => rgb!(139, 0, 0), + "darksalmon" => rgb!(233, 150, 122), + "darkseagreen" => rgb!(143, 188, 143), + "darkslateblue" => rgb!(72, 61, 139), + "darkslategray" => rgb!(47, 79, 79), + "darkslategrey" => rgb!(47, 79, 79), + "darkturquoise" => rgb!(0, 206, 209), + "darkviolet" => rgb!(148, 0, 211), + "deeppink" => rgb!(255, 20, 147), + "deepskyblue" => rgb!(0, 191, 255), + "dimgray" => rgb!(105, 105, 105), + "dimgrey" => rgb!(105, 105, 105), + "dodgerblue" => rgb!(30, 144, 255), + "firebrick" => rgb!(178, 34, 34), + "floralwhite" => rgb!(255, 250, 240), + "forestgreen" => rgb!(34, 139, 34), + "gainsboro" => rgb!(220, 220, 220), + "ghostwhite" => rgb!(248, 248, 255), + "gold" => rgb!(255, 215, 0), + "goldenrod" => rgb!(218, 165, 32), + "greenyellow" => rgb!(173, 255, 47), + "grey" => rgb!(128, 128, 128), + "honeydew" => rgb!(240, 255, 240), + "hotpink" => rgb!(255, 105, 180), + "indianred" => rgb!(205, 92, 92), + "indigo" => rgb!(75, 0, 130), + "ivory" => rgb!(255, 255, 240), + "khaki" => rgb!(240, 230, 140), + "lavender" => rgb!(230, 230, 250), + "lavenderblush" => rgb!(255, 240, 245), + "lawngreen" => rgb!(124, 252, 0), + "lemonchiffon" => rgb!(255, 250, 205), + "lightblue" => rgb!(173, 216, 230), + "lightcoral" => rgb!(240, 128, 128), + "lightcyan" => rgb!(224, 255, 255), + "lightgoldenrodyellow" => rgb!(250, 250, 210), + "lightgray" => rgb!(211, 211, 211), + "lightgreen" => rgb!(144, 238, 144), + "lightgrey" => rgb!(211, 211, 211), + "lightpink" => rgb!(255, 182, 193), + "lightsalmon" => rgb!(255, 160, 122), + "lightseagreen" => rgb!(32, 178, 170), + "lightskyblue" => rgb!(135, 206, 250), + "lightslategray" => rgb!(119, 136, 153), + "lightslategrey" => rgb!(119, 136, 153), + "lightsteelblue" => rgb!(176, 196, 222), + "lightyellow" => rgb!(255, 255, 224), + "limegreen" => rgb!(50, 205, 50), + "linen" => rgb!(250, 240, 230), + "magenta" => rgb!(255, 0, 255), + "mediumaquamarine" => rgb!(102, 205, 170), + "mediumblue" => rgb!(0, 0, 205), + "mediumorchid" => rgb!(186, 85, 211), + "mediumpurple" => rgb!(147, 112, 219), + "mediumseagreen" => rgb!(60, 179, 113), + "mediumslateblue" => rgb!(123, 104, 238), + "mediumspringgreen" => rgb!(0, 250, 154), + "mediumturquoise" => rgb!(72, 209, 204), + "mediumvioletred" => rgb!(199, 21, 133), + "midnightblue" => rgb!(25, 25, 112), + "mintcream" => rgb!(245, 255, 250), + "mistyrose" => rgb!(255, 228, 225), + "moccasin" => rgb!(255, 228, 181), + "navajowhite" => rgb!(255, 222, 173), + "oldlace" => rgb!(253, 245, 230), + "olivedrab" => rgb!(107, 142, 35), + "orange" => rgb!(255, 165, 0), + "orangered" => rgb!(255, 69, 0), + "orchid" => rgb!(218, 112, 214), + "palegoldenrod" => rgb!(238, 232, 170), + "palegreen" => rgb!(152, 251, 152), + "paleturquoise" => rgb!(175, 238, 238), + "palevioletred" => rgb!(219, 112, 147), + "papayawhip" => rgb!(255, 239, 213), + "peachpuff" => rgb!(255, 218, 185), + "peru" => rgb!(205, 133, 63), + "pink" => rgb!(255, 192, 203), + "plum" => rgb!(221, 160, 221), + "powderblue" => rgb!(176, 224, 230), + "rebeccapurple" => rgb!(102, 51, 153), + "rosybrown" => rgb!(188, 143, 143), + "royalblue" => rgb!(65, 105, 225), + "saddlebrown" => rgb!(139, 69, 19), + "salmon" => rgb!(250, 128, 114), + "sandybrown" => rgb!(244, 164, 96), + "seagreen" => rgb!(46, 139, 87), + "seashell" => rgb!(255, 245, 238), + "sienna" => rgb!(160, 82, 45), + "skyblue" => rgb!(135, 206, 235), + "slateblue" => rgb!(106, 90, 205), + "slategray" => rgb!(112, 128, 144), + "slategrey" => rgb!(112, 128, 144), + "snow" => rgb!(255, 250, 250), + "springgreen" => rgb!(0, 255, 127), + "steelblue" => rgb!(70, 130, 180), + "tan" => rgb!(210, 180, 140), + "thistle" => rgb!(216, 191, 216), + "tomato" => rgb!(255, 99, 71), + "turquoise" => rgb!(64, 224, 208), + "violet" => rgb!(238, 130, 238), + "wheat" => rgb!(245, 222, 179), + "whitesmoke" => rgb!(245, 245, 245), + "yellowgreen" => rgb!(154, 205, 50), + + "transparent" => Color::RGBA(RGBA { red: 0, green: 0, blue: 0, alpha: 0 }), + "currentcolor" => Color::CurrentColor, } } - KEYWORDS::get(ident).cloned().ok_or(()) + keyword(ident).cloned().ok_or(()) } diff --git a/src/lib.rs b/src/lib.rs index 08e7bd71..c438b015 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,13 +70,16 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser) #[macro_use] extern crate cssparser_macros; #[macro_use] extern crate matches; -extern crate phf; +#[macro_use] extern crate procedural_masquerade; +#[doc(hidden)] pub extern crate phf as _internal__phf; #[cfg(test)] extern crate encoding_rs; #[cfg(test)] extern crate tempdir; #[cfg(test)] extern crate rustc_serialize; #[cfg(feature = "serde")] extern crate serde; #[cfg(feature = "heapsize")] #[macro_use] extern crate heapsize; +pub use cssparser_macros::*; + pub use tokenizer::{Token, NumericValue, PercentageValue, SourceLocation}; pub use rules_and_declarations::{parse_important}; pub use rules_and_declarations::{DeclarationParser, DeclarationListParser, parse_one_declaration}; @@ -89,199 +92,17 @@ pub use serializer::{ToCss, CssStringWriter, serialize_identifier, serialize_str pub use parser::{Parser, Delimiter, Delimiters, SourcePosition}; pub use unicode_range::UnicodeRange; -/// Expands to an expression equivalent to a `match` with string patterns, -/// but matching is case-insensitive in the ASCII range. -/// -/// Requirements: -/// -/// * The `cssparser_macros` crate must also be imported at the crate root -/// * The patterns must not contain ASCII upper case letters. (They must be already be lower-cased.) -/// -/// # Example -/// -/// ```rust -/// #[macro_use] extern crate cssparser; -/// #[macro_use] extern crate cssparser_macros; -/// -/// # fn main() {} // Make doctest not wrap everythig in its own main -/// # fn dummy(function_name: &String) { let _ = -/// match_ignore_ascii_case! { &function_name, -/// "rgb" => parse_rgb(..), -/// "rgba" => parse_rgba(..), -/// "hsl" => parse_hsl(..), -/// "hsla" => parse_hsla(..), -/// _ => Err("unknown function") -/// } -/// # ;} -/// # use std::ops::RangeFull; -/// # fn parse_rgb(_: RangeFull) -> Result<(), &'static str> { Err("") } -/// # fn parse_rgba(_: RangeFull) -> Result<(), &'static str> { Err("") } -/// # fn parse_hsl(_: RangeFull) -> Result<(), &'static str> { Err("") } -/// # fn parse_hsla(_: RangeFull) -> Result<(), &'static str> { Err("") } -/// ``` -#[macro_export] -macro_rules! match_ignore_ascii_case { - // parse the last case plus the fallback - (@inner $value:expr, ($string:expr => $result:expr, _ => $fallback:expr) -> ($($parsed:tt)*) ) => { - match_ignore_ascii_case!(@inner $value, () -> ($($parsed)* ($string => $result)) $fallback) - }; - - // parse a case (not the last one) - (@inner $value:expr, ($string:expr => $result:expr, $($rest:tt)*) -> ($($parsed:tt)*) ) => { - match_ignore_ascii_case!(@inner $value, ($($rest)*) -> ($($parsed)* ($string => $result))) - }; - - // finished parsing - (@inner $value:expr, () -> ($(($string:expr => $result:expr))*) $fallback:expr ) => { - { - #[derive(cssparser__assert_ascii_lowercase)] - #[cssparser__assert_ascii_lowercase__data($(string = $string),+)] - #[allow(dead_code)] - struct Dummy; - - _cssparser_internal__to_lowercase!($value => lowercase, $($string),+); - match lowercase { - $( - Some($string) => $result, - )+ - _ => $fallback - } - } - }; - - // entry point, start parsing - ( $value:expr, $($rest:tt)* ) => { - match_ignore_ascii_case!(@inner $value, ($($rest)*) -> ()) - }; -} - -/// Define a placeholder type `$Name` -/// with a method `fn get(input: &str) -> Option<&'static $ValueType>`. -/// -/// This method uses finds a match for the input string -/// in a [`phf` map](https://github.com/sfackler/rust-phf). -/// Matching is case-insensitive in the ASCII range. -/// -/// Requirements: -/// -/// * The `phf` and `cssparser_macros` crates must also be imported at the crate root -/// * The values must be given a strings that contain Rust syntax for a constant expression. -/// -/// ## Example: -/// -/// ```rust -/// extern crate phf; -/// #[macro_use] extern crate cssparser; -/// #[macro_use] extern crate cssparser_macros; -/// -/// # fn main() {} // Make doctest not wrap everythig in its own main -/// -/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> { -/// ascii_case_insensitive_phf_map! { -/// KEYWORDS: Map<(u8, u8, u8)> = { -/// "red" => "(255, 0, 0)", -/// "green" => "(0, 255, 0)", -/// "blue" => "(0, 0, 255)", -/// } -/// } -/// KEYWORDS::get(input).cloned() -/// } -#[macro_export] -macro_rules! ascii_case_insensitive_phf_map { - ($Name: ident : Map<$ValueType: ty> = { - $( $key: expr => $value: expr, )* - }) => { - #[derive(cssparser__phf_map)] - #[cssparser__phf_map__kv_pairs( - $( - key = $key, - value = $value - ),+ - )] - struct $Name($ValueType); - - impl $Name { - #[inline] - fn get(input: &str) -> Option<&'static $ValueType> { - _cssparser_internal__to_lowercase!(input => lowercase, $($key),+); - lowercase.and_then(|string| $Name::map().get(string)) - } - } - } -} - -/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. -/// -/// **This macro is not part of the public API. It can change or be removed between any versions.** -/// -/// * Check at compile-time that none of the `$string`s contain ASCII uppercase letters -/// * Define a local variable named `$output` to the result of calling `_internal__to_lowercase` -/// with a stack-allocated buffer as long as the longest `$string`. -#[macro_export] -#[doc(hidden)] -macro_rules! _cssparser_internal__to_lowercase { - ($input: expr => $output: ident, $($string: expr),+) => { - #[derive(cssparser__max_len)] - #[cssparser__max_len__data($(string = $string),+)] - #[allow(dead_code)] - struct Dummy2; +// For macros +#[doc(hidden)] pub use macros::_internal__to_lowercase; - // mem::uninitialized() is ok because `buffer` is only used in `_internal__to_lowercase`, - // which initializes with `copy_from_slice` the part of the buffer it uses, - // before it uses it. - #[allow(unsafe_code)] - // MAX_LENGTH is generated by cssparser__max_len - let mut buffer: [u8; MAX_LENGTH] = unsafe { - ::std::mem::uninitialized() - }; - let input: &str = $input; - let $output = $crate::_internal__to_lowercase(&mut buffer, input); - } -} +// For macros when used in this crate. Unsure how $crate works with procedural-masquerade. +mod cssparser { pub use _internal__phf; } - -/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. -/// -/// **This function is not part of the public API. It can change or be removed between any verisons.** -/// -/// Return `input`, lower-cased, unless larger than `buffer` -/// which is used temporary space for lower-casing a copy of `input` if necessary. -#[doc(hidden)] -#[allow(non_snake_case)] -pub fn _internal__to_lowercase<'a>(buffer: &'a mut [u8], input: &'a str) -> Option<&'a str> { - if let Some(buffer) = buffer.get_mut(..input.len()) { - if let Some(first_uppercase) = input.bytes().position(|byte| matches!(byte, b'A'...b'Z')) { - buffer.copy_from_slice(input.as_bytes()); - std::ascii::AsciiExt::make_ascii_lowercase(&mut buffer[first_uppercase..]); - // `buffer` was initialized to a copy of `input` (which is &str so well-formed UTF-8) - // then lowercased (which preserves UTF-8 well-formedness) - unsafe { - Some(::std::str::from_utf8_unchecked(buffer)) - } - } else { - // Input is already lower-case - Some(input) - } - } else { - // Input is longer than buffer, which has the length of the longest expected string: - // none of the expected strings would match. - None - } -} +#[macro_use] +mod macros; mod rules_and_declarations; -#[cfg(feature = "dummy_match_byte")] -macro_rules! match_byte { - ($value:expr, $($rest:tt)* ) => { - match $value { - $( - $rest - )+ - } - }; -} - #[cfg(feature = "dummy_match_byte")] mod tokenizer; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..6b75d46b --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,163 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +/// See docs of the `procedural-masquerade` crate. +define_invoke_proc_macro!(cssparser_internal__invoke_proc_macro); + +/// Expands to a `match` expression with string patterns, +/// matching case-insensitively in the ASCII range. +/// +/// The patterns must not contain ASCII upper case letters. (They must be already be lower-cased.) +/// +/// # Example +/// +/// ```rust +/// #[macro_use] extern crate cssparser; +/// +/// # fn main() {} // Make doctest not wrap everythig in its own main +/// # fn dummy(function_name: &String) { let _ = +/// match_ignore_ascii_case! { &function_name, +/// "rgb" => parse_rgb(..), +/// "rgba" => parse_rgba(..), +/// "hsl" => parse_hsl(..), +/// "hsla" => parse_hsla(..), +/// name @ _ => Err(format!("unknown function: {}", name)) +/// } +/// # ;} +/// # use std::ops::RangeFull; +/// # fn parse_rgb(_: RangeFull) -> Result<(), String> { Ok(()) } +/// # fn parse_rgba(_: RangeFull) -> Result<(), String> { Ok(()) } +/// # fn parse_hsl(_: RangeFull) -> Result<(), String> { Ok(()) } +/// # fn parse_hsla(_: RangeFull) -> Result<(), String> { Ok(()) } +/// ``` +#[macro_export] +macro_rules! match_ignore_ascii_case { + ( $input:expr, $( $match_body:tt )* ) => { + { + cssparser_internal__invoke_proc_macro! { + cssparser_internal__assert_ascii_lowercase__max_len!( $( $match_body )* ) + } + + { + // MAX_LENGTH is generated by cssparser_internal__assert_ascii_lowercase__max_len + 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") { + $( $match_body )* + } + } + } + }; +} + +/// 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 +/// #[macro_use] extern crate cssparser; +/// +/// # fn main() {} // Make doctest not wrap everything in its own main +/// +/// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> { +/// ascii_case_insensitive_phf_map! { +/// keyword -> (u8, u8, u8) = { +/// "red" => (255, 0, 0), +/// "green" => (0, 255, 0), +/// "blue" => (0, 0, 255), +/// } +/// } +/// keyword(input).cloned() +/// } +#[macro_export] +macro_rules! ascii_case_insensitive_phf_map { + ($name: ident -> $ValueType: ty = { $( $key: expr => $value: expr ),* }) => { + ascii_case_insensitive_phf_map!($name -> $ValueType = { $( $key => $value, )* }) + }; + ($name: ident -> $ValueType: ty = { $( $key: expr => $value: expr, )* }) => { + fn $name(input: &str) -> Option<&'static $ValueType> { + cssparser_internal__invoke_proc_macro! { + cssparser_internal__phf_map!( ($ValueType) $( $key ($value) )+ ) + } + + { + cssparser_internal__invoke_proc_macro! { + cssparser_internal__max_len!( $( $key )+ ) + } + // MAX_LENGTH is generated by cssparser_internal__max_len + cssparser_internal__to_lowercase!(input, MAX_LENGTH => lowercase); + lowercase.and_then(|s| MAP.get(s)) + } + } + } +} + +/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. +/// +/// **This macro is not part of the public API. It can change or be removed between any versions.** +/// +/// Define a local variable named `$output` +/// and assign it the result of calling `_internal__to_lowercase` +/// with a stack-allocated buffer of length `$BUFFER_SIZE`. +#[macro_export] +#[doc(hidden)] +macro_rules! cssparser_internal__to_lowercase { + ($input: expr, $BUFFER_SIZE: expr => $output: ident) => { + // mem::uninitialized() is ok because `buffer` is only used in `_internal__to_lowercase`, + // which initializes with `copy_from_slice` the part of the buffer it uses, + // before it uses it. + #[allow(unsafe_code)] + let mut buffer: [u8; $BUFFER_SIZE] = unsafe { + ::std::mem::uninitialized() + }; + let input: &str = $input; + let $output = $crate::_internal__to_lowercase(&mut buffer, input); + } +} + +/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. +/// +/// **This function is not part of the public API. It can change or be removed between any verisons.** +/// +/// If `input` is larger than buffer, return `None`. +/// Otherwise, return `input` ASCII-lowercased, using `buffer` as temporary space if necessary. +#[doc(hidden)] +#[allow(non_snake_case)] +pub fn _internal__to_lowercase<'a>(buffer: &'a mut [u8], input: &'a str) -> Option<&'a str> { + if let Some(buffer) = buffer.get_mut(..input.len()) { + if let Some(first_uppercase) = input.bytes().position(|byte| matches!(byte, b'A'...b'Z')) { + buffer.copy_from_slice(input.as_bytes()); + ::std::ascii::AsciiExt::make_ascii_lowercase(&mut buffer[first_uppercase..]); + // `buffer` was initialized to a copy of `input` (which is &str so well-formed UTF-8) + // then lowercased (which preserves UTF-8 well-formedness) + unsafe { + Some(::std::str::from_utf8_unchecked(buffer)) + } + } else { + // Input is already lower-case + Some(input) + } + } else { + // Input is longer than buffer, which has the length of the longest expected string: + // none of the expected strings would match. + None + } +} + +#[cfg(feature = "dummy_match_byte")] +macro_rules! match_byte { + ($value:expr, $($rest:tt)* ) => { + match $value { + $( + $rest + )+ + } + }; +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs deleted file mode 100644 index 6799e549..00000000 --- a/src/macros/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -pub mod match_byte; diff --git a/src/tests.rs b/src/tests.rs index b5063138..84199438 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -817,3 +817,29 @@ fn one_component_value_to_json(token: Token, input: &mut Parser) -> Json { Token::CloseCurlyBracket => JArray!["error", "}"], } } + +/// A previous version of procedural-masquerade had a bug where it +/// would normalize consecutive whitespace to a single space, +/// including in string literals. +#[test] +fn procedural_masquerade_whitespace() { + ascii_case_insensitive_phf_map! { + map -> () = { + " \t\n" => () + } + } + assert_eq!(map(" \t\n"), Some(&())); + assert_eq!(map(" "), None); + + match_ignore_ascii_case! { " \t\n", + " " => panic!("1"), + " \t\n" => {}, + _ => panic!("2"), + } + + match_ignore_ascii_case! { " ", + " \t\n" => panic!("3"), + " " => {}, + _ => panic!("4"), + } +}