From 63c2044aa1bd7a9a652e5846b873eb3128aa9ba4 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 24 Feb 2017 21:59:46 +0100 Subject: [PATCH 01/14] Update syn and encoding_rs --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 30f13090..ca28b548 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ doctest = false [dev-dependencies] rustc-serialize = "0.3" tempdir = "0.3" -encoding_rs = "0.3.2" +encoding_rs = "0.5" [dependencies] heapsize = {version = "0.3", optional = true} @@ -28,7 +28,7 @@ matches = "0.1" serde = {version = "0.9", optional = true} [build-dependencies] -syn = { version = "0.10.6", features = ["full", "visit"]} +syn = "0.11" quote = "0.3" [features] From 0592beaf385712aadf9642d5c558001b0fe12f31 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 24 Feb 2017 22:02:14 +0100 Subject: [PATCH 02/14] Use docs.rs for docs. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ca28b548..167e9969 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ version = "0.10.0" authors = [ "Simon Sapin " ] description = "Rust implementation of CSS Syntax Level 3" -documentation = "http://servo.github.io/rust-cssparser/cssparser/index.html" +documentation = "https://docs.rs/cssparser/" repository = "https://github.com/servo/rust-cssparser" readme = "README.md" keywords = ["css", "syntax", "parser"] From 808922abbc7b410f76804e47a5f485eef38d7fa9 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 24 Feb 2017 22:56:07 +0100 Subject: [PATCH 03/14] Use a proc-macro to optimize match_ignore_ascii_case Previously, the compiler would emit many `eq_ignore_ascii_case` calls, leading to code bloat and probably some slowness. Now, we pre-lowercase the input in a stack-allocated buffer then match exact strings. Hopefully, the optimizer can turn this into a static table and a loop. --- Cargo.toml | 4 ++++ macros/Cargo.toml | 16 ++++++++++++++++ macros/lib.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 35 ++++++++++++++++++++++++++++++++--- 4 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 macros/Cargo.toml create mode 100644 macros/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 167e9969..88c585ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ tempdir = "0.3" encoding_rs = "0.5" [dependencies] +cssparser-macros = {path = "./macros", version = "0.1"} heapsize = {version = "0.3", optional = true} matches = "0.1" serde = {version = "0.9", optional = true} @@ -34,3 +35,6 @@ quote = "0.3" [features] bench = [] dummy_match_byte = [] + +[workspace] +members = [".", "./macros"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 00000000..173d2021 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cssparser-macros" +version = "0.1.0" +authors = ["Simon Sapin "] +description = "Procedural macros for cssparser" +documentation = "https://docs.rs/cssparser-macros/" +repository = "https://github.com/servo/rust-cssparser" +license = "MPL-2.0" + +[lib] +path = "lib.rs" +proc-macro = true + +[dependencies] +syn = "0.11" +quote = "0.3" diff --git a/macros/lib.rs b/macros/lib.rs new file mode 100644 index 00000000..55e93bf6 --- /dev/null +++ b/macros/lib.rs @@ -0,0 +1,47 @@ +/* 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/. */ + +extern crate proc_macro; +#[macro_use] extern crate quote; +extern crate syn; + +use std::ascii::AsciiExt; + +#[proc_macro_derive(cssparser__match_ignore_ascii_case__derive, + attributes(cssparser__match_ignore_ascii_case__data))] +pub fn expand_token_stream(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input(&input.to_string()).unwrap(); + + let max_length; + + match input.attrs[0].value { + syn::MetaItem::List(ref ident, ref nested) + if ident == "cssparser__match_ignore_ascii_case__data" => { + let lengths = nested.iter().map(|sub_attr| match *sub_attr { + syn::NestedMetaItem::MetaItem( + syn::MetaItem::NameValue(ref ident, syn::Lit::Str(ref string, _)) + ) + if ident == "string" => { + assert_eq!(*string, string.to_ascii_lowercase(), + "the expected strings must be given in ASCII lowercase"); + string.len() + } + _ => { + panic!("expected a `string = \"…\" parameter to the attribute, got {:?}", sub_attr) + } + }); + + max_length = lengths.max().expect("expected at least one string") + } + _ => { + panic!("expected a cssparser_match_ignore_ascii_case_data attribute") + } + } + + let tokens = quote! { + const MAX_LENGTH: usize = #max_length; + }; + + tokens.as_str().parse().unwrap() +} diff --git a/src/lib.rs b/src/lib.rs index 905f5066..6964352b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser) #![recursion_limit="200"] // For color::parse_color_keyword +#[macro_use] extern crate cssparser_macros; #[macro_use] extern crate matches; #[cfg(test)] extern crate encoding_rs; #[cfg(test)] extern crate tempdir; @@ -123,10 +124,16 @@ macro_rules! match_ignore_ascii_case { // finished parsing (@inner $value:expr, () -> ($(($string:expr => $result:expr))*) $fallback:expr ) => { { - use std::ascii::AsciiExt; - match &$value[..] { + #[derive(cssparser__match_ignore_ascii_case__derive)] + #[cssparser__match_ignore_ascii_case__data($(string = $string),+)] + #[allow(dead_code)] + struct Dummy; + + // MAX_LENGTH is generated by cssparser_MatchIgnoreAsciiCase_internal + let mut buffer: [u8; MAX_LENGTH] = unsafe { ::std::mem::uninitialized() }; + match $crate::_match_ignore_ascii_case__to_lowercase(&mut buffer, &$value[..]) { $( - s if s.eq_ignore_ascii_case($string) => $result, + Some($string) => $result, )+ _ => $fallback } @@ -139,6 +146,28 @@ macro_rules! match_ignore_ascii_case { }; } +/// Implementation detail of the match_ignore_ascii_case! macro. +#[doc(hidden)] +#[allow(non_snake_case)] +pub fn _match_ignore_ascii_case__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..]); + 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 + } +} + mod rules_and_declarations; #[cfg(feature = "dummy_match_byte")] From 0192030d8d4ef19fe43c8bb3f28df0f5f372a72f Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 12:56:06 +0100 Subject: [PATCH 04/14] Add a ascii_case_insensitive_phf_map macro, use it for color keywords. Fix #115. --- Cargo.toml | 1 + macros/Cargo.toml | 4 +- macros/lib.rs | 100 +++++++++++---- src/color.rs | 320 ++++++++++++++++++++++++---------------------- src/lib.rs | 52 ++++++-- 5 files changed, 288 insertions(+), 189 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88c585ce..afa65899 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ encoding_rs = "0.5" cssparser-macros = {path = "./macros", version = "0.1"} heapsize = {version = "0.3", optional = true} matches = "0.1" +phf = "0.7" serde = {version = "0.9", optional = true} [build-dependencies] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 173d2021..2ca733c4 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,5 +12,7 @@ path = "lib.rs" proc-macro = true [dependencies] -syn = "0.11" +phf_codegen = "0.7" quote = "0.3" +syn = "0.11" + diff --git a/macros/lib.rs b/macros/lib.rs index 55e93bf6..790d583f 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -2,46 +2,96 @@ * 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/. */ +extern crate phf_codegen; extern crate proc_macro; #[macro_use] extern crate quote; extern crate syn; use std::ascii::AsciiExt; -#[proc_macro_derive(cssparser__match_ignore_ascii_case__derive, +#[proc_macro_derive(cssparser__match_ignore_ascii_case__max_len, attributes(cssparser__match_ignore_ascii_case__data))] -pub fn expand_token_stream(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +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__match_ignore_ascii_case__data"); - let max_length; + let lengths = data.iter().map(|sub_attr| { + let string = sub_attr_value(sub_attr, "string"); + assert_eq!(*string, string.to_ascii_lowercase(), + "the expected strings must be given in ASCII lowercase"); + string.len() + }); + let max_length = lengths.max().expect("expected at least one string"); - match input.attrs[0].value { - syn::MetaItem::List(ref ident, ref nested) - if ident == "cssparser__match_ignore_ascii_case__data" => { - let lengths = nested.iter().map(|sub_attr| match *sub_attr { - syn::NestedMetaItem::MetaItem( - syn::MetaItem::NameValue(ref ident, syn::Lit::Str(ref string, _)) - ) - if ident == "string" => { - assert_eq!(*string, string.to_ascii_lowercase(), - "the expected strings must be given in ASCII lowercase"); - string.len() - } - _ => { - panic!("expected a `string = \"…\" parameter to the attribute, got {:?}", sub_attr) - } - }); - - max_length = lengths.max().expect("expected at least one string") - } - _ => { - panic!("expected a cssparser_match_ignore_ascii_case_data attribute") + let tokens = quote! { + const MAX_LENGTH: usize = #max_length; + }; + + tokens.as_str().parse().unwrap() +} + + +#[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 kv_pairs = list_attr(&input, "cssparser__phf_map__kv_pairs"); + + let mut map = phf_codegen::Map::new(); + for chunk in kv_pairs.chunks(2) { + let key = sub_attr_value(&chunk[0], "key"); + let value = sub_attr_value(&chunk[1], "value"); + map.entry(key, value); } + 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! { - const MAX_LENGTH: usize = #max_length; + impl #name { + #[inline] + fn map() -> &'static ::phf::Map<&'static str, #value_type> { + static MAP: ::phf::Map<&'static str, #value_type> = #initializer_tokens; + &MAP + } + } }; tokens.as_str().parse().unwrap() } + +fn list_attr<'a>(input: &'a syn::DeriveInput, expected_name: &str) -> &'a [syn::NestedMetaItem] { + match input.attrs[0].value { + syn::MetaItem::List(ref name, ref nested) if name == expected_name => { + nested + } + _ => { + panic!("expected a {} attribute", expected_name) + } + } +} + +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) + } + } +} diff --git a/src/color.rs b/src/color.rs index 6f3d886b..85ae102a 100644 --- a/src/color.rs +++ b/src/color.rs @@ -169,161 +169,173 @@ fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Result { /// (For example, the value of an `Ident` token is fine.) #[inline] pub fn parse_color_keyword(ident: &str) -> Result { - match_ignore_ascii_case! { ident, - "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" => rgba(0, 0, 0, 0), - "currentcolor" => Ok(Color::CurrentColor), - _ => Err(()) + macro_rules! rgb { + ($red: expr, $green: expr, $blue: expr) => { + Color::RGBA(RGBA { + red: $red, + green: $green, + blue: $blue, + alpha: 255, + }) + } + } + 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", + } } + KEYWORDS::get(ident).cloned().ok_or(()) } diff --git a/src/lib.rs b/src/lib.rs index 6964352b..7cd366d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,7 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser) #[macro_use] extern crate cssparser_macros; #[macro_use] extern crate matches; +extern crate phf; #[cfg(test)] extern crate encoding_rs; #[cfg(test)] extern crate tempdir; #[cfg(test)] extern crate rustc_serialize; @@ -124,14 +125,8 @@ macro_rules! match_ignore_ascii_case { // finished parsing (@inner $value:expr, () -> ($(($string:expr => $result:expr))*) $fallback:expr ) => { { - #[derive(cssparser__match_ignore_ascii_case__derive)] - #[cssparser__match_ignore_ascii_case__data($(string = $string),+)] - #[allow(dead_code)] - struct Dummy; - - // MAX_LENGTH is generated by cssparser_MatchIgnoreAsciiCase_internal - let mut buffer: [u8; MAX_LENGTH] = unsafe { ::std::mem::uninitialized() }; - match $crate::_match_ignore_ascii_case__to_lowercase(&mut buffer, &$value[..]) { + _cssparser_internal__max_len!(&$value[..] => lowercase, $($string),+); + match lowercase { $( Some($string) => $result, )+ @@ -146,7 +141,46 @@ macro_rules! match_ignore_ascii_case { }; } -/// Implementation detail of the match_ignore_ascii_case! macro. +#[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__max_len!(input => lowercase, $($key),+); + lowercase.and_then(|string| $Name::map().get(string)) + } + } + } +} + +#[macro_export] +macro_rules! _cssparser_internal__max_len { + ($input: expr => $output: ident, $($string: expr),+) => { + #[derive(cssparser__match_ignore_ascii_case__max_len)] + #[cssparser__match_ignore_ascii_case__data($(string = $string),+)] + #[allow(dead_code)] + struct Dummy; + + // MAX_LENGTH is generated by cssparser__match_ignore_ascii_case__max_len + let mut buffer: [u8; MAX_LENGTH] = unsafe { ::std::mem::uninitialized() }; + let $output = $crate::_match_ignore_ascii_case__to_lowercase(&mut buffer, $input); + } +} + + +/// Implementation detail of macros. #[doc(hidden)] #[allow(non_snake_case)] pub fn _match_ignore_ascii_case__to_lowercase<'a>(buffer: &'a mut [u8], input: &'a str) -> Option<&'a str> { From 5a177507887022a2cc3cbce170deee5dfec94df1 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 12:59:15 +0100 Subject: [PATCH 05/14] Add comments to explain new `unsafe` code. --- src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7cd366d4..377d2965 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,7 +174,13 @@ macro_rules! _cssparser_internal__max_len { struct Dummy; // MAX_LENGTH is generated by cssparser__match_ignore_ascii_case__max_len - let mut buffer: [u8; MAX_LENGTH] = unsafe { ::std::mem::uninitialized() }; + let mut buffer: [u8; MAX_LENGTH] = + // `buffer` is only used in `_match_ignore_ascii_case__to_lowercase`, + // which initializes with `copy_from_slice` the part of the buffer it uses, + // before it uses it. + unsafe { + ::std::mem::uninitialized() + }; let $output = $crate::_match_ignore_ascii_case__to_lowercase(&mut buffer, $input); } } @@ -188,6 +194,8 @@ pub fn _match_ignore_ascii_case__to_lowercase<'a>(buffer: &'a mut [u8], input: & 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)) } From ea6843d8d50b5319a628285690b45ae38e91d851 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 13:31:16 +0100 Subject: [PATCH 06/14] =?UTF-8?q?Don=E2=80=99t=20borrow=20$input=20in=20ma?= =?UTF-8?q?tch=5Fignore=5Fascii=5Fcase!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let users pass a `&foo` borrow. `&Cow` can auto-deref to `&str`. --- src/lib.rs | 5 +---- src/nth.rs | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 377d2965..039f531c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,9 +106,6 @@ match_ignore_ascii_case! { string, } ``` -The macro also takes a slice of the value, -so that a `String` or `CowString` could be passed directly instead of a `&str`. - */ #[macro_export] macro_rules! match_ignore_ascii_case { @@ -125,7 +122,7 @@ macro_rules! match_ignore_ascii_case { // finished parsing (@inner $value:expr, () -> ($(($string:expr => $result:expr))*) $fallback:expr ) => { { - _cssparser_internal__max_len!(&$value[..] => lowercase, $($string),+); + _cssparser_internal__max_len!($value => lowercase, $($string),+); match lowercase { $( Some($string) => $result, diff --git a/src/nth.rs b/src/nth.rs index 675f4ba8..ec735bc2 100644 --- a/src/nth.rs +++ b/src/nth.rs @@ -16,14 +16,14 @@ pub fn parse_nth(input: &mut Parser) -> Result<(i32, i32), ()> { Token::Number(value) => Ok((0, try!(value.int_value.ok_or(())) as i32)), Token::Dimension(value, unit) => { let a = try!(value.int_value.ok_or(())) as i32; - match_ignore_ascii_case! { unit, + match_ignore_ascii_case! { &unit, "n" => parse_b(input, a), "n-" => parse_signless_b(input, a, -1), _ => Ok((a, try!(parse_n_dash_digits(&*unit)))) } } Token::Ident(value) => { - match_ignore_ascii_case! { value, + match_ignore_ascii_case! { &value, "even" => Ok((2, 0)), "odd" => Ok((2, 1)), "n" => parse_b(input, 1), @@ -39,7 +39,7 @@ pub fn parse_nth(input: &mut Parser) -> Result<(i32, i32), ()> { } Token::Delim('+') => match try!(input.next_including_whitespace()) { Token::Ident(value) => { - match_ignore_ascii_case! { value, + match_ignore_ascii_case! { &value, "n" => parse_b(input, 1), "n-" => parse_signless_b(input, 1, -1), _ => Ok((1, try!(parse_n_dash_digits(&*value)))) From 983529f85f6153799be99cf77e32429b80e196aa Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 13:44:02 +0100 Subject: [PATCH 07/14] Document macros. --- Cargo.toml | 3 -- macros/lib.rs | 12 ++++++- src/lib.rs | 92 ++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 84 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index afa65899..c42d7e3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,6 @@ build = "build.rs" exclude = ["src/css-parsing-tests"] -[lib] -doctest = false - [dev-dependencies] rustc-serialize = "0.3" tempdir = "0.3" diff --git a/macros/lib.rs b/macros/lib.rs index 790d583f..260f32d3 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -9,6 +9,9 @@ extern crate syn; use std::ascii::AsciiExt; +/// Find a `#[cssparser__match_ignore_ascii_case__data(string = "…", string = "…")]` attribute, +/// panic if any string contains ASCII uppercase letters, +/// emit a `MAX_LENGTH` constant with the length of the longest string. #[proc_macro_derive(cssparser__match_ignore_ascii_case__max_len, attributes(cssparser__match_ignore_ascii_case__data))] pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -30,7 +33,10 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 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 = "…")]`. #[proc_macro_derive(cssparser__phf_map, attributes(cssparser__phf_map__kv_pairs))] pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -70,6 +76,8 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { tokens.as_str().parse().unwrap() } +/// 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] { match input.attrs[0].value { syn::MetaItem::List(ref name, ref nested) if name == expected_name => { @@ -81,6 +89,8 @@ fn list_attr<'a>(input: &'a syn::DeriveInput, expected_name: &str) -> &'a [syn:: } } +/// 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( diff --git a/src/lib.rs b/src/lib.rs index 039f531c..121bbab0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,24 +89,36 @@ pub use serializer::{ToCss, CssStringWriter, serialize_identifier, serialize_str pub use parser::{Parser, Delimiter, Delimiters, SourcePosition}; pub use unicode_range::UnicodeRange; - -/** - -This macro is equivalent to a `match` expression on an `&str` value, -but matching is case-insensitive in the ASCII range. - -Usage example: - -```{rust,ignore} -match_ignore_ascii_case! { string, - "foo" => Some(Foo), - "bar" => Some(Bar), - "baz" => Some(Baz), - _ => None -} -``` - -*/ +/// 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 @@ -138,6 +150,38 @@ macro_rules! match_ignore_ascii_case { }; } +/// 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 keys must not contain ASCII upper case letters. (They must be already be lower-cased.) +/// * 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> = { @@ -162,7 +206,14 @@ macro_rules! ascii_case_insensitive_phf_map { } } +/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. +/// +/// * 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 `_match_ignore_ascii_case__to_lowercase` +/// with a stack-allocated buffer as long as the longest `$string`. #[macro_export] +#[doc(hidden)] macro_rules! _cssparser_internal__max_len { ($input: expr => $output: ident, $($string: expr),+) => { #[derive(cssparser__match_ignore_ascii_case__max_len)] @@ -183,7 +234,10 @@ macro_rules! _cssparser_internal__max_len { } -/// Implementation detail of macros. +/// Implementation detail of match_ignore_ascii_case! and ascii_case_insensitive_phf_map! macros. +/// +/// 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 _match_ignore_ascii_case__to_lowercase<'a>(buffer: &'a mut [u8], input: &'a str) -> Option<&'a str> { From b0c427d8b8e4424fc20834ba4cfe9cc9b5e51782 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 14:04:25 +0100 Subject: [PATCH 08/14] Remove the already-lowercase requirement in ascii_case_insensitive_phf_map! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can do the lower-casing at compile time in a proc-macro. Note that `match_ignore_ascii_case!` still has that requirement, since it’s `macro_rules!` that generates a `match` expression. --- macros/lib.rs | 45 ++++++++++++++++++++++++++++++--------------- src/lib.rs | 12 ++++++++---- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index 260f32d3..e0165242 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -9,21 +9,33 @@ extern crate syn; use std::ascii::AsciiExt; -/// Find a `#[cssparser__match_ignore_ascii_case__data(string = "…", string = "…")]` attribute, -/// panic if any string contains ASCII uppercase letters, -/// emit a `MAX_LENGTH` constant with the length of the longest string. -#[proc_macro_derive(cssparser__match_ignore_ascii_case__max_len, - attributes(cssparser__match_ignore_ascii_case__data))] -pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +/// 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__match_ignore_ascii_case__data"); + let data = list_attr(&input, "cssparser__assert_ascii_lowercase__data"); - let lengths = data.iter().map(|sub_attr| { + 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"); - string.len() - }); + } + + "".parse().unwrap() +} + +/// Find a `#[cssparser__max_len__data(string = "…", string = "…")]` attribute, +/// panic if any string contains ASCII uppercase letters, +/// 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! { @@ -37,6 +49,7 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// `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 { @@ -49,13 +62,15 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { _ => panic!("expected tuple struct newtype, got {:?}", input.body) }; - let kv_pairs = list_attr(&input, "cssparser__phf_map__kv_pairs"); - - let mut map = phf_codegen::Map::new(); - for chunk in kv_pairs.chunks(2) { + 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"); - map.entry(key, value); + (key.to_ascii_lowercase(), value) + }).collect(); + + let mut map = phf_codegen::Map::new(); + for &(ref key, value) in &pairs { + map.entry(&**key, value); } let mut initializer_bytes = Vec::::new(); diff --git a/src/lib.rs b/src/lib.rs index 121bbab0..467ea0eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,6 +134,11 @@ macro_rules! match_ignore_ascii_case { // 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__max_len!($value => lowercase, $($string),+); match lowercase { $( @@ -160,7 +165,6 @@ macro_rules! match_ignore_ascii_case { /// Requirements: /// /// * The `phf` and `cssparser_macros` crates must also be imported at the crate root -/// * The keys must not contain ASCII upper case letters. (They must be already be lower-cased.) /// * The values must be given a strings that contain Rust syntax for a constant expression. /// /// ## Example: @@ -216,10 +220,10 @@ macro_rules! ascii_case_insensitive_phf_map { #[doc(hidden)] macro_rules! _cssparser_internal__max_len { ($input: expr => $output: ident, $($string: expr),+) => { - #[derive(cssparser__match_ignore_ascii_case__max_len)] - #[cssparser__match_ignore_ascii_case__data($(string = $string),+)] + #[derive(cssparser__max_len)] + #[cssparser__max_len__data($(string = $string),+)] #[allow(dead_code)] - struct Dummy; + struct Dummy2; // MAX_LENGTH is generated by cssparser__match_ignore_ascii_case__max_len let mut buffer: [u8; MAX_LENGTH] = From 78996fc0a3c6d63fbfca332e3c0babdc732d018c Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 14:06:28 +0100 Subject: [PATCH 09/14] Increment version for breaking changes in match_ignore_ascii_case * String patterns now need to be already ASCII lower-case * Input string is no longer implicitly borrowed, add `&` as needed. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c42d7e3e..211221fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cssparser" -version = "0.10.0" +version = "0.11.0" authors = [ "Simon Sapin " ] description = "Rust implementation of CSS Syntax Level 3" From 0db4f8ed7dd72cbd08099b6b06a5b1ce68f36227 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 15:41:32 +0100 Subject: [PATCH 10/14] Rename internal things per what they do. --- src/lib.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 467ea0eb..c2b9a84a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,7 +139,7 @@ macro_rules! match_ignore_ascii_case { #[allow(dead_code)] struct Dummy; - _cssparser_internal__max_len!($value => lowercase, $($string),+); + _cssparser_internal__to_lowercase!($value => lowercase, $($string),+); match lowercase { $( Some($string) => $result, @@ -203,7 +203,7 @@ macro_rules! ascii_case_insensitive_phf_map { impl $Name { #[inline] fn get(input: &str) -> Option<&'static $ValueType> { - _cssparser_internal__max_len!(input => lowercase, $($key),+); + _cssparser_internal__to_lowercase!(input => lowercase, $($key),+); lowercase.and_then(|string| $Name::map().get(string)) } } @@ -212,13 +212,14 @@ macro_rules! ascii_case_insensitive_phf_map { /// 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 `_match_ignore_ascii_case__to_lowercase` +/// * 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__max_len { +macro_rules! _cssparser_internal__to_lowercase { ($input: expr => $output: ident, $($string: expr),+) => { #[derive(cssparser__max_len)] #[cssparser__max_len__data($(string = $string),+)] @@ -227,24 +228,26 @@ macro_rules! _cssparser_internal__max_len { // MAX_LENGTH is generated by cssparser__match_ignore_ascii_case__max_len let mut buffer: [u8; MAX_LENGTH] = - // `buffer` is only used in `_match_ignore_ascii_case__to_lowercase`, + // `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. unsafe { ::std::mem::uninitialized() }; - let $output = $crate::_match_ignore_ascii_case__to_lowercase(&mut buffer, $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.** +/// /// 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 _match_ignore_ascii_case__to_lowercase<'a>(buffer: &'a mut [u8], input: &'a str) -> Option<&'a str> { +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()); From c85990a1c8a92cc4d56e9101e857b225683ddb66 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 17:46:35 +0100 Subject: [PATCH 11/14] Be more robust regarding attribute ordering in proc macros. --- macros/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index e0165242..0c408d4c 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -94,14 +94,15 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// 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] { - match input.attrs[0].value { - syn::MetaItem::List(ref name, ref nested) if name == expected_name => { - nested - } - _ => { - panic!("expected a {} attribute", expected_name) + 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) } /// Panic if `sub_attr` is not a name-value like `foo = "…"` with the given name, From df6892b17b17d94e50a9c96964c6da2e43f65b67 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 17:51:29 +0100 Subject: [PATCH 12/14] Support macros being used in a module with #[deny(unsafe_code)] --- src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c2b9a84a..270e2276 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,14 +226,14 @@ macro_rules! _cssparser_internal__to_lowercase { #[allow(dead_code)] struct Dummy2; - // MAX_LENGTH is generated by cssparser__match_ignore_ascii_case__max_len - let mut buffer: [u8; MAX_LENGTH] = - // `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. - unsafe { - ::std::mem::uninitialized() - }; + // 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 $output = $crate::_internal__to_lowercase(&mut buffer, $input); } } From 136c97a0f6040d810735deb8e418d2236bf6cf0b Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 17:52:28 +0100 Subject: [PATCH 13/14] match_ignore_ascii_case: extend lifetime of input. --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 270e2276..08e7bd71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -234,7 +234,8 @@ macro_rules! _cssparser_internal__to_lowercase { let mut buffer: [u8; MAX_LENGTH] = unsafe { ::std::mem::uninitialized() }; - let $output = $crate::_internal__to_lowercase(&mut buffer, $input); + let input: &str = $input; + let $output = $crate::_internal__to_lowercase(&mut buffer, input); } } From 138ccaf9bdd480f79e7e8e6ca8b8948310057397 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 25 Feb 2017 19:49:48 +0100 Subject: [PATCH 14/14] Remove an obsolete bit of comment. --- macros/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index 0c408d4c..a783147a 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -27,8 +27,7 @@ pub fn assert_ascii_lowercase(input: proc_macro::TokenStream) -> proc_macro::Tok } /// Find a `#[cssparser__max_len__data(string = "…", string = "…")]` attribute, -/// panic if any string contains ASCII uppercase letters, -/// emit a `MAX_LENGTH` constant with the length of the longest string. +/// 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 {