/* 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, pub condition: Option, } #[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, 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::(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::(&variant.ast()); if css_variant_attrs.skip { continue; } effective_variants += 1; let parse_attrs = cg::parse_variant_attrs_from_ast::(&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> { #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> { 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_keywords _ => Err(()), } } } }; quote! { #parse_trait_impl #methods_impl } }