tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

parse.rs (10384B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 use crate::cg;
      6 use crate::to_css::{CssBitflagAttrs, CssVariantAttrs};
      7 use proc_macro2::{Span, TokenStream};
      8 use quote::TokenStreamExt;
      9 use syn::{self, DeriveInput, Ident, Path};
     10 use synstructure::{Structure, VariantInfo};
     11 
     12 #[derive(Default, FromVariant)]
     13 #[darling(attributes(parse), default)]
     14 pub struct ParseVariantAttrs {
     15    pub aliases: Option<String>,
     16    pub condition: Option<Path>,
     17    pub parse_fn: Option<Path>,
     18 }
     19 
     20 #[derive(Default, FromField)]
     21 #[darling(attributes(parse), default)]
     22 pub struct ParseFieldAttrs {
     23    field_bound: bool,
     24 }
     25 
     26 fn parse_bitflags(bitflags: &CssBitflagAttrs) -> TokenStream {
     27    let mut match_arms = TokenStream::new();
     28    for (rust_name, css_name) in bitflags.single_flags() {
     29        let rust_ident = Ident::new(&rust_name, Span::call_site());
     30        match_arms.append_all(quote! {
     31            #css_name if result.is_empty() => {
     32                single_flag = true;
     33                Self::#rust_ident
     34            },
     35        });
     36    }
     37 
     38    for (rust_name, css_name) in bitflags.mixed_flags() {
     39        let rust_ident = Ident::new(&rust_name, Span::call_site());
     40        match_arms.append_all(quote! {
     41            #css_name => Self::#rust_ident,
     42        });
     43    }
     44 
     45    let mut validate_condition = quote! { !result.is_empty() };
     46    if let Some(ref function) = bitflags.validate_mixed {
     47        validate_condition.append_all(quote! {
     48            && #function(&mut result)
     49        });
     50    }
     51 
     52    // NOTE(emilio): this loop has this weird structure because we run this code
     53    // to parse stuff like text-decoration-line in the text-decoration
     54    // shorthand, so we need to be a bit careful that we don't error if we don't
     55    // consume the whole thing because we find an invalid identifier or other
     56    // kind of token. Instead, we should leave it unconsumed.
     57    quote! {
     58        let mut result = Self::empty();
     59        loop {
     60            let mut single_flag = false;
     61            let flag: Result<_, style_traits::ParseError<'i>> = input.try_parse(|input| {
     62                Ok(try_match_ident_ignore_ascii_case! { input,
     63                    #match_arms
     64                })
     65            });
     66 
     67            let flag = match flag {
     68                Ok(flag) => flag,
     69                Err(..) => break,
     70            };
     71 
     72            if single_flag {
     73                return Ok(flag);
     74            }
     75 
     76            if result.intersects(flag) {
     77                return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
     78            }
     79 
     80            result.insert(flag);
     81        }
     82        if #validate_condition {
     83            Ok(result)
     84        } else {
     85            Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError))
     86        }
     87    }
     88 }
     89 
     90 fn parse_non_keyword_variant(
     91    where_clause: &mut Option<syn::WhereClause>,
     92    variant: &VariantInfo,
     93    variant_attrs: &CssVariantAttrs,
     94    parse_attrs: &ParseVariantAttrs,
     95    skip_try: bool,
     96 ) -> TokenStream {
     97    let bindings = variant.bindings();
     98    assert!(parse_attrs.aliases.is_none());
     99    assert!(variant_attrs.function.is_none());
    100    assert!(variant_attrs.keyword.is_none());
    101    assert_eq!(
    102        bindings.len(),
    103        1,
    104        "We only support deriving parse for simple variants"
    105    );
    106    let binding_ast = &bindings[0].ast();
    107    let ty = &binding_ast.ty;
    108 
    109    if let Some(ref bitflags) = variant_attrs.bitflags {
    110        assert!(skip_try, "Should be the only variant");
    111        assert!(parse_attrs.parse_fn.is_none(), "should not be needed");
    112        assert!(
    113            parse_attrs.condition.is_none(),
    114            "Should be the only variant"
    115        );
    116        assert!(where_clause.is_none(), "Generic bitflags?");
    117        return parse_bitflags(bitflags);
    118    }
    119 
    120    let field_attrs = cg::parse_field_attrs::<ParseFieldAttrs>(binding_ast);
    121 
    122    if field_attrs.field_bound {
    123        cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse));
    124    }
    125 
    126    let mut parse = if let Some(ref parse_fn) = parse_attrs.parse_fn {
    127        quote! { #parse_fn(context, input) }
    128    } else {
    129        quote! { <#ty as crate::parser::Parse>::parse(context, input) }
    130    };
    131 
    132    let variant_name = &variant.ast().ident;
    133    let variant_name = match variant.prefix {
    134        Some(p) => quote! { #p::#variant_name },
    135        None => quote! { #variant_name },
    136    };
    137 
    138    parse = if skip_try {
    139        quote! {
    140            let v = #parse?;
    141            return Ok(#variant_name(v));
    142        }
    143    } else {
    144        quote! {
    145            if let Ok(v) = input.try_parse(|input| #parse) {
    146                return Ok(#variant_name(v));
    147            }
    148        }
    149    };
    150 
    151    if let Some(ref condition) = parse_attrs.condition {
    152        parse = quote! {
    153            if #condition(context) {
    154                #parse
    155            }
    156        };
    157 
    158        if skip_try {
    159            // We're the last variant and we can fail to parse due to the
    160            // condition clause. If that happens, we need to return an error.
    161            parse = quote! {
    162                #parse
    163                Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError))
    164            };
    165        }
    166    }
    167 
    168    parse
    169 }
    170 
    171 pub fn derive(mut input: DeriveInput) -> TokenStream {
    172    let mut where_clause = input.generics.where_clause.take();
    173    for param in input.generics.type_params() {
    174        cg::add_predicate(
    175            &mut where_clause,
    176            parse_quote!(#param: crate::parser::Parse),
    177        );
    178    }
    179 
    180    let name = &input.ident;
    181    let s = Structure::new(&input);
    182 
    183    let mut saw_condition = false;
    184    let mut match_keywords = quote! {};
    185    let mut non_keywords = vec![];
    186 
    187    let mut effective_variants = 0;
    188    for variant in s.variants().iter() {
    189        let css_variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&variant.ast());
    190        if css_variant_attrs.skip {
    191            continue;
    192        }
    193        effective_variants += 1;
    194 
    195        let parse_attrs = cg::parse_variant_attrs_from_ast::<ParseVariantAttrs>(&variant.ast());
    196 
    197        saw_condition |= parse_attrs.condition.is_some();
    198 
    199        if !variant.bindings().is_empty() {
    200            non_keywords.push((variant, css_variant_attrs, parse_attrs));
    201            continue;
    202        }
    203 
    204        assert!(parse_attrs.parse_fn.is_none());
    205 
    206        let identifier = cg::to_css_identifier(
    207            &css_variant_attrs
    208                .keyword
    209                .unwrap_or_else(|| variant.ast().ident.to_string()),
    210        );
    211        let ident = &variant.ast().ident;
    212 
    213        let condition = match parse_attrs.condition {
    214            Some(ref p) => quote! { if #p(context) },
    215            None => quote! {},
    216        };
    217 
    218        match_keywords.extend(quote! {
    219            #identifier #condition => Ok(#name::#ident),
    220        });
    221 
    222        let aliases = match parse_attrs.aliases {
    223            Some(aliases) => aliases,
    224            None => continue,
    225        };
    226 
    227        for alias in aliases.split(',') {
    228            match_keywords.extend(quote! {
    229                #alias #condition => Ok(#name::#ident),
    230            });
    231        }
    232    }
    233 
    234    let needs_context = saw_condition || !non_keywords.is_empty();
    235 
    236    let context_ident = if needs_context {
    237        quote! { context }
    238    } else {
    239        quote! { _ }
    240    };
    241 
    242    let has_keywords = non_keywords.len() != effective_variants;
    243 
    244    let mut parse_non_keywords = quote! {};
    245    for (i, (variant, css_attrs, parse_attrs)) in non_keywords.iter().enumerate() {
    246        let skip_try = !has_keywords && i == non_keywords.len() - 1;
    247        let parse_variant =
    248            parse_non_keyword_variant(&mut where_clause, variant, css_attrs, parse_attrs, skip_try);
    249        parse_non_keywords.extend(parse_variant);
    250    }
    251 
    252    let parse_body = if needs_context {
    253        let parse_keywords = if has_keywords {
    254            quote! {
    255                let location = input.current_source_location();
    256                let ident = input.expect_ident()?;
    257                cssparser::match_ignore_ascii_case! { &ident,
    258                    #match_keywords
    259                    _ => Err(location.new_unexpected_token_error(
    260                        cssparser::Token::Ident(ident.clone())
    261                    ))
    262                }
    263            }
    264        } else {
    265            quote! {}
    266        };
    267 
    268        quote! {
    269            #parse_non_keywords
    270            #parse_keywords
    271        }
    272    } else {
    273        quote! { Self::parse(input) }
    274    };
    275 
    276    let has_non_keywords = !non_keywords.is_empty();
    277 
    278    input.generics.where_clause = where_clause;
    279    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    280 
    281    let parse_trait_impl = quote! {
    282        impl #impl_generics crate::parser::Parse for #name #ty_generics #where_clause {
    283            #[inline]
    284            fn parse<'i, 't>(
    285                #context_ident: &crate::parser::ParserContext,
    286                input: &mut cssparser::Parser<'i, 't>,
    287            ) -> Result<Self, style_traits::ParseError<'i>> {
    288                #parse_body
    289            }
    290        }
    291    };
    292 
    293    if needs_context {
    294        return parse_trait_impl;
    295    }
    296 
    297    assert!(!has_non_keywords);
    298 
    299    // TODO(emilio): It'd be nice to get rid of these, but that makes the
    300    // conversion harder...
    301    let methods_impl = quote! {
    302        impl #name {
    303            /// Parse this keyword.
    304            #[inline]
    305            pub fn parse<'i, 't>(
    306                input: &mut cssparser::Parser<'i, 't>,
    307            ) -> Result<Self, style_traits::ParseError<'i>> {
    308                let location = input.current_source_location();
    309                let ident = input.expect_ident()?;
    310                Self::from_ident(ident.as_ref()).map_err(|()| {
    311                    location.new_unexpected_token_error(
    312                        cssparser::Token::Ident(ident.clone())
    313                    )
    314                })
    315            }
    316 
    317            /// Parse this keyword from a string slice.
    318            #[inline]
    319            pub fn from_ident(ident: &str) -> Result<Self, ()> {
    320                cssparser::match_ignore_ascii_case! { ident,
    321                    #match_keywords
    322                    _ => Err(()),
    323                }
    324            }
    325        }
    326    };
    327 
    328    quote! {
    329        #parse_trait_impl
    330        #methods_impl
    331    }
    332 }