/* 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 http://mozilla.org/MPL/2.0/. */ extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro] pub fn _cssparser_internal_max_len(input: TokenStream) -> TokenStream { struct Input { max_length: usize, } impl syn::parse::Parse for Input { fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { let mut max_length = 0; while !input.is_empty() { if input.peek(syn::Token![_]) { input.parse::().unwrap(); continue; } let lit: syn::LitStr = input.parse()?; let value = lit.value(); if value.to_ascii_lowercase() != value { return Err(syn::Error::new(lit.span(), "must be ASCII-lowercase")); } max_length = max_length.max(value.len()); } Ok(Input { max_length }) } } let Input { max_length } = syn::parse_macro_input!(input); quote::quote!( pub(super) const MAX_LENGTH: usize = #max_length; ) .into() } fn get_byte_from_expr_lit(expr: &syn::Expr) -> u8 { match *expr { syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => { if let syn::Lit::Byte(ref byte) = *lit { byte.value() } else { panic!("Found a pattern that wasn't a byte") } } _ => unreachable!(), } } /// Parse a pattern and fill the table accordingly fn parse_pat_to_table<'a>( pat: &'a syn::Pat, case_id: u8, wildcard: &mut Option<&'a syn::Ident>, table: &mut [u8; 256], ) { match pat { &syn::Pat::Lit(syn::PatLit { ref expr, .. }) => { let value = get_byte_from_expr_lit(expr); if table[value as usize] == 0 { table[value as usize] = case_id; } } &syn::Pat::Range(syn::PatRange { ref lo, ref hi, .. }) => { let lo = get_byte_from_expr_lit(lo); let hi = get_byte_from_expr_lit(hi); for value in lo..hi { if table[value as usize] == 0 { table[value as usize] = case_id; } } if table[hi as usize] == 0 { table[hi as usize] = case_id; } } &syn::Pat::Wild(_) => { for byte in table.iter_mut() { if *byte == 0 { *byte = case_id; } } } &syn::Pat::Ident(syn::PatIdent { ref ident, .. }) => { assert_eq!(*wildcard, None); *wildcard = Some(ident); for byte in table.iter_mut() { if *byte == 0 { *byte = case_id; } } } &syn::Pat::Or(syn::PatOr { ref cases, .. }) => { for case in cases { parse_pat_to_table(case, case_id, wildcard, table); } } _ => { panic!("Unexpected pattern: {:?}. Buggy code ?", pat); } } } /// Expand a TokenStream corresponding to the `match_byte` macro. /// /// ## Example /// /// ```rust /// match_byte! { tokenizer.next_byte_unchecked(), /// b'a'..b'z' => { ... } /// b'0'..b'9' => { ... } /// b'\n' | b'\\' => { ... } /// foo => { ... } /// } /// ``` /// #[proc_macro] pub fn match_byte(input: TokenStream) -> TokenStream { use syn::spanned::Spanned; struct MatchByte { expr: syn::Expr, arms: Vec, } impl syn::parse::Parse for MatchByte { fn parse(input: syn::parse::ParseStream) -> syn::Result { Ok(MatchByte { expr: { let expr = input.parse()?; input.parse::()?; expr }, arms: { let mut arms = Vec::new(); while !input.is_empty() { let arm = input.call(syn::Arm::parse)?; assert!(arm.guard.is_none(), "match_byte doesn't support guards"); assert!( arm.attrs.is_empty(), "match_byte doesn't support attributes" ); arms.push(arm); } arms }, }) } } let MatchByte { expr, arms } = syn::parse_macro_input!(input); let mut cases = Vec::new(); let mut table = [0u8; 256]; let mut match_body = Vec::new(); let mut wildcard = None; for (i, ref arm) in arms.iter().enumerate() { let case_id = i + 1; let index = case_id as isize; let name = syn::Ident::new(&format!("Case{}", case_id), arm.span()); let pat = &arm.pat; parse_pat_to_table(pat, case_id as u8, &mut wildcard, &mut table); cases.push(quote::quote!(#name = #index)); let body = &arm.body; match_body.push(quote::quote!(Case::#name => { #body })) } let en = quote::quote!(enum Case { #(#cases),* }); let mut table_content = Vec::new(); for entry in table.iter() { let name: syn::Path = syn::parse_str(&format!("Case::Case{}", entry)).unwrap(); table_content.push(name); } let table = quote::quote!(static __CASES: [Case; 256] = [#(#table_content),*];); if let Some(binding) = wildcard { quote::quote!({ #en #table let #binding = #expr; match __CASES[#binding as usize] { #(#match_body),* }}) } else { quote::quote!({ #en #table match __CASES[#expr as usize] { #(#match_body),* }}) }.into() }