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 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

Allow deriving Parse for keywords.

  • Loading branch information
emilio committed Dec 15, 2017
commit 7036cb0077d7b9c051822e4417bc66803f81dea8
@@ -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>
@@ -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,

This comment has been minimized.

@canova

canova Dec 15, 2017

Member

We should also add "Servo" here.

This comment has been minimized.

@emilio

emilio Dec 15, 2017

Author Member

This is just moving code around, but yeah, you're right.

_ => {},
}
}
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.