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
9#[proc_macro]
10pub fn _cssparser_internal_max_len(input: TokenStream) -> TokenStream {
11    struct Input {
12        max_length: usize,
13    }
14
15    impl syn::parse::Parse for Input {
16        fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
17            let mut max_length = 0;
18            while !input.is_empty() {
19                if input.peek(syn::Token![_]) {
20                    input.parse::<syn::Token![_]>().unwrap();
21                    continue;
22                }
23                let lit: syn::LitStr = input.parse()?;
24                let value = lit.value();
25                if value.to_ascii_lowercase() != value {
26                    return Err(syn::Error::new(lit.span(), "must be ASCII-lowercase"));
27                }
28                max_length = max_length.max(value.len());
29            }
30            Ok(Input { max_length })
31        }
32    }
33
34    let Input { max_length } = syn::parse_macro_input!(input);
35    quote::quote!(
36        pub(super) const MAX_LENGTH: usize = #max_length;
37    )
38    .into()
39}
40
41fn get_byte_from_lit(lit: &syn::Lit) -> u8 {
42    if let syn::Lit::Byte(ref byte) = *lit {
43        byte.value()
44    } else {
45        panic!("Found a pattern that wasn't a byte")
46    }
47}
48
49fn get_byte_from_expr_lit(expr: &syn::Expr) -> u8 {
50    match *expr {
51        syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => {
52            get_byte_from_lit(lit)
53        }
54        _ => unreachable!(),
55    }
56}
57
58/// Parse a pattern and fill the table accordingly
59fn parse_pat_to_table<'a>(
60    pat: &'a syn::Pat,
61    case_id: u8,
62    wildcard: &mut Option<&'a syn::Ident>,
63    table: &mut [u8; 256],
64) {
65    match pat {
66        &syn::Pat::Lit(syn::PatLit { ref lit, .. }) => {
67            let value = get_byte_from_lit(lit);
68            if table[value as usize] == 0 {
69                table[value as usize] = case_id;
70            }
71        }
72        &syn::Pat::Range(syn::PatRange { ref start, ref end, .. }) => {
73            let lo = get_byte_from_expr_lit(&start.as_ref().unwrap());
74            let hi = get_byte_from_expr_lit(&end.as_ref().unwrap());
75            for value in lo..hi {
76                if table[value as usize] == 0 {
77                    table[value as usize] = case_id;
78                }
79            }
80            if table[hi as usize] == 0 {
81                table[hi as usize] = case_id;
82            }
83        }
84        &syn::Pat::Wild(_) => {
85            for byte in table.iter_mut() {
86                if *byte == 0 {
87                    *byte = case_id;
88                }
89            }
90        }
91        &syn::Pat::Ident(syn::PatIdent { ref ident, .. }) => {
92            assert_eq!(*wildcard, None);
93            *wildcard = Some(ident);
94            for byte in table.iter_mut() {
95                if *byte == 0 {
96                    *byte = case_id;
97                }
98            }
99        }
100        &syn::Pat::Or(syn::PatOr { ref cases, .. }) => {
101            for case in cases {
102                parse_pat_to_table(case, case_id, wildcard, table);
103            }
104        }
105        _ => {
106            panic!("Unexpected pattern: {:?}. Buggy code ?", pat);
107        }
108    }
109}
110
111/// Expand a TokenStream corresponding to the `match_byte` macro.
112///
113/// ## Example
114///
115/// ```rust
116/// match_byte! { tokenizer.next_byte_unchecked(),
117///     b'a'..b'z' => { ... }
118///     b'0'..b'9' => { ... }
119///     b'\n' | b'\\' => { ... }
120///     foo => { ... }
121///  }
122///  ```
123///
124#[proc_macro]
125pub fn match_byte(input: TokenStream) -> TokenStream {
126    use syn::spanned::Spanned;
127    struct MatchByte {
128        expr: syn::Expr,
129        arms: Vec<syn::Arm>,
130    }
131
132    impl syn::parse::Parse for MatchByte {
133        fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
134            Ok(MatchByte {
135                expr: {
136                    let expr = input.parse()?;
137                    input.parse::<syn::Token![,]>()?;
138                    expr
139                },
140                arms: {
141                    let mut arms = Vec::new();
142                    while !input.is_empty() {
143                        let arm = input.call(syn::Arm::parse)?;
144                        assert!(arm.guard.is_none(), "match_byte doesn't support guards");
145                        assert!(
146                            arm.attrs.is_empty(),
147                            "match_byte doesn't support attributes"
148                        );
149                        arms.push(arm);
150                    }
151                    arms
152                },
153            })
154        }
155    }
156    let MatchByte { expr, arms } = syn::parse_macro_input!(input);
157
158    let mut cases = Vec::new();
159    let mut table = [0u8; 256];
160    let mut match_body = Vec::new();
161    let mut wildcard = None;
162    for (i, ref arm) in arms.iter().enumerate() {
163        let case_id = i + 1;
164        let index = case_id as isize;
165        let name = syn::Ident::new(&format!("Case{}", case_id), arm.span());
166        let pat = &arm.pat;
167        parse_pat_to_table(pat, case_id as u8, &mut wildcard, &mut table);
168
169        cases.push(quote::quote!(#name = #index));
170        let body = &arm.body;
171        match_body.push(quote::quote!(Case::#name => { #body }))
172    }
173
174    let en = quote::quote!(enum Case {
175        #(#cases),*
176    });
177
178    let mut table_content = Vec::new();
179    for entry in table.iter() {
180        let name: syn::Path = syn::parse_str(&format!("Case::Case{}", entry)).unwrap();
181        table_content.push(name);
182    }
183    let table = quote::quote!(static __CASES: [Case; 256] = [#(#table_content),*];);
184
185    if let Some(binding) = wildcard {
186        quote::quote!({ #en #table let #binding = #expr; match __CASES[#binding as usize] { #(#match_body),* }})
187    } else {
188        quote::quote!({ #en #table match __CASES[#expr as usize] { #(#match_body),* }})
189    }.into()
190}