| /* 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<Self> { |
| let mut max_length = 0; |
| while !input.is_empty() { |
| if input.peek(syn::Token![_]) { |
| input.parse::<syn::Token![_]>().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_lit(lit: &syn::Lit) -> u8 { |
| if let syn::Lit::Byte(ref byte) = *lit { |
| byte.value() |
| } else { |
| panic!("Found a pattern that wasn't a byte") |
| } |
| } |
| |
| fn get_byte_from_expr_lit(expr: &syn::Expr) -> u8 { |
| match *expr { |
| syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => get_byte_from_lit(lit), |
| _ => 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 lit, .. }) => { |
| let value = get_byte_from_lit(lit); |
| if table[value as usize] == 0 { |
| table[value as usize] = case_id; |
| } |
| } |
| syn::Pat::Range(syn::PatRange { |
| ref start, ref end, .. |
| }) => { |
| let lo = get_byte_from_expr_lit(start.as_ref().unwrap()); |
| let hi = get_byte_from_expr_lit(end.as_ref().unwrap()); |
| 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<syn::Arm>, |
| } |
| |
| impl syn::parse::Parse for MatchByte { |
| fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
| Ok(MatchByte { |
| expr: { |
| let expr = input.parse()?; |
| input.parse::<syn::Token![,]>()?; |
| 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() |
| } |