Source code

Revision control

Copy as Markdown

Other Tools

/* 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 https://mozilla.org/MPL/2.0/. */
use crate::to_css::{CssBitflagAttrs, CssVariantAttrs};
use derive_common::cg;
use proc_macro2::{Span, TokenStream};
use quote::TokenStreamExt;
use syn::{self, DeriveInput, Ident, Path};
use synstructure::{Structure, VariantInfo};
#[derive(Default, FromVariant)]
#[darling(attributes(parse), default)]
pub struct ParseVariantAttrs {
pub aliases: Option<String>,
pub condition: Option<Path>,
}
#[derive(Default, FromField)]
#[darling(attributes(parse), default)]
pub struct ParseFieldAttrs {
field_bound: bool,
}
fn parse_bitflags(bitflags: &CssBitflagAttrs) -> TokenStream {
let mut match_arms = TokenStream::new();
for (rust_name, css_name) in bitflags.single_flags() {
let rust_ident = Ident::new(&rust_name, Span::call_site());
match_arms.append_all(quote! {
#css_name if result.is_empty() => {
single_flag = true;
Self::#rust_ident
},
});
}
for (rust_name, css_name) in bitflags.mixed_flags() {
let rust_ident = Ident::new(&rust_name, Span::call_site());
match_arms.append_all(quote! {
#css_name => Self::#rust_ident,
});
}
let mut validate_condition = quote! { !result.is_empty() };
if let Some(ref function) = bitflags.validate_mixed {
validate_condition.append_all(quote! {
&& #function(&mut result)
});
}
// NOTE(emilio): this loop has this weird structure because we run this code
// to parse stuff like text-decoration-line in the text-decoration
// shorthand, so we need to be a bit careful that we don't error if we don't
// consume the whole thing because we find an invalid identifier or other
// kind of token. Instead, we should leave it unconsumed.
quote! {
let mut result = Self::empty();
loop {
let mut single_flag = false;
let flag: Result<_, style_traits::ParseError<'i>> = input.try_parse(|input| {
Ok(try_match_ident_ignore_ascii_case! { input,
#match_arms
})
});
let flag = match flag {
Ok(flag) => flag,
Err(..) => break,
};
if single_flag {
return Ok(flag);
}
if result.intersects(flag) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
result.insert(flag);
}
if #validate_condition {
Ok(result)
} else {
Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError))
}
}
}
fn parse_non_keyword_variant(
where_clause: &mut Option<syn::WhereClause>,
name: &syn::Ident,
variant: &VariantInfo,
variant_attrs: &CssVariantAttrs,
parse_attrs: &ParseVariantAttrs,
skip_try: bool,
) -> TokenStream {
let bindings = variant.bindings();
assert!(parse_attrs.aliases.is_none());
assert!(variant_attrs.function.is_none());
assert!(variant_attrs.keyword.is_none());
assert_eq!(
bindings.len(),
1,
"We only support deriving parse for simple variants"
);
let variant_name = &variant.ast().ident;
let binding_ast = &bindings[0].ast();
let ty = &binding_ast.ty;
if let Some(ref bitflags) = variant_attrs.bitflags {
assert!(skip_try, "Should be the only variant");
assert!(
parse_attrs.condition.is_none(),
"Should be the only variant"
);
assert!(where_clause.is_none(), "Generic bitflags?");
return parse_bitflags(bitflags);
}
let field_attrs = cg::parse_field_attrs::<ParseFieldAttrs>(binding_ast);
if field_attrs.field_bound {
cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse));
}
let mut parse = if skip_try {
quote! {
let v = <#ty as crate::parser::Parse>::parse(context, input)?;
return Ok(#name::#variant_name(v));
}
} else {
quote! {
if let Ok(v) = input.try(|i| <#ty as crate::parser::Parse>::parse(context, i)) {
return Ok(#name::#variant_name(v));
}
}
};
if let Some(ref condition) = parse_attrs.condition {
parse = quote! {
if #condition(context) {
#parse
}
};
if skip_try {
// We're the last variant and we can fail to parse due to the
// condition clause. If that happens, we need to return an error.
parse = quote! {
#parse
Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError))
};
}
}
parse
}
pub fn derive(mut input: DeriveInput) -> TokenStream {
let mut where_clause = input.generics.where_clause.take();
for param in input.generics.type_params() {
cg::add_predicate(
&mut where_clause,
parse_quote!(#param: crate::parser::Parse),
);
}
let name = &input.ident;
let s = Structure::new(&input);
let mut saw_condition = false;
let mut match_keywords = quote! {};
let mut non_keywords = vec![];
let mut effective_variants = 0;
for variant in s.variants().iter() {
let css_variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&variant.ast());
if css_variant_attrs.skip {
continue;
}
effective_variants += 1;
let parse_attrs = cg::parse_variant_attrs_from_ast::<ParseVariantAttrs>(&variant.ast());
saw_condition |= parse_attrs.condition.is_some();
if !variant.bindings().is_empty() {
non_keywords.push((variant, css_variant_attrs, parse_attrs));
continue;
}
let identifier = cg::to_css_identifier(
&css_variant_attrs
.keyword
.unwrap_or_else(|| variant.ast().ident.to_string()),
);
let ident = &variant.ast().ident;
let condition = match parse_attrs.condition {
Some(ref p) => quote! { if #p(context) },
None => quote! {},
};
match_keywords.extend(quote! {
#identifier #condition => Ok(#name::#ident),
});
let aliases = match parse_attrs.aliases {
Some(aliases) => aliases,
None => continue,
};
for alias in aliases.split(',') {
match_keywords.extend(quote! {
#alias #condition => Ok(#name::#ident),
});
}
}
let needs_context = saw_condition || !non_keywords.is_empty();
let context_ident = if needs_context {
quote! { context }
} else {
quote! { _ }
};
let has_keywords = non_keywords.len() != effective_variants;
let mut parse_non_keywords = quote! {};
for (i, (variant, css_attrs, parse_attrs)) in non_keywords.iter().enumerate() {
let skip_try = !has_keywords && i == non_keywords.len() - 1;
let parse_variant = parse_non_keyword_variant(
&mut where_clause,
name,
variant,
css_attrs,
parse_attrs,
skip_try,
);
parse_non_keywords.extend(parse_variant);
}
let parse_body = if needs_context {
let parse_keywords = if has_keywords {
quote! {
let location = input.current_source_location();
let ident = input.expect_ident()?;
match_ignore_ascii_case! { &ident,
#match_keywords
_ => Err(location.new_unexpected_token_error(
cssparser::Token::Ident(ident.clone())
))
}
}
} else {
quote! {}
};
quote! {
#parse_non_keywords
#parse_keywords
}
} else {
quote! { Self::parse(input) }
};
let has_non_keywords = !non_keywords.is_empty();
input.generics.where_clause = where_clause;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let parse_trait_impl = quote! {
impl #impl_generics crate::parser::Parse for #name #ty_generics #where_clause {
#[inline]
fn parse<'i, 't>(
#context_ident: &crate::parser::ParserContext,
input: &mut cssparser::Parser<'i, 't>,
) -> Result<Self, style_traits::ParseError<'i>> {
#parse_body
}
}
};
if needs_context {
return parse_trait_impl;
}
assert!(!has_non_keywords);
// 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_keywords
_ => Err(()),
}
}
}
};
quote! {
#parse_trait_impl
#methods_impl
}
}