servo: Merge #19578 - Allow deriving Parse on keywords (from emilio:parse-keyword); r=Manishearth,canaltinova
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 15 Dec 2017 14:55:49 -0600
changeset 448332 c4cf8f4683114454167cc8db9fc352f0b634306b
parent 448331 dac7620a6ecc830ea59f64e83c97c64c416271bb
child 448333 9ea232c87a7dd818363e7703b744b2612d4e6e58
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth, canaltinova
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #19578 - Allow deriving Parse on keywords (from emilio:parse-keyword); r=Manishearth,canaltinova This makes patches like #19576 much easier. Source-Repo: https://github.com/servo/servo Source-Revision: e631d167bf28676e45e37f418b4282fcfba98c1e
servo/components/style/parser.rs
servo/components/style/values/specified/background.rs
servo/components/style/values/specified/position.rs
servo/components/style_derive/cg.rs
servo/components/style_derive/lib.rs
servo/components/style_derive/parse.rs
servo/components/style_derive/to_css.rs
--- a/servo/components/style/parser.rs
+++ b/servo/components/style/parser.rs
@@ -144,18 +144,20 @@ impl<'a> ParserContext<'a> {
 // XXXManishearth Replace all specified value parse impls with impls of this
 // trait. This will make it easy to write more generic values in the future.
 /// A trait to abstract parsing of a specified value given a `ParserContext` and
 /// CSS input.
 pub trait Parse : Sized {
     /// Parse a value of this type.
     ///
     /// Returns an error on failure.
-    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                     -> Result<Self, ParseError<'i>>;
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>>;
 }
 
 impl<T> Parse for Vec<T>
 where
     T: Parse + OneOrMoreSeparated,
     <T as OneOrMoreSeparated>::S: Separator,
 {
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
--- a/servo/components/style/values/specified/background.rs
+++ b/servo/components/style/values/specified/background.rs
@@ -41,21 +41,23 @@ impl BackgroundSize {
         GenericBackgroundSize::Explicit {
             width: LengthOrPercentageOrAuto::Auto,
             height: LengthOrPercentageOrAuto::Auto,
         }
     }
 }
 
 /// One of the keywords for `background-repeat`.
-define_css_keyword_enum! { RepeatKeyword:
-    "repeat" => Repeat,
-    "space" => Space,
-    "round" => Round,
-    "no-repeat" => NoRepeat
+#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss)]
+#[allow(missing_docs)]
+pub enum RepeatKeyword {
+    Repeat,
+    Space,
+    Round,
+    NoRepeat,
 }
 
 /// The specified value for the `background-repeat` property.
 ///
 /// https://drafts.csswg.org/css-backgrounds/#the-background-repeat
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
 pub enum BackgroundRepeat {
     /// `repeat-x`
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -37,27 +37,31 @@ pub enum PositionComponent<S> {
     /// `center`
     Center,
     /// `<lop>`
     Length(LengthOrPercentage),
     /// `<side> <lop>?`
     Side(S, Option<LengthOrPercentage>),
 }
 
-define_css_keyword_enum! { X:
-    "left" => Left,
-    "right" => Right,
+/// A keyword for the X direction.
+#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss)]
+#[allow(missing_docs)]
+pub enum X {
+    Left,
+    Right,
 }
-add_impls_for_keyword_enum!(X);
 
-define_css_keyword_enum! { Y:
-    "top" => Top,
-    "bottom" => Bottom,
+/// A keyword for the Y direction.
+#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss)]
+#[allow(missing_docs)]
+pub enum Y {
+    Top,
+    Bottom,
 }
-add_impls_for_keyword_enum!(Y);
 
 impl Parse for Position {
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         Self::parse_quirky(context, input, AllowQuirks::No)
     }
 }
 
 impl Position {
--- a/servo/components/style_derive/cg.rs
+++ b/servo/components/style_derive/cg.rs
@@ -401,8 +401,46 @@ pub fn where_predicate(
             PolyTraitRef {
                 bound_lifetimes: vec![],
                 trait_ref: trait_ref(trait_path, trait_output),
             },
             TraitBoundModifier::None
         )],
     })
 }
+
+/// Transforms "FooBar" to "foo-bar".
+///
+/// If the first Camel segment is "Moz", "Webkit", or "Servo", the result string
+/// is prepended with "-".
+pub fn to_css_identifier(mut camel_case: &str) -> String {
+    camel_case = camel_case.trim_right_matches('_');
+    let mut first = true;
+    let mut result = String::with_capacity(camel_case.len());
+    while let Some(segment) = split_camel_segment(&mut camel_case) {
+        if first {
+            match segment {
+                "Moz" | "Webkit" | "Servo" => first = false,
+                _ => {},
+            }
+        }
+        if !first {
+            result.push_str("-");
+        }
+        first = false;
+        result.push_str(&segment.to_lowercase());
+    }
+    result
+}
+
+/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
+fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
+    let index = match camel_case.chars().next() {
+        None => return None,
+        Some(ch) => ch.len_utf8(),
+    };
+    let end_position = camel_case[index..]
+        .find(char::is_uppercase)
+        .map_or(camel_case.len(), |pos| index + pos);
+    let result = &camel_case[..end_position];
+    *camel_case = &camel_case[end_position..];
+    Some(result)
+}
--- a/servo/components/style_derive/lib.rs
+++ b/servo/components/style_derive/lib.rs
@@ -1,23 +1,26 @@
 /* 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/. */
 
