summaryrefslogtreecommitdiffstats
path: root/servo/components/style_derive/parse.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--servo/components/style_derive/parse.rs323
1 files changed, 323 insertions, 0 deletions
diff --git a/servo/components/style_derive/parse.rs b/servo/components/style_derive/parse.rs
new file mode 100644
index 0000000000..b1a1213435
--- /dev/null
+++ b/servo/components/style_derive/parse.rs
@@ -0,0 +1,323 @@
+/* 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
+ }
+}