Skip to main content

cssparser_macros/
lib.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 http://mozilla.org/MPL/2.0/. */
4
5extern crate proc_macro;
6
7use proc_macro::TokenStream;
8
9fn get_byte_from_lit(lit: &syn::Lit) -> u8 {
10    if let syn::Lit::Byte(ref byte) = *lit {
11        byte.value()
12    } else {
13        panic!("Found a pattern that wasn't a byte")
14    }
15}
16
17fn get_byte_from_expr_lit(expr: &syn::Expr) -> u8 {
18    match *expr {
19        syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => get_byte_from_lit(lit),
20        _ => unreachable!(),
21    }
22}
23
24/// Parse a pattern and fill the table accordingly
25fn parse_pat_to_table<'a>(
26    pat: &'a syn::Pat,
27    case_id: u8,
28    wildcard: &mut Option<&'a syn::Ident>,
29    table: &mut [Option<u8>; 256],
30) {
31    match pat {
32        syn::Pat::Lit(syn::PatLit { ref lit, .. }) => {
33            let value = get_byte_from_lit(lit);
34            table[value as usize].get_or_insert(case_id);
35        }
36        syn::Pat::Range(syn::PatRange {
37            ref start, ref end, ..
38        }) => {
39            let lo = get_byte_from_expr_lit(start.as_ref().unwrap());
40            let hi = get_byte_from_expr_lit(end.as_ref().unwrap());
41            for value in lo..hi {
42                table[value as usize].get_or_insert(case_id);
43            }
44            table[hi as usize].get_or_insert(case_id);
45        }
46        syn::Pat::Wild(_) => {
47            for byte in table.iter_mut() {
48                byte.get_or_insert(case_id);
49            }
50        }
51        syn::Pat::Ident(syn::PatIdent { ref ident, .. }) => {
52            assert_eq!(*wildcard, None);
53            *wildcard = Some(ident);
54            for byte in table.iter_mut() {
55                byte.get_or_insert(case_id);
56            }
57        }
58        syn::Pat::Or(syn::PatOr { ref cases, .. }) => {
59            for case in cases {
60                parse_pat_to_table(case, case_id, wildcard, table);
61            }
62        }
63        _ => {
64            panic!("Unexpected or unsupported pattern: {:?}. Buggy code ?", pat);
65        }
66    }
67}
68
69/// Expand a TokenStream corresponding to the `match_byte` macro.
70///
71/// ## Example
72///
73/// ```rust,ignore
74/// match_byte! { tokenizer.next_byte_unchecked(),
75///     b'a'..b'z' => { ... }
76///     b'0'..b'9' => { ... }
77///     b'\n' | b'\\' => { ... }
78///     foo => { ... }
79///  }
80///  ```
81///
82#[proc_macro]
83pub fn match_byte(input: TokenStream) -> TokenStream {
84    use syn::spanned::Spanned;
85    struct MatchByte {
86        expr: syn::Expr,
87        arms: Vec<syn::Arm>,
88    }
89
90    impl syn::parse::Parse for MatchByte {
91        fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
92            Ok(MatchByte {
93                expr: {
94                    let expr = input.parse()?;
95                    input.parse::<syn::Token![,]>()?;
96                    expr
97                },
98                arms: {
99                    let mut arms = Vec::new();
100                    while !input.is_empty() {
101                        let arm = input.call(syn::Arm::parse)?;
102                        assert!(
103                            arm.attrs.is_empty(),
104                            "match_byte doesn't support attributes"
105                        );
106                        arms.push(arm);
107                    }
108                    arms
109                },
110            })
111        }
112    }
113    let MatchByte { expr, arms } = syn::parse_macro_input!(input);
114
115    let mut cases = Vec::new();
116    let mut table = [None; 256];
117    let mut match_body = Vec::new();
118    let mut wildcard = None;
119    for (i, ref arm) in arms.iter().enumerate() {
120        let case_id = i as isize;
121        let name = syn::Ident::new(&format!("Case{case_id}"), arm.span());
122        let pat = &arm.pat;
123        parse_pat_to_table(pat, i as u8, &mut wildcard, &mut table);
124
125        cases.push(quote::quote!(#name = #case_id));
126        let body = &arm.body;
127        match_body.push(quote::quote!(Case::#name => { #body }))
128    }
129
130    let en = quote::quote!(enum Case {
131        #(#cases),*
132    });
133
134    let mut table_content = Vec::new();
135    for (byte, entry) in table.iter().enumerate() {
136        let case_id = match entry {
137            Some(id) => id,
138            None => panic!("Uncovered byte {:?} (add a wildcard pattern?)", byte),
139        };
140        let name: syn::Path = syn::parse_str(&format!("Case::Case{case_id}")).unwrap();
141        table_content.push(name);
142    }
143    let table = quote::quote!(static __CASES: [Case; 256] = [#(#table_content),*];);
144
145    if let Some(binding) = wildcard {
146        quote::quote!({ #en #table let #binding = #expr; match __CASES[#binding as usize] { #(#match_body),* }})
147    } else {
148        quote::quote!({ #en #table match __CASES[#expr as usize] { #(#match_body),* }})
149    }.into()
150}