+#![recursion_limit = "128"]
+
 #[macro_use] extern crate darling;
 extern crate proc_macro;
 #[macro_use] extern crate quote;
 extern crate syn;
 extern crate synstructure;
 
 use proc_macro::TokenStream;
 
 mod animate;
 mod cg;
 mod compute_squared_distance;
+mod parse;
 mod to_animated_value;
 mod to_animated_zero;
 mod to_computed_value;
 mod to_css;
 
 #[proc_macro_derive(Animate, attributes(animate, animation))]
 pub fn derive_animate(stream: TokenStream) -> TokenStream {
     let input = syn::parse_derive_input(&stream.to_string()).unwrap();
@@ -31,16 +34,22 @@ pub fn derive_compute_squared_distance(s
 }
 
 #[proc_macro_derive(ToAnimatedValue)]
 pub fn derive_to_animated_value(stream: TokenStream) -> TokenStream {
     let input = syn::parse_derive_input(&stream.to_string()).unwrap();
     to_animated_value::derive(input).to_string().parse().unwrap()
 }
 
+#[proc_macro_derive(Parse)]
+pub fn derive_parse(stream: TokenStream) -> TokenStream {
+    let input = syn::parse_derive_input(&stream.to_string()).unwrap();
+    parse::derive(input).to_string().parse().unwrap()
+}
+
 #[proc_macro_derive(ToAnimatedZero, attributes(animation))]
 pub fn derive_to_animated_zero(stream: TokenStream) -> TokenStream {
     let input = syn::parse_derive_input(&stream.to_string()).unwrap();
     to_animated_zero::derive(input).to_string().parse().unwrap()
 }
 
 #[proc_macro_derive(ToComputedValue, attributes(compute))]
 pub fn derive_to_computed_value(stream: TokenStream) -> TokenStream {
new file mode 100644
--- /dev/null
+++ b/servo/components/style_derive/parse.rs
@@ -0,0 +1,74 @@
+/* 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/. */
+
+use cg;
+use quote::Tokens;
+use syn::DeriveInput;
+use synstructure;
+
+pub fn derive(input: DeriveInput) -> Tokens {
+    let name = &input.ident;
+
+    let mut match_body = quote! {};
+
+    let style = synstructure::BindStyle::Ref.into();
+    synstructure::each_variant(&input, &style, |bindings, variant| {
+        assert!(
+            bindings.is_empty(),
+            "Parse is only supported for single-variant enums for now"
+        );
+
+        let identifier = cg::to_css_identifier(variant.ident.as_ref());
+        match_body = quote! {
+            #match_body
+            #identifier => Ok(#name::#variant),
+        }
+    });
+
+    let parse_trait_impl = quote! {
+        impl ::parser::Parse for #name {
+            #[inline]
+            fn parse<'i, 't>(
+                _: &::parser::ParserContext,
+                input: &mut ::cssparser::Parser<'i, 't>,
+            ) -> Result<Self, ::style_traits::ParseError<'i>> {
+                Self::parse(input)
+            }
+        }
+    };
+
+    // TODO(emilio): It'd be nice to get rid of these, but that makes the
+    // conversion harder...
+    let methods_impl = quote! {
+        impl #name {
+            /// Parse this keyword.
+            #[inline]
+            pub fn parse<'i, 't>(
+                input: &mut ::cssparser::Parser<'i, 't>,
+            ) -> Result<Self, ::style_traits::ParseError<'i>> {
+                let location = input.current_source_location();
+                let ident = input.expect_ident()?;
+                Self::from_ident(ident.as_ref()).map_err(|()| {
+                    location.new_unexpected_token_error(
+                        ::cssparser::Token::Ident(ident.clone())
+                    )
+                })
+            }
+
+            /// Parse this keyword from a string slice.
+            #[inline]
+            pub fn from_ident(ident: &str) -> Result<Self, ()> {
+                match_ignore_ascii_case! { ident,
+                    #match_body
+                    _ => Err(()),
+                }
+            }
+        }
+    };
+
+    quote! {
+        #parse_trait_impl
+        #methods_impl
+    }
+}
--- a/servo/components/style_derive/to_css.rs
+++ b/servo/components/style_derive/to_css.rs
@@ -11,17 +11,17 @@ pub fn derive(input: DeriveInput) -> Tok
     let name = &input.ident;
     let trait_path = &["style_traits", "ToCss"];
     let (impl_generics, ty_generics, mut where_clause) =
         cg::trait_parts(&input, trait_path);
 
     let input_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
     let style = synstructure::BindStyle::Ref.into();
     let match_body = synstructure::each_variant(&input, &style, |bindings, variant| {
-        let mut identifier = to_css_identifier(variant.ident.as_ref());
+        let mut identifier = cg::to_css_identifier(variant.ident.as_ref());
         let variant_attrs = cg::parse_variant_attrs::<CssVariantAttrs>(variant);
         let separator = if variant_attrs.comma { ", " } else { " " };
 
         if variant_attrs.dimension {
             assert_eq!(bindings.len(), 1);
             assert!(!variant_attrs.function, "That makes no sense");
         }
 
@@ -113,43 +113,8 @@ struct CssInputAttrs {
 #[darling(attributes(css), default)]
 #[derive(Default, FromVariant)]
 struct CssVariantAttrs {
     function: bool,
     iterable: bool,
     comma: bool,
     dimension: bool,
 }
-
-/// Transforms "FooBar" to "foo-bar".
-///
-/// If the first Camel segment is "Moz" or "Webkit", the result string
-/// is prepended with "-".
-fn to_css_identifier(mut camel_case: &str) -> String {
-    camel_case = camel_case.trim_right_matches('_');
-    let mut first = true;
-    let mut result = String::with_capacity(camel_case.len());
-    while let Some(segment) = split_camel_segment(&mut camel_case) {
-        if first {
-            match segment {
-                "Moz" | "Webkit" => first = false,
-                _ => {},
-            }
-        }
-        if !first {
-            result.push_str("-");
-        }
-        first = false;
-        result.push_str(&segment.to_lowercase());
-    }
-    result
-}
-
-/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
-fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
-    let index = camel_case.chars().next()?.len_utf8();
-    let end_position = camel_case[index..]
-        .find(char::is_uppercase)
-        .map_or(camel_case.len(), |pos| index + pos);
-    let result = &camel_case[..end_position];
-    *camel_case = &camel_case[end_position..];
-    Some(result)
-}