From 8c2f499939a0fbf2eeec055d309ebf314d803acb Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 12:52:01 +0100 Subject: [PATCH 01/19] Make phf map macro take literal values rather than strings of Rust syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … in the style of https://github.com/dtolnay/proc-macro-hack/ --- Cargo.toml | 4 +- macros/Cargo.toml | 5 +- macros/lib.rs | 75 +++++++++--- src/color.rs | 304 +++++++++++++++++++++++----------------------- src/lib.rs | 23 ++-- 5 files changed, 219 insertions(+), 192 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 211221fe..4068795f 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,7 +20,7 @@ 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" diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 2ca733c4..fd98d2cb 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/" @@ -14,5 +14,4 @@ proc-macro = true [dependencies] phf_codegen = "0.7" quote = "0.3" -syn = "0.11" - +syn = {version = "0.11", features = ["full"]} diff --git a/macros/lib.rs b/macros/lib.rs index a783147a..bc35cfe5 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -44,32 +44,31 @@ 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 = "…")]`. -/// Keys are ASCII-lowercased. -#[proc_macro_derive(cssparser__phf_map, - attributes(cssparser__phf_map__kv_pairs))] +/// ``` +/// impl $Name { +/// fn map() -> &'static ::phf::Map<&'static str, $ValueType> { … } +/// } +/// ``` +/// +/// Map keys are ASCII-lowercased. +#[proc_macro_derive(cssparser__phf_map)] 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) + let token_trees = find_smuggled_tokens(&input); + let value_type = &token_trees[0]; + let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| { + let key = match chunk[0] { + syn::TokenTree::Token(syn::Token::Literal(syn::Lit::Str(ref string, _))) => string, + _ => panic!("expected string literal, got {:?}", chunk[0]) + }; + let value = &chunk[1]; + (key.to_ascii_lowercase(), quote!(#value).to_string()) }).collect(); let mut map = phf_codegen::Map::new(); - for &(ref key, value) in &pairs { - map.entry(&**key, value); + for &(ref key, ref value) in &pairs { + map.entry(&**key, &**value); } let mut initializer_bytes = Vec::::new(); @@ -90,6 +89,42 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { tokens.as_str().parse().unwrap() } +/// Return the `…` part in: +/// +/// ```rust +/// enum $Name { +/// Input = (0, stringify!(…)).0 +/// } +/// ``` +fn find_smuggled_tokens(input: &syn::DeriveInput) -> &[syn::TokenTree] { + let discriminant = match input.body { + syn::Body::Enum(ref variants) if variants.len() == 1 => &variants[0].discriminant, + _ => panic!("expected single-variant enum, got {:?}", input.body) + }; + let tuple = match *discriminant { + Some(syn::ConstExpr::Other(syn::Expr { node: syn::ExprKind::TupField(ref t, 0), .. })) => t, + _ => panic!("expected a discriminant like tuple.0, got {:?}", discriminant) + }; + let expr = match **tuple { + syn::Expr { node: syn::ExprKind::Tup(ref values), .. } if values.len() == 2 => &values[1], + _ => panic!("expected non-empty tuple, got {:?}", tuple) + }; + let macro_args = match *expr { + syn::Expr { node: syn::ExprKind::Mac( + syn::Mac { ref tts, path: syn::Path { global: false, ref segments }} + ), .. } + if segments.len() == 1 + && segments[0] == syn::PathSegment::from("stringify") + && tts.len() == 1 + => &tts[0], + _ => panic!("expected a stringify!(…) macro, got {:?}", expr) + }; + match *macro_args { + syn::TokenTree::Delimited(syn::Delimited { ref tts, delim: syn::DelimToken::Paren }) => tts, + _ => panic!("expected (…) parentheses, got {:?}", macro_args) + } +} + /// 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] { diff --git a/src/color.rs b/src/color.rs index 85ae102a..343e9c5b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -181,158 +181,158 @@ 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", + "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 08e7bd71..a61fb353 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,10 +162,7 @@ macro_rules! match_ignore_ascii_case { /// 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. +/// The `phf` and `cssparser_macros` crates must also be imported at the crate root /// /// ## Example: /// @@ -179,9 +176,9 @@ macro_rules! match_ignore_ascii_case { /// 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)", +/// "red" => (255, 0, 0), +/// "green" => (0, 255, 0), +/// "blue" => (0, 0, 255), /// } /// } /// KEYWORDS::get(input).cloned() @@ -192,13 +189,10 @@ macro_rules! ascii_case_insensitive_phf_map { $( $key: expr => $value: expr, )* }) => { #[derive(cssparser__phf_map)] - #[cssparser__phf_map__kv_pairs( - $( - key = $key, - value = $value - ),+ - )] - struct $Name($ValueType); + #[allow(unused)] + enum $Name { + Input = (0, stringify!( ($ValueType) $( $key ($value) )+ )).0 + } impl $Name { #[inline] @@ -239,7 +233,6 @@ macro_rules! _cssparser_internal__to_lowercase { } } - /// 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.** From 0ead1b54496dbb1e4d575d876736a0452e8dcc28 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 14:36:58 +0100 Subject: [PATCH 02/19] Use the enum discriminant trick for match_ignore_ascii_case too --- macros/lib.rs | 59 ++++++++++++--------------------------------------- src/lib.rs | 14 ++++++------ 2 files changed, 22 insertions(+), 51 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index bc35cfe5..0bcc879b 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -9,32 +9,27 @@ 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))] +/// Panic if any string contains ASCII uppercase letters. +#[proc_macro_derive(cssparser__assert_ascii_lowercase)] 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"); + for token in find_smuggled_tokens(&input) { + let string = string_literal(token); assert_eq!(*string, string.to_ascii_lowercase(), - "the expected strings must be given in ASCII lowercase"); + "the string patterns must be given in ASCII lowercase"); } "".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))] +#[proc_macro_derive(cssparser__max_len)] 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 token_trees = find_smuggled_tokens(&input); + let lengths = token_trees.iter().map(|tt| string_literal(tt).len()); let max_length = lengths.max().expect("expected at least one string"); let tokens = quote! { @@ -55,13 +50,11 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 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 token_trees = find_smuggled_tokens(&input); let value_type = &token_trees[0]; let pairs: Vec<_> = token_trees[1..].chunks(2).map(|chunk| { - let key = match chunk[0] { - syn::TokenTree::Token(syn::Token::Literal(syn::Lit::Str(ref string, _))) => string, - _ => panic!("expected string literal, got {:?}", chunk[0]) - }; + let key = string_literal(&chunk[0]); let value = &chunk[1]; (key.to_ascii_lowercase(), quote!(#value).to_string()) }).collect(); @@ -125,33 +118,9 @@ fn find_smuggled_tokens(input: &syn::DeriveInput) -> &[syn::TokenTree] { } } -/// 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) -} - -/// 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/src/lib.rs b/src/lib.rs index a61fb353..a75131b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,9 +135,10 @@ macro_rules! match_ignore_ascii_case { (@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; + #[allow(unused)] + enum Dummy { + Input = (0, stringify!( $( $string )+ )).0 + } _cssparser_internal__to_lowercase!($value => lowercase, $($string),+); match lowercase { @@ -216,9 +217,10 @@ macro_rules! ascii_case_insensitive_phf_map { 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; + #[allow(unused)] + enum Dummy2 { + Input = (0, stringify!( $( $string )+ )).0 + } // 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, From d2cae6c6400f4120025718978cc73d43f4342ac9 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 14:59:32 +0100 Subject: [PATCH 03/19] Random macro refactoring --- macros/lib.rs | 28 +++++++------------- src/color.rs | 4 +-- src/lib.rs | 72 ++++++++++++++++++++++++++++----------------------- 3 files changed, 51 insertions(+), 53 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index 0bcc879b..b59fe571 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -23,7 +23,7 @@ pub fn assert_ascii_lowercase(input: proc_macro::TokenStream) -> proc_macro::Tok "".parse().unwrap() } -/// and emit a `MAX_LENGTH` constant with the length of the longest string. +/// Emit a `MAX_LENGTH` constant with the length of the longest string. #[proc_macro_derive(cssparser__max_len)] pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = syn::parse_macro_input(&input.to_string()).unwrap(); @@ -40,16 +40,13 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } /// ``` -/// impl $Name { -/// fn map() -> &'static ::phf::Map<&'static str, $ValueType> { … } -/// } +/// static MAP: &'static ::phf::Map<&'static str, $ValueType> = …; /// ``` /// /// Map keys are ASCII-lowercased. #[proc_macro_derive(cssparser__phf_map)] 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 token_trees = find_smuggled_tokens(&input); let value_type = &token_trees[0]; @@ -64,20 +61,13 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 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! { - impl #name { - #[inline] - fn map() -> &'static ::phf::Map<&'static str, #value_type> { - static MAP: ::phf::Map<&'static str, #value_type> = #initializer_tokens; - &MAP - } - } + let mut tokens = quote! { + static MAP: ::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.as_str().parse().unwrap() } @@ -85,7 +75,7 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Return the `…` part in: /// /// ```rust -/// enum $Name { +/// enum Somthing { /// Input = (0, stringify!(…)).0 /// } /// ``` diff --git a/src/color.rs b/src/color.rs index 343e9c5b..ea65f549 100644 --- a/src/color.rs +++ b/src/color.rs @@ -180,7 +180,7 @@ pub fn parse_color_keyword(ident: &str) -> Result { } } ascii_case_insensitive_phf_map! { - KEYWORDS: Map = { + keyword -> Color = { "black" => rgb!(0, 0, 0), "silver" => rgb!(192, 192, 192), "gray" => rgb!(128, 128, 128), @@ -335,7 +335,7 @@ pub fn parse_color_keyword(ident: &str) -> Result { "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 a75131b2..82ed067d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,18 +134,18 @@ macro_rules! match_ignore_ascii_case { // finished parsing (@inner $value:expr, () -> ($(($string:expr => $result:expr))*) $fallback:expr ) => { { - #[derive(cssparser__assert_ascii_lowercase)] - #[allow(unused)] - enum Dummy { - Input = (0, stringify!( $( $string )+ )).0 + _cssparser_internal__pretend_proc_macro! { + cssparser__assert_ascii_lowercase!( $( $string )+ ) } - _cssparser_internal__to_lowercase!($value => lowercase, $($string),+); - match lowercase { - $( - Some($string) => $result, - )+ - _ => $fallback + { + _cssparser_internal__to_lowercase!($value => lowercase, $($string),+); + match lowercase { + $( + Some($string) => $result, + )+ + _ => $fallback + } } } }; @@ -156,11 +156,11 @@ macro_rules! match_ignore_ascii_case { }; } -/// Define a placeholder type `$Name` -/// with a method `fn get(input: &str) -> Option<&'static $ValueType>`. +/// Define a function `$name(&str) -> Option<&'static $ValueType>` /// -/// This method uses finds a match for the input string -/// in a [`phf` map](https://github.com/sfackler/rust-phf). +/// 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. /// /// The `phf` and `cssparser_macros` crates must also be imported at the crate root @@ -176,30 +176,25 @@ macro_rules! match_ignore_ascii_case { /// /// fn color_rgb(input: &str) -> Option<(u8, u8, u8)> { /// ascii_case_insensitive_phf_map! { -/// KEYWORDS: Map<(u8, u8, u8)> = { +/// keyword -> (u8, u8, u8) = { /// "red" => (255, 0, 0), /// "green" => (0, 255, 0), /// "blue" => (0, 0, 255), /// } /// } -/// KEYWORDS::get(input).cloned() +/// keyword(input).cloned() /// } #[macro_export] macro_rules! ascii_case_insensitive_phf_map { - ($Name: ident : Map<$ValueType: ty> = { - $( $key: expr => $value: expr, )* - }) => { - #[derive(cssparser__phf_map)] - #[allow(unused)] - enum $Name { - Input = (0, stringify!( ($ValueType) $( $key ($value) )+ )).0 - } + ($name: ident -> $ValueType: ty = { $( $key: expr => $value: expr, )* }) => { + fn $name(input: &str) -> Option<&'static $ValueType> { + _cssparser_internal__pretend_proc_macro! { + cssparser__phf_map!( ($ValueType) $( $key ($value) )+ ) + } - 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)) + lowercase.and_then(|s| MAP.get(s)) } } } @@ -216,10 +211,8 @@ macro_rules! ascii_case_insensitive_phf_map { #[doc(hidden)] macro_rules! _cssparser_internal__to_lowercase { ($input: expr => $output: ident, $($string: expr),+) => { - #[derive(cssparser__max_len)] - #[allow(unused)] - enum Dummy2 { - Input = (0, stringify!( $( $string )+ )).0 + _cssparser_internal__pretend_proc_macro! { + cssparser__max_len!( $( $string )+ ) } // mem::uninitialized() is ok because `buffer` is only used in `_internal__to_lowercase`, @@ -235,6 +228,21 @@ macro_rules! _cssparser_internal__to_lowercase { } } +/// 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.** +#[macro_export] +#[doc(hidden)] +macro_rules! _cssparser_internal__pretend_proc_macro { + ($macro_name: ident ! ( $($tt: tt)* )) => { + #[derive($macro_name)] + #[allow(unused)] + enum Dummy { + Input = (0, stringify!( $( $tt )* )).0 + } + } +} + /// 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.** From 54cb055c3ddb7fc15f16de12e0e2f6ca286301b5 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 15:04:53 +0100 Subject: [PATCH 04/19] Move match_byte.rs out of src/ --- build.rs | 10 +++++----- {src/macros => build}/match_byte.rs | 0 src/macros/mod.rs | 5 ----- 3 files changed, 5 insertions(+), 10 deletions(-) rename {src/macros => build}/match_byte.rs (100%) delete mode 100644 src/macros/mod.rs 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/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; From d517090d40a07b3073aff6bd6e247b3ecfe4111a Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 15:08:14 +0100 Subject: [PATCH 05/19] Move macro_rules! macros into their own file. --- src/lib.rs | 195 +------------------------------------------------ src/macros.rs | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 192 deletions(-) create mode 100644 src/macros.rs diff --git a/src/lib.rs b/src/lib.rs index 82ed067d..32c13a89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,202 +89,13 @@ 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) - }; +#[doc(hidden)] pub use macros::_internal__to_lowercase; - // 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 ) => { - { - _cssparser_internal__pretend_proc_macro! { - cssparser__assert_ascii_lowercase!( $( $string )+ ) - } - - { - _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 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. -/// -/// The `phf` and `cssparser_macros` crates must also be imported at the crate root -/// -/// ## 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! { -/// 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, )* }) => { - fn $name(input: &str) -> Option<&'static $ValueType> { - _cssparser_internal__pretend_proc_macro! { - cssparser__phf_map!( ($ValueType) $( $key ($value) )+ ) - } - - { - _cssparser_internal__to_lowercase!(input => lowercase, $($key),+); - 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.** -/// -/// * 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),+) => { - _cssparser_internal__pretend_proc_macro! { - cssparser__max_len!( $( $string )+ ) - } - - // 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); - } -} - -/// 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.** -#[macro_export] -#[doc(hidden)] -macro_rules! _cssparser_internal__pretend_proc_macro { - ($macro_name: ident ! ( $($tt: tt)* )) => { - #[derive($macro_name)] - #[allow(unused)] - enum Dummy { - Input = (0, stringify!( $( $tt )* )).0 - } - } -} - -/// 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..02816c30 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,198 @@ +/* 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/. */ + + +/// 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 ) => { + { + _cssparser_internal__pretend_proc_macro! { + cssparser__assert_ascii_lowercase!( $( $string )+ ) + } + + { + _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 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. +/// +/// The `phf` and `cssparser_macros` crates must also be imported at the crate root +/// +/// ## 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! { +/// 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, )* }) => { + fn $name(input: &str) -> Option<&'static $ValueType> { + _cssparser_internal__pretend_proc_macro! { + cssparser__phf_map!( ($ValueType) $( $key ($value) )+ ) + } + + { + _cssparser_internal__to_lowercase!(input => lowercase, $($key),+); + 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.** +/// +/// * 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),+) => { + _cssparser_internal__pretend_proc_macro! { + cssparser__max_len!( $( $string )+ ) + } + + // 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); + } +} + +/// 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.** +#[macro_export] +#[doc(hidden)] +macro_rules! _cssparser_internal__pretend_proc_macro { + ($macro_name: ident ! ( $($tt: tt)* )) => { + #[derive($macro_name)] + #[allow(unused)] + enum Dummy { + Input = (0, stringify!( $( $tt )* )).0 + } + } +} + +/// 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 + } +} + +#[cfg(feature = "dummy_match_byte")] +macro_rules! match_byte { + ($value:expr, $($rest:tt)* ) => { + match $value { + $( + $rest + )+ + } + }; +} From 7f9e876cbee313c129c7ca4b1fd183e1a2e12693 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 16:26:54 +0100 Subject: [PATCH 06/19] Support full match syntax in match_ignore_ascii_case --- macros/lib.rs | 115 ++++++++++++++++++++++++++++++++------------------ src/macros.rs | 57 +++++++++---------------- 2 files changed, 93 insertions(+), 79 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index b59fe571..7cbc0b0a 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -10,33 +10,58 @@ extern crate syn; use std::ascii::AsciiExt; /// Panic if any string contains ASCII uppercase letters. -#[proc_macro_derive(cssparser__assert_ascii_lowercase)] -pub fn assert_ascii_lowercase(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = syn::parse_macro_input(&input.to_string()).unwrap(); - - for token in find_smuggled_tokens(&input) { - let string = string_literal(token); - assert_eq!(*string, string.to_ascii_lowercase(), - "the string patterns must be given in ASCII lowercase"); - } - - "".parse().unwrap() +/// Emit a `MAX_LENGTH` constant with the length of the longest string. +#[proc_macro_derive(cssparser__assert_ascii_lowercase__max_len)] +pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + max_len_common(input, |token_trees| { + let tokens = quote!( match x { #( #token_trees )* } ); + let expr = syn::parse_expr(tokens.as_str()).unwrap(); + let arms = match expr { + syn::Expr { node: syn::ExprKind::Match(_, ref arms), .. } => arms, + _ => panic!("expected a match expression, got {:?}", expr) + }; + 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) + } + }).max() + }) } /// Emit a `MAX_LENGTH` constant with the length of the longest string. #[proc_macro_derive(cssparser__max_len)] pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = syn::parse_macro_input(&input.to_string()).unwrap(); - - let token_trees = find_smuggled_tokens(&input); - let lengths = token_trees.iter().map(|tt| string_literal(tt).len()); - let max_length = lengths.max().expect("expected at least one string"); - - let tokens = quote! { - const MAX_LENGTH: usize = #max_length; - }; + max_len_common(input, |token_trees| { + token_trees.iter().map(|tt| string_literal(tt).len()).max() + }) +} - tokens.as_str().parse().unwrap() +fn max_len_common(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream +where F : FnOnce(&[syn::TokenTree]) -> Option { + common(input, |token_trees| { + let max_length = f(token_trees).expect("expected at least one string"); + quote! { + const MAX_LENGTH: usize = #max_length; + } + }) } /// ``` @@ -46,29 +71,35 @@ pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Map keys are ASCII-lowercased. #[proc_macro_derive(cssparser__phf_map)] pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = syn::parse_macro_input(&input.to_string()).unwrap(); + common(input, |token_trees| { + 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(); + for &(ref key, ref value) in &pairs { + map.entry(&**key, &**value); + } + + let mut tokens = quote! { + static MAP: ::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 + }) +} +fn common(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream +where F: FnOnce(&[syn::TokenTree]) -> quote::Tokens { + let input = syn::parse_macro_input(&input.to_string()).unwrap(); let token_trees = find_smuggled_tokens(&input); - 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(); - for &(ref key, ref value) in &pairs { - map.entry(&**key, &**value); - } - - let mut tokens = quote! { - static MAP: ::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(";"); - + let tokens = f(token_trees); tokens.as_str().parse().unwrap() } diff --git a/src/macros.rs b/src/macros.rs index 02816c30..fd2622c4 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -24,50 +24,34 @@ /// "rgba" => parse_rgba(..), /// "hsl" => parse_hsl(..), /// "hsla" => parse_hsla(..), -/// _ => Err("unknown function") +/// name @ _ => Err(format!("unknown function: {}", name)) /// } /// # ;} /// # 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("") } +/// # 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 { - // 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 ) => { + ( $input:expr, $( $match_body:tt )* ) => { { _cssparser_internal__pretend_proc_macro! { - cssparser__assert_ascii_lowercase!( $( $string )+ ) + cssparser__assert_ascii_lowercase__max_len!( $( $match_body )* ) } { - _cssparser_internal__to_lowercase!($value => lowercase, $($string),+); - match lowercase { - $( - Some($string) => $result, - )+ - _ => $fallback + // MAX_LENGTH is generated by cssparser__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 )* } } } }; - - // entry point, start parsing - ( $value:expr, $($rest:tt)* ) => { - match_ignore_ascii_case!(@inner $value, ($($rest)*) -> ()) - }; } /// Define a function `$name(&str) -> Option<&'static $ValueType>` @@ -107,7 +91,11 @@ macro_rules! ascii_case_insensitive_phf_map { } { - _cssparser_internal__to_lowercase!(input => lowercase, $($key),+); + _cssparser_internal__pretend_proc_macro! { + cssparser__max_len!( $( $key )+ ) + } + // MAX_LENGTH is generated by cssparser__max_len + _cssparser_internal__to_lowercase!(input, MAX_LENGTH => lowercase); lowercase.and_then(|s| MAP.get(s)) } } @@ -124,17 +112,12 @@ macro_rules! ascii_case_insensitive_phf_map { #[macro_export] #[doc(hidden)] macro_rules! _cssparser_internal__to_lowercase { - ($input: expr => $output: ident, $($string: expr),+) => { - _cssparser_internal__pretend_proc_macro! { - cssparser__max_len!( $( $string )+ ) - } - + ($input: expr, $MAX_LENGTH: 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)] - // MAX_LENGTH is generated by cssparser__max_len - let mut buffer: [u8; MAX_LENGTH] = unsafe { + let mut buffer: [u8; $MAX_LENGTH] = unsafe { ::std::mem::uninitialized() }; let input: &str = $input; From 0909afdb686b0e43ce416e9d3723c5c1155a4dd3 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 20:04:10 +0100 Subject: [PATCH 07/19] Normalize prefix for exported but not-really-public names --- macros/lib.rs | 6 +++--- src/macros.rs | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index 7cbc0b0a..4ec28937 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -11,7 +11,7 @@ use std::ascii::AsciiExt; /// Panic if any string contains ASCII uppercase letters. /// Emit a `MAX_LENGTH` constant with the length of the longest string. -#[proc_macro_derive(cssparser__assert_ascii_lowercase__max_len)] +#[proc_macro_derive(cssparser_internal__assert_ascii_lowercase__max_len)] pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { max_len_common(input, |token_trees| { let tokens = quote!( match x { #( #token_trees )* } ); @@ -47,7 +47,7 @@ pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_ma } /// Emit a `MAX_LENGTH` constant with the length of the longest string. -#[proc_macro_derive(cssparser__max_len)] +#[proc_macro_derive(cssparser_internal__max_len)] pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { max_len_common(input, |token_trees| { token_trees.iter().map(|tt| string_literal(tt).len()).max() @@ -69,7 +69,7 @@ where F : FnOnce(&[syn::TokenTree]) -> Option { /// ``` /// /// Map keys are ASCII-lowercased. -#[proc_macro_derive(cssparser__phf_map)] +#[proc_macro_derive(cssparser_internal__phf_map)] pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { common(input, |token_trees| { let value_type = &token_trees[0]; diff --git a/src/macros.rs b/src/macros.rs index fd2622c4..f1831d31 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -37,13 +37,13 @@ macro_rules! match_ignore_ascii_case { ( $input:expr, $( $match_body:tt )* ) => { { - _cssparser_internal__pretend_proc_macro! { - cssparser__assert_ascii_lowercase__max_len!( $( $match_body )* ) + cssparser_internal__pretend_proc_macro! { + cssparser_internal__assert_ascii_lowercase__max_len!( $( $match_body )* ) } { - // MAX_LENGTH is generated by cssparser__assert_ascii_lowercase__max_len - _cssparser_internal__to_lowercase!($input, MAX_LENGTH => lowercase); + // 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") { @@ -86,16 +86,16 @@ macro_rules! match_ignore_ascii_case { macro_rules! ascii_case_insensitive_phf_map { ($name: ident -> $ValueType: ty = { $( $key: expr => $value: expr, )* }) => { fn $name(input: &str) -> Option<&'static $ValueType> { - _cssparser_internal__pretend_proc_macro! { - cssparser__phf_map!( ($ValueType) $( $key ($value) )+ ) + cssparser_internal__pretend_proc_macro! { + cssparser_internal__phf_map!( ($ValueType) $( $key ($value) )+ ) } { - _cssparser_internal__pretend_proc_macro! { - cssparser__max_len!( $( $key )+ ) + cssparser_internal__pretend_proc_macro! { + cssparser_internal__max_len!( $( $key )+ ) } - // MAX_LENGTH is generated by cssparser__max_len - _cssparser_internal__to_lowercase!(input, MAX_LENGTH => lowercase); + // MAX_LENGTH is generated by cssparser_internal__max_len + cssparser_internal__to_lowercase!(input, MAX_LENGTH => lowercase); lowercase.and_then(|s| MAP.get(s)) } } @@ -111,7 +111,7 @@ macro_rules! ascii_case_insensitive_phf_map { /// with a stack-allocated buffer as long as the longest `$string`. #[macro_export] #[doc(hidden)] -macro_rules! _cssparser_internal__to_lowercase { +macro_rules! cssparser_internal__to_lowercase { ($input: expr, $MAX_LENGTH: 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, @@ -130,7 +130,7 @@ macro_rules! _cssparser_internal__to_lowercase { /// **This macro is not part of the public API. It can change or be removed between any versions.** #[macro_export] #[doc(hidden)] -macro_rules! _cssparser_internal__pretend_proc_macro { +macro_rules! cssparser_internal__pretend_proc_macro { ($macro_name: ident ! ( $($tt: tt)* )) => { #[derive($macro_name)] #[allow(unused)] From 5efba23102b8297caf043722c5a0f3fadbe5fd0a Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 20:51:45 +0100 Subject: [PATCH 08/19] Move proc-macro trickery to a separate crate. It could be useful outside of cssparser. --- Cargo.toml | 3 +- macros/Cargo.toml | 5 +- macros/lib.rs | 108 ++++++++----------------------- procedural-masquarade/Cargo.toml | 11 ++++ procedural-masquarade/lib.rs | 53 +++++++++++++++ src/lib.rs | 1 + src/macros.rs | 22 ++----- 7 files changed, 102 insertions(+), 101 deletions(-) create mode 100644 procedural-masquarade/Cargo.toml create mode 100644 procedural-masquarade/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 4068795f..035f1a7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ cssparser-macros = {path = "./macros", version = "0.2"} heapsize = {version = "0.3", optional = true} matches = "0.1" phf = "0.7" +procedural-masquarade = {path = "./procedural-masquarade", 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-masquarade"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index fd98d2cb..45c17cfc 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,6 +12,7 @@ path = "lib.rs" proc-macro = true [dependencies] +procedural-masquarade = {path = "../procedural-masquarade", version = "0.1"} phf_codegen = "0.7" -quote = "0.3" -syn = {version = "0.11", features = ["full"]} +quote = "0.3.14" +syn = {version = "0.11.8", features = ["full"]} diff --git a/macros/lib.rs b/macros/lib.rs index 4ec28937..b72de5a5 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_masquarade; extern crate phf_codegen; extern crate proc_macro; #[macro_use] extern crate quote; @@ -9,18 +10,17 @@ extern crate syn; use std::ascii::AsciiExt; -/// Panic if any string contains ASCII uppercase letters. -/// Emit a `MAX_LENGTH` constant with the length of the longest string. -#[proc_macro_derive(cssparser_internal__assert_ascii_lowercase__max_len)] -pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - max_len_common(input, |token_trees| { - let tokens = quote!( match x { #( #token_trees )* } ); - let expr = syn::parse_expr(tokens.as_str()).unwrap(); +define_proc_macros! { + /// Panic if any string contains ASCII uppercase letters. + /// Emit a `MAX_LENGTH` constant with the length of the longest string. + #[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) }; - arms.iter().flat_map(|arm| &arm.pats).filter_map(|pattern| { + 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 | @@ -42,36 +42,23 @@ pub fn assert_ascii_lowercase_max_len(input: proc_macro::TokenStream) -> proc_ma } _ => panic!("expected string pattern, got {:?}", expr) } - }).max() - }) -} - -/// Emit a `MAX_LENGTH` constant with the length of the longest string. -#[proc_macro_derive(cssparser_internal__max_len)] -pub fn max_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - max_len_common(input, |token_trees| { - token_trees.iter().map(|tt| string_literal(tt).len()).max() - }) -} + })) + } -fn max_len_common(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream -where F : FnOnce(&[syn::TokenTree]) -> Option { - common(input, |token_trees| { - let max_length = f(token_trees).expect("expected at least one string"); - quote! { - const MAX_LENGTH: usize = #max_length; - } - }) -} + /// Emit 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())) + } -/// ``` -/// static MAP: &'static ::phf::Map<&'static str, $ValueType> = …; -/// ``` -/// -/// Map keys are ASCII-lowercased. -#[proc_macro_derive(cssparser_internal__phf_map)] -pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - common(input, |token_trees| { + /// ``` + /// static MAP: &'static ::phf::Map<&'static str, $ValueType> = …; + /// ``` + /// + /// Map keys are ASCII-lowercased. + #[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]); @@ -91,52 +78,13 @@ pub fn phf_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { map.build(&mut initializer_bytes).unwrap(); tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap()); tokens.append(";"); - tokens - }) -} - -fn common(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream -where F: FnOnce(&[syn::TokenTree]) -> quote::Tokens { - let input = syn::parse_macro_input(&input.to_string()).unwrap(); - let token_trees = find_smuggled_tokens(&input); - let tokens = f(token_trees); - tokens.as_str().parse().unwrap() + tokens.to_string() + } } -/// Return the `…` part in: -/// -/// ```rust -/// enum Somthing { -/// Input = (0, stringify!(…)).0 -/// } -/// ``` -fn find_smuggled_tokens(input: &syn::DeriveInput) -> &[syn::TokenTree] { - let discriminant = match input.body { - syn::Body::Enum(ref variants) if variants.len() == 1 => &variants[0].discriminant, - _ => panic!("expected single-variant enum, got {:?}", input.body) - }; - let tuple = match *discriminant { - Some(syn::ConstExpr::Other(syn::Expr { node: syn::ExprKind::TupField(ref t, 0), .. })) => t, - _ => panic!("expected a discriminant like tuple.0, got {:?}", discriminant) - }; - let expr = match **tuple { - syn::Expr { node: syn::ExprKind::Tup(ref values), .. } if values.len() == 2 => &values[1], - _ => panic!("expected non-empty tuple, got {:?}", tuple) - }; - let macro_args = match *expr { - syn::Expr { node: syn::ExprKind::Mac( - syn::Mac { ref tts, path: syn::Path { global: false, ref segments }} - ), .. } - if segments.len() == 1 - && segments[0] == syn::PathSegment::from("stringify") - && tts.len() == 1 - => &tts[0], - _ => panic!("expected a stringify!(…) macro, got {:?}", expr) - }; - match *macro_args { - syn::TokenTree::Delimited(syn::Delimited { ref tts, delim: syn::DelimToken::Paren }) => tts, - _ => panic!("expected (…) parentheses, got {:?}", macro_args) - } +fn max_len>(lengths: I) -> String { + let max_length = lengths.max().expect("expected at least one string"); + quote!( const MAX_LENGTH: usize = #max_length; ).to_string() } fn string_literal(token: &syn::TokenTree) -> &str { diff --git a/procedural-masquarade/Cargo.toml b/procedural-masquarade/Cargo.toml new file mode 100644 index 00000000..2f7d586f --- /dev/null +++ b/procedural-masquarade/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "procedural-masquarade" +version = "0.1.0" +authors = ["Simon Sapin "] +description = "macro_rules for making proc_macro_derive pretending to be proc_macro" +documentation = "https://docs.rs/procedural-masquarade/" +repository = "https://github.com/servo/rust-cssparser" +license = "MIT/Apache-2.0" + +[lib] +path = "lib.rs" diff --git a/procedural-masquarade/lib.rs b/procedural-masquarade/lib.rs new file mode 100644 index 00000000..e6d443a0 --- /dev/null +++ b/procedural-masquarade/lib.rs @@ -0,0 +1,53 @@ +#[macro_export] +macro_rules! define_proc_macros { + ( + $( + $( #[$attr:meta] )* + pub fn $func:ident($input:ident: &str) -> String + $body:block + )+ + ) => { + $( + $( #[$attr] )* + #[proc_macro_derive($func)] + pub fn $func(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { + // Use another function to hide this one’s local variables from $block + #[inline] + fn implementation($input: &str) -> String $body + + let input = input.to_string(); + let mut normalized = String::with_capacity(input.len()); + for piece in input.split_whitespace() { + normalized.push_str(piece); + normalized.push(' '); + } + + let prefix = "#[allow(unused)] enum Dummy { Input = (0, stringify!("; + let suffix = ")).0, } "; + assert!(normalized.starts_with(prefix), "Unexpected proc_macro_derive input {:?}", input); + assert!(normalized.ends_with(suffix), "Unexpected proc_macro_derive input {:?}", input); + + let start = prefix.len(); + let end = normalized.len() - suffix.len(); + let output = implementation(&normalized[start..end]); + output.parse().unwrap() + } + )+ + } +} + +#[macro_export] +macro_rules! define_invoke_proc_macro { + ($macro_name: ident) => { + #[macro_export] + macro_rules! $macro_name { + ($proc_macro_name: ident ! $paren: tt) => { + #[derive($proc_macro_name)] + #[allow(unused)] + enum Dummy { + Input = (0, stringify! $paren ).0 + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 32c13a89..71e381f9 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; +#[macro_use] extern crate procedural_masquarade; extern crate phf; #[cfg(test)] extern crate encoding_rs; #[cfg(test)] extern crate tempdir; diff --git a/src/macros.rs b/src/macros.rs index f1831d31..3f6752dc 100644 --- a/src/macros.rs +++ b/src/macros.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/. */ +define_invoke_proc_macro!(cssparser_internal__invoke_proc_macro); /// Expands to an expression equivalent to a `match` with string patterns, /// but matching is case-insensitive in the ASCII range. @@ -37,7 +38,7 @@ macro_rules! match_ignore_ascii_case { ( $input:expr, $( $match_body:tt )* ) => { { - cssparser_internal__pretend_proc_macro! { + cssparser_internal__invoke_proc_macro! { cssparser_internal__assert_ascii_lowercase__max_len!( $( $match_body )* ) } @@ -86,12 +87,12 @@ macro_rules! match_ignore_ascii_case { macro_rules! ascii_case_insensitive_phf_map { ($name: ident -> $ValueType: ty = { $( $key: expr => $value: expr, )* }) => { fn $name(input: &str) -> Option<&'static $ValueType> { - cssparser_internal__pretend_proc_macro! { + cssparser_internal__invoke_proc_macro! { cssparser_internal__phf_map!( ($ValueType) $( $key ($value) )+ ) } { - cssparser_internal__pretend_proc_macro! { + cssparser_internal__invoke_proc_macro! { cssparser_internal__max_len!( $( $key )+ ) } // MAX_LENGTH is generated by cssparser_internal__max_len @@ -125,21 +126,6 @@ macro_rules! cssparser_internal__to_lowercase { } } -/// 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.** -#[macro_export] -#[doc(hidden)] -macro_rules! cssparser_internal__pretend_proc_macro { - ($macro_name: ident ! ( $($tt: tt)* )) => { - #[derive($macro_name)] - #[allow(unused)] - enum Dummy { - Input = (0, stringify!( $( $tt )* )).0 - } - } -} - /// 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.** From ba6d9430549a5cc4285837c5e8c5fd728443cf1d Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 23:00:25 +0100 Subject: [PATCH 09/19] Remove requirements for users to import other crates. --- macros/lib.rs | 3 ++- src/lib.rs | 8 +++++++- src/macros.rs | 10 +--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index b72de5a5..662b6fef 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -67,12 +67,13 @@ define_proc_macros! { }).collect(); let mut map = phf_codegen::Map::new(); + map.phf_path("::cssparser::phf"); for &(ref key, ref value) in &pairs { map.entry(&**key, &**value); } let mut tokens = quote! { - static MAP: ::phf::Map<&'static str, #value_type> = + static MAP: ::cssparser::phf::Map<&'static str, #value_type> = }; let mut initializer_bytes = Vec::new(); map.build(&mut initializer_bytes).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 71e381f9..51b47b55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,13 +71,15 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser) #[macro_use] extern crate cssparser_macros; #[macro_use] extern crate matches; #[macro_use] extern crate procedural_masquarade; -extern crate phf; +pub extern crate 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}; @@ -90,8 +92,12 @@ pub use serializer::{ToCss, CssStringWriter, serialize_identifier, serialize_str pub use parser::{Parser, Delimiter, Delimiters, SourcePosition}; pub use unicode_range::UnicodeRange; +// For macros #[doc(hidden)] pub use macros::_internal__to_lowercase; +// For macros when used in this crate. Unsure how $crate works with procedural-masquarade. +mod cssparser { pub use phf; } + #[macro_use] mod macros; diff --git a/src/macros.rs b/src/macros.rs index 3f6752dc..2d9672d5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -7,16 +7,12 @@ define_invoke_proc_macro!(cssparser_internal__invoke_proc_macro); /// 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.) +/// 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 _ = @@ -62,14 +58,10 @@ macro_rules! match_ignore_ascii_case { /// and returns a reference to the corresponding value. /// Matching is case-insensitive in the ASCII range. /// -/// The `phf` and `cssparser_macros` crates must also be imported at the crate root -/// /// ## 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 /// From b1b6ed9a80e305074668bbd2565af4da23a6584c Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 27 Feb 2017 23:18:55 +0100 Subject: [PATCH 10/19] =?UTF-8?q?Clean=20up=20macros=E2=80=99=20doc-commen?= =?UTF-8?q?ts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macros/lib.rs | 21 +++++++++++++++------ src/macros.rs | 21 +++++++++++---------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index 662b6fef..a4d6ceaa 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -11,8 +11,12 @@ extern crate syn; use std::ascii::AsciiExt; define_proc_macros! { - /// Panic if any string contains ASCII uppercase letters. - /// Emit a `MAX_LENGTH` constant with the length of the longest string. + /// 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(); @@ -45,17 +49,22 @@ define_proc_macros! { })) } - /// Emit a `MAX_LENGTH` constant with the length of the longest string. + /// 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())) } + /// 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 ::phf::Map<&'static str, $ValueType> = …; + /// static MAP: &'static ::cssparser::phf::Map<&'static str, $ValueType> = …; /// ``` - /// - /// Map keys are ASCII-lowercased. #[allow(non_snake_case)] pub fn cssparser_internal__phf_map(input: &str) -> String { let token_trees = syn::parse_token_trees(input).unwrap(); diff --git a/src/macros.rs b/src/macros.rs index 2d9672d5..2ee0f866 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -2,10 +2,11 @@ * 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-masquarade` crate. define_invoke_proc_macro!(cssparser_internal__invoke_proc_macro); -/// Expands to an expression equivalent to a `match` with string patterns, -/// but matching is case-insensitive in the ASCII range. +/// 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.) /// @@ -63,7 +64,7 @@ macro_rules! match_ignore_ascii_case { /// ```rust /// #[macro_use] extern crate cssparser; /// -/// # fn main() {} // Make doctest not wrap everythig in its own main +/// # 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! { @@ -99,18 +100,18 @@ macro_rules! ascii_case_insensitive_phf_map { /// /// **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`. +/// 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, $MAX_LENGTH: expr => $output: ident) => { + ($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; $MAX_LENGTH] = unsafe { + let mut buffer: [u8; $BUFFER_SIZE] = unsafe { ::std::mem::uninitialized() }; let input: &str = $input; @@ -122,8 +123,8 @@ macro_rules! cssparser_internal__to_lowercase { /// /// **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. +/// 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> { From eb5c5bf24deeb3f5218ca097ba5a32e4b55855e8 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 00:41:59 +0100 Subject: [PATCH 11/19] Document procedural-masquarade --- procedural-masquarade/lib.rs | 206 +++++++++++++++++++++++++++++++++-- 1 file changed, 198 insertions(+), 8 deletions(-) diff --git a/procedural-masquarade/lib.rs b/procedural-masquarade/lib.rs index e6d443a0..ed73c6a3 100644 --- a/procedural-masquarade/lib.rs +++ b/procedural-masquarade/lib.rs @@ -1,16 +1,183 @@ +//! # 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_masquarade; +//! 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 `ProceduralMasquaradeDummyType`, +//! 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 indirection is necessary because re-exporting `macro_rules!` macros doesn’t work. +///// Without it, when a library `libfoo` defines a macro whose expansion uses directly +///// a macro + +/// 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 $func:ident($input:ident: &str) -> String - $body:block + pub fn $proc_macro_name: ident ($input: ident : &str) -> String + $body: block )+ ) => { $( $( #[$attr] )* - #[proc_macro_derive($func)] - pub fn $func(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { + #[proc_macro_derive($proc_macro_name)] + pub fn $proc_macro_name(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { // Use another function to hide this one’s local variables from $block #[inline] fn implementation($input: &str) -> String $body @@ -22,10 +189,10 @@ macro_rules! define_proc_macros { normalized.push(' '); } - let prefix = "#[allow(unused)] enum Dummy { Input = (0, stringify!("; + let prefix = "#[allow(unused)] enum ProceduralMasqueradeDummyType { Input = (0, stringify!("; let suffix = ")).0, } "; - assert!(normalized.starts_with(prefix), "Unexpected proc_macro_derive input {:?}", input); - assert!(normalized.ends_with(suffix), "Unexpected proc_macro_derive input {:?}", input); + assert!(normalized.starts_with(prefix), "expected prefix not found in {:?}", input); + assert!(normalized.ends_with(suffix), "expected suffix not found in {:?}", input); let start = prefix.len(); let end = normalized.len() - suffix.len(); @@ -36,6 +203,9 @@ macro_rules! define_proc_macros { } } +/// 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) => { @@ -44,7 +214,27 @@ macro_rules! define_invoke_proc_macro { ($proc_macro_name: ident ! $paren: tt) => { #[derive($proc_macro_name)] #[allow(unused)] - enum Dummy { + enum ProceduralMasquaradeDummyType { + // 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 } } From 3e0524c5305cb895fd26042eecbe3a1a2b530d2a Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 00:49:57 +0100 Subject: [PATCH 12/19] English spelling --- Cargo.toml | 4 ++-- macros/Cargo.toml | 2 +- macros/lib.rs | 2 +- {procedural-masquarade => procedural-masquerade}/Cargo.toml | 4 ++-- {procedural-masquarade => procedural-masquerade}/lib.rs | 6 +++--- src/lib.rs | 4 ++-- src/macros.rs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) rename {procedural-masquarade => procedural-masquerade}/Cargo.toml (75%) rename {procedural-masquarade => procedural-masquerade}/lib.rs (98%) diff --git a/Cargo.toml b/Cargo.toml index 035f1a7c..ecc71727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ cssparser-macros = {path = "./macros", version = "0.2"} heapsize = {version = "0.3", optional = true} matches = "0.1" phf = "0.7" -procedural-masquarade = {path = "./procedural-masquarade", version = "0.1"} +procedural-masquerade = {path = "./procedural-masquerade", version = "0.1"} serde = {version = "0.9", optional = true} [build-dependencies] @@ -36,4 +36,4 @@ bench = [] dummy_match_byte = [] [workspace] -members = [".", "./macros", "./procedural-masquarade"] +members = [".", "./macros", "./procedural-masquerade"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 45c17cfc..3dd61a72 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,7 +12,7 @@ path = "lib.rs" proc-macro = true [dependencies] -procedural-masquarade = {path = "../procedural-masquarade", version = "0.1"} +procedural-masquerade = {path = "../procedural-masquerade", version = "0.1"} phf_codegen = "0.7" quote = "0.3.14" syn = {version = "0.11.8", features = ["full"]} diff --git a/macros/lib.rs b/macros/lib.rs index a4d6ceaa..96eb1ed6 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -2,7 +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_masquarade; +#[macro_use] extern crate procedural_masquerade; extern crate phf_codegen; extern crate proc_macro; #[macro_use] extern crate quote; diff --git a/procedural-masquarade/Cargo.toml b/procedural-masquerade/Cargo.toml similarity index 75% rename from procedural-masquarade/Cargo.toml rename to procedural-masquerade/Cargo.toml index 2f7d586f..77441438 100644 --- a/procedural-masquarade/Cargo.toml +++ b/procedural-masquerade/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "procedural-masquarade" +name = "procedural-masquerade" version = "0.1.0" authors = ["Simon Sapin "] description = "macro_rules for making proc_macro_derive pretending to be proc_macro" -documentation = "https://docs.rs/procedural-masquarade/" +documentation = "https://docs.rs/procedural-masquerade/" repository = "https://github.com/servo/rust-cssparser" license = "MIT/Apache-2.0" diff --git a/procedural-masquarade/lib.rs b/procedural-masquerade/lib.rs similarity index 98% rename from procedural-masquarade/lib.rs rename to procedural-masquerade/lib.rs index ed73c6a3..9e8d9029 100644 --- a/procedural-masquarade/lib.rs +++ b/procedural-masquerade/lib.rs @@ -50,7 +50,7 @@ //! but not expressions directly. //! //! ```rust -//! #[macro_use] extern crate procedural_masquarade; +//! #[macro_use] extern crate procedural_masquerade; //! extern crate proc_macro; //! //! define_proc_macros! { @@ -117,7 +117,7 @@ //! 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 `ProceduralMasquaradeDummyType`, +//! 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 @@ -214,7 +214,7 @@ macro_rules! define_invoke_proc_macro { ($proc_macro_name: ident ! $paren: tt) => { #[derive($proc_macro_name)] #[allow(unused)] - enum ProceduralMasquaradeDummyType { + enum ProceduralMasqueradeDummyType { // The magic happens here. // // We use an `enum` with an explicit discriminant diff --git a/src/lib.rs b/src/lib.rs index 51b47b55..20ac3f5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,7 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser) #[macro_use] extern crate cssparser_macros; #[macro_use] extern crate matches; -#[macro_use] extern crate procedural_masquarade; +#[macro_use] extern crate procedural_masquerade; pub extern crate phf; #[cfg(test)] extern crate encoding_rs; #[cfg(test)] extern crate tempdir; @@ -95,7 +95,7 @@ pub use unicode_range::UnicodeRange; // For macros #[doc(hidden)] pub use macros::_internal__to_lowercase; -// For macros when used in this crate. Unsure how $crate works with procedural-masquarade. +// For macros when used in this crate. Unsure how $crate works with procedural-masquerade. mod cssparser { pub use phf; } #[macro_use] diff --git a/src/macros.rs b/src/macros.rs index 2ee0f866..1af8b588 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -2,7 +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/. */ -/// See docs of the `procedural-masquarade` crate. +/// See docs of the `procedural-masquerade` crate. define_invoke_proc_macro!(cssparser_internal__invoke_proc_macro); /// Expands to a `match` expression with string patterns, From 7368ec1e0a6d6c967b6379b10a4453dfd089dcef Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 13:00:11 +0100 Subject: [PATCH 13/19] Remove leftover copy/pasted comment --- procedural-masquerade/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/procedural-masquerade/lib.rs b/procedural-masquerade/lib.rs index 9e8d9029..ca76a66f 100644 --- a/procedural-masquerade/lib.rs +++ b/procedural-masquerade/lib.rs @@ -156,11 +156,6 @@ //! and //! [`cssparser-macros`’s `macros/lib.rs](https://github.com/servo/rust-cssparser/blob/master/macros/lib.rs). -///// -///// This indirection is necessary because re-exporting `macro_rules!` macros doesn’t work. -///// Without it, when a library `libfoo` defines a macro whose expansion uses directly -///// a macro - /// This macro wraps `&str -> String` functions /// in custom `derive` implementations with `#[proc_macro_derive]`. /// From 157fca44bf1d2ca1fe16676f10777a7099fa6de2 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 13:22:10 +0100 Subject: [PATCH 14/19] Move common proc_macro_derive code into a function. --- procedural-masquerade/Cargo.toml | 1 + procedural-masquerade/lib.rs | 47 ++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/procedural-masquerade/Cargo.toml b/procedural-masquerade/Cargo.toml index 77441438..04ee1742 100644 --- a/procedural-masquerade/Cargo.toml +++ b/procedural-masquerade/Cargo.toml @@ -9,3 +9,4 @@ license = "MIT/Apache-2.0" [lib] path = "lib.rs" +doctest = false diff --git a/procedural-masquerade/lib.rs b/procedural-masquerade/lib.rs index ca76a66f..7fd0b775 100644 --- a/procedural-masquerade/lib.rs +++ b/procedural-masquerade/lib.rs @@ -172,32 +172,37 @@ macro_rules! define_proc_macros { $( $( #[$attr] )* #[proc_macro_derive($proc_macro_name)] - pub fn $proc_macro_name(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { - // Use another function to hide this one’s local variables from $block - #[inline] - fn implementation($input: &str) -> String $body - - let input = input.to_string(); - let mut normalized = String::with_capacity(input.len()); - for piece in input.split_whitespace() { - normalized.push_str(piece); - normalized.push(' '); - } - - let prefix = "#[allow(unused)] enum ProceduralMasqueradeDummyType { Input = (0, stringify!("; - let suffix = ")).0, } "; - assert!(normalized.starts_with(prefix), "expected prefix not found in {:?}", input); - assert!(normalized.ends_with(suffix), "expected suffix not found in {:?}", input); - - let start = prefix.len(); - let end = normalized.len() - suffix.len(); - let output = implementation(&normalized[start..end]); - output.parse().unwrap() + pub fn $proc_macro_name(derive_input: ::proc_macro::TokenStream) + -> ::proc_macro::TokenStream { + let $input = derive_input.to_string(); + let $input: &str = &$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) -> String { + let mut normalized = String::with_capacity(derive_input.len()); + for piece in derive_input.split_whitespace() { + normalized.push_str(piece); + normalized.push(' '); + } + + let prefix = "#[allow(unused)] enum ProceduralMasqueradeDummyType { Input = (0, stringify!("; + let suffix = ")).0, } "; + assert!(normalized.starts_with(prefix), "expected prefix not found in {:?}", derive_input); + assert!(normalized.ends_with(suffix), "expected suffix not found in {:?}", derive_input); + + let start = prefix.len(); + let end = normalized.len() - suffix.len(); + normalized[start..end].to_owned() +} + /// This macro expands to the definition of another macro (whose name is given as a parameter). /// /// See crate documentation for details. From 6c3665cbc8f85bc0d4f1944f21013ca3728e2c30 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 13:26:29 +0100 Subject: [PATCH 15/19] Use Tokens::into_string --- macros/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index 96eb1ed6..7ec857d3 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -88,13 +88,13 @@ define_proc_macros! { map.build(&mut initializer_bytes).unwrap(); tokens.append(::std::str::from_utf8(&initializer_bytes).unwrap()); tokens.append(";"); - tokens.to_string() + tokens.into_string() } } fn max_len>(lengths: I) -> String { let max_length = lengths.max().expect("expected at least one string"); - quote!( const MAX_LENGTH: usize = #max_length; ).to_string() + quote!( const MAX_LENGTH: usize = #max_length; ).into_string() } fn string_literal(token: &syn::TokenTree) -> &str { From 3ad1cbac797a40782e8c35dedbf222928cbd2750 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 13:35:01 +0100 Subject: [PATCH 16/19] =?UTF-8?q?Don=E2=80=99t=20include=20invoke=5Fproc?= =?UTF-8?q?=5Fmacro=20in=20docs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- procedural-masquerade/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/procedural-masquerade/lib.rs b/procedural-masquerade/lib.rs index 7fd0b775..4e255d22 100644 --- a/procedural-masquerade/lib.rs +++ b/procedural-masquerade/lib.rs @@ -209,6 +209,8 @@ pub fn _extract_input(derive_input: &str) -> String { #[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) => { From 6ffb7eb573be47b9c43c9815d464703edbb2de78 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 13:48:32 +0100 Subject: [PATCH 17/19] =?UTF-8?q?procedural-masquerade:=20don=E2=80=99t=20?= =?UTF-8?q?normalize=20whitespace=20in=20string=20literals.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- procedural-masquerade/Cargo.toml | 2 +- procedural-masquerade/lib.rs | 31 ++++++++++++++++++------------- src/tests.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/procedural-masquerade/Cargo.toml b/procedural-masquerade/Cargo.toml index 04ee1742..d1cdccc4 100644 --- a/procedural-masquerade/Cargo.toml +++ b/procedural-masquerade/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "procedural-masquerade" -version = "0.1.0" +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/" diff --git a/procedural-masquerade/lib.rs b/procedural-masquerade/lib.rs index 4e255d22..c2bd6b48 100644 --- a/procedural-masquerade/lib.rs +++ b/procedural-masquerade/lib.rs @@ -175,7 +175,7 @@ macro_rules! define_proc_macros { pub fn $proc_macro_name(derive_input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { let $input = derive_input.to_string(); - let $input: &str = &$crate::_extract_input(&$input); + let $input = $crate::_extract_input(&$input); $body.parse().unwrap() } )+ @@ -186,21 +186,26 @@ macro_rules! 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) -> String { - let mut normalized = String::with_capacity(derive_input.len()); - for piece in derive_input.split_whitespace() { - normalized.push_str(piece); - normalized.push(' '); +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()..]; } - let prefix = "#[allow(unused)] enum ProceduralMasqueradeDummyType { Input = (0, stringify!("; - let suffix = ")).0, } "; - assert!(normalized.starts_with(prefix), "expected prefix not found in {:?}", derive_input); - assert!(normalized.ends_with(suffix), "expected suffix not found in {:?}", derive_input); + 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]; + } - let start = prefix.len(); - let end = normalized.len() - suffix.len(); - normalized[start..end].to_owned() + input } /// This macro expands to the definition of another macro (whose name is given as a parameter). 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"), + } +} From 8c1dd28b4a18dc5832aee428aedb78edcaba53ed Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 14:03:11 +0100 Subject: [PATCH 18/19] ascii_case_insensitive_phf_map: accept k/v pairs without a trailing comma --- src/macros.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index 1af8b588..6b75d46b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -78,6 +78,9 @@ macro_rules! match_ignore_ascii_case { /// } #[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! { From 4a46a540f4c35b109027b965ed657646ebc11fda Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Feb 2017 15:43:00 +0100 Subject: [PATCH 19/19] Hide re-exported phf from docs --- macros/lib.rs | 4 ++-- src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/macros/lib.rs b/macros/lib.rs index 7ec857d3..1e6f3590 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -76,13 +76,13 @@ define_proc_macros! { }).collect(); let mut map = phf_codegen::Map::new(); - map.phf_path("::cssparser::phf"); + map.phf_path("::cssparser::_internal__phf"); for &(ref key, ref value) in &pairs { map.entry(&**key, &**value); } let mut tokens = quote! { - static MAP: ::cssparser::phf::Map<&'static str, #value_type> = + static MAP: ::cssparser::_internal__phf::Map<&'static str, #value_type> = }; let mut initializer_bytes = Vec::new(); map.build(&mut initializer_bytes).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 20ac3f5a..c438b015 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser) #[macro_use] extern crate cssparser_macros; #[macro_use] extern crate matches; #[macro_use] extern crate procedural_masquerade; -pub extern crate phf; +#[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; @@ -96,7 +96,7 @@ pub use unicode_range::UnicodeRange; #[doc(hidden)] pub use macros::_internal__to_lowercase; // For macros when used in this crate. Unsure how $crate works with procedural-masquerade. -mod cssparser { pub use phf; } +mod cssparser { pub use _internal__phf; } #[macro_use] mod macros;