From 7036cb0077d7b9c051822e4417bc66803f81dea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sun, 12 Nov 2017 05:43:53 +0100 Subject: [PATCH] Allow deriving Parse for keywords. --- components/style/parser.rs | 6 ++- components/style_derive/cg.rs | 38 ++++++++++++++++ components/style_derive/lib.rs | 9 ++++ components/style_derive/parse.rs | 74 +++++++++++++++++++++++++++++++ components/style_derive/to_css.rs | 37 +--------------- 5 files changed, 126 insertions(+), 38 deletions(-) create mode 100644 components/style_derive/parse.rs diff --git a/components/style/parser.rs b/components/style/parser.rs index 1b1d4151600..df8551bff3b 100644 --- a/components/style/parser.rs +++ b/components/style/parser.rs @@ -149,8 +149,10 @@ 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>; + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result>; } impl Parse for Vec diff --git a/components/style_derive/cg.rs b/components/style_derive/cg.rs index 6937d5836d2..52adb15a582 100644 --- a/components/style_derive/cg.rs +++ b/components/style_derive/cg.rs @@ -406,3 +406,41 @@ pub fn where_predicate( )], }) } + +/// Transforms "FooBar" to "foo-bar". +/// +/// If the first Camel segment is "Moz" or "Webkit", 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" => 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) +} diff --git a/components/style_derive/lib.rs b/components/style_derive/lib.rs index 6e87cc4972a..cbe33a3d199 100644 --- a/components/style_derive/lib.rs +++ b/components/style_derive/lib.rs @@ -2,6 +2,8 @@ * 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; @@ -13,6 +15,7 @@ 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; @@ -36,6 +39,12 @@ pub fn derive_to_animated_value(stream: TokenStream) -> TokenStream { 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(); diff --git a/components/style_derive/parse.rs b/components/style_derive/parse.rs new file mode 100644 index 00000000000..eb7d6913743 --- /dev/null +++ b/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::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> { + 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 { + match_ignore_ascii_case! { ident, + #match_body + _ => Err(()), + } + } + } + }; + + quote! { + #parse_trait_impl + #methods_impl + } +} diff --git a/components/style_derive/to_css.rs b/components/style_derive/to_css.rs index ed34841c7bf..02a44617bde 100644 --- a/components/style_derive/to_css.rs +++ b/components/style_derive/to_css.rs @@ -16,7 +16,7 @@ pub fn derive(input: DeriveInput) -> Tokens { let input_attrs = cg::parse_input_attrs::(&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::(variant); let separator = if variant_attrs.comma { ", " } else { " " }; @@ -118,38 +118,3 @@ struct CssVariantAttrs { 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) -}