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 }