Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow deriving Parse on keywords. #19578

Merged
merged 4 commits into from Dec 15, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -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<Self, ParseError<'i>>;
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>>;
}

impl<T> Parse for Vec<T>
@@ -46,11 +46,13 @@ impl BackgroundSize {
}

/// 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.
@@ -42,17 +42,21 @@ pub enum PositionComponent<S> {
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>> {
@@ -406,3 +406,41 @@ pub fn where_predicate(
)],
})
}

/// 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)
}
@@ -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"]

This comment has been minimized.


#[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();
@@ -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
}
}
@@ -16,7 +16,7 @@ pub fn derive(input: DeriveInput) -> Tokens {
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 { " " };

@@ -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)
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.