stylo_derive/
parse.rs

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
5use crate::cg;
6use crate::to_css::{CssBitflagAttrs, CssVariantAttrs};
7use proc_macro2::{Span, TokenStream};
8use quote::TokenStreamExt;
9use syn::{self, DeriveInput, Ident, Path};
10use synstructure::{Structure, VariantInfo};
11
12#[derive(Default, FromVariant)]
13#[darling(attributes(parse), default)]
14pub 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)]
22pub struct ParseFieldAttrs {
23    field_bound: bool,
24}
25
26fn 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
90fn 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
171pub 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                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                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}