Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Impressum parse.rs   Sprache: unbekannt

 
/* 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 crate::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>,
    pub parse_fn: 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>,
    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 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.parse_fn.is_none(), "should not be needed");
        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 let Some(ref parse_fn) = parse_attrs.parse_fn {
        quote! { #parse_fn(context, input) }
    } else {
        quote! { <#ty as crate::parser::Parse>::parse(context, input) }
    };

    let variant_name = &variant.ast().ident;
    let variant_name = match variant.prefix {
        Some(p) => quote! { #p::#variant_name },
        None => quote! { #variant_name },
    };

    parse = if skip_try {
        quote! {
            let v = #parse?;
            return Ok(#variant_name(v));
        }
    } else {
        quote! {
            if let Ok(v) = input.try(|input| #parse) {
                return Ok(#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;
        }

        assert!(parse_attrs.parse_fn.is_none());

        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,
            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
    }
}

[ Seitenstruktur0.4Drucken  etwas mehr zur Ethik  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge