| use proc_macro::TokenStream; |
| use quote::quote; |
| use std::collections::HashSet; |
| use syn::parse::{Parse, ParseStream, Result}; |
| use syn::{braced, parse_macro_input, Ident, LitStr, Token}; |
| |
| mod kw { |
| syn::custom_keyword!(Keywords); |
| syn::custom_keyword!(Symbols); |
| } |
| |
| struct Keyword { |
| name: Ident, |
| value: LitStr, |
| } |
| |
| impl Parse for Keyword { |
| fn parse(input: ParseStream<'_>) -> Result<Self> { |
| let name = input.parse()?; |
| input.parse::<Token![:]>()?; |
| let value = input.parse()?; |
| input.parse::<Token![,]>()?; |
| |
| Ok(Keyword { name, value }) |
| } |
| } |
| |
| struct Symbol { |
| name: Ident, |
| value: Option<LitStr>, |
| } |
| |
| impl Parse for Symbol { |
| fn parse(input: ParseStream<'_>) -> Result<Self> { |
| let name = input.parse()?; |
| let value = match input.parse::<Token![:]>() { |
| Ok(_) => Some(input.parse()?), |
| Err(_) => None, |
| }; |
| input.parse::<Token![,]>()?; |
| |
| Ok(Symbol { name, value }) |
| } |
| } |
| |
| /// A type used to greedily parse another type until the input is empty. |
| struct List<T>(Vec<T>); |
| |
| impl<T: Parse> Parse for List<T> { |
| fn parse(input: ParseStream<'_>) -> Result<Self> { |
| let mut list = Vec::new(); |
| while !input.is_empty() { |
| list.push(input.parse()?); |
| } |
| Ok(List(list)) |
| } |
| } |
| |
| struct Input { |
| keywords: List<Keyword>, |
| symbols: List<Symbol>, |
| } |
| |
| impl Parse for Input { |
| fn parse(input: ParseStream<'_>) -> Result<Self> { |
| input.parse::<kw::Keywords>()?; |
| let content; |
| braced!(content in input); |
| let keywords = content.parse()?; |
| |
| input.parse::<kw::Symbols>()?; |
| let content; |
| braced!(content in input); |
| let symbols = content.parse()?; |
| |
| Ok(Input { keywords, symbols }) |
| } |
| } |
| |
| pub fn symbols(input: TokenStream) -> TokenStream { |
| let input = parse_macro_input!(input as Input); |
| |
| let mut keyword_stream = quote! {}; |
| let mut symbols_stream = quote! {}; |
| let mut digits_stream = quote! {}; |
| let mut prefill_stream = quote! {}; |
| let mut counter = 0u32; |
| let mut keys = HashSet::<String>::new(); |
| let mut prev_key: Option<String> = None; |
| let mut errors = Vec::<String>::new(); |
| |
| let mut check_dup = |str: &str, errors: &mut Vec<String>| { |
| if !keys.insert(str.to_string()) { |
| errors.push(format!("Symbol `{}` is duplicated", str)); |
| } |
| }; |
| |
| let mut check_order = |str: &str, errors: &mut Vec<String>| { |
| if let Some(ref prev_str) = prev_key { |
| if str < prev_str { |
| errors.push(format!("Symbol `{}` must precede `{}`", str, prev_str)); |
| } |
| } |
| prev_key = Some(str.to_string()); |
| }; |
| |
| // Generate the listed keywords. |
| for keyword in &input.keywords.0 { |
| let name = &keyword.name; |
| let value = &keyword.value; |
| check_dup(&value.value(), &mut errors); |
| prefill_stream.extend(quote! { |
| #value, |
| }); |
| keyword_stream.extend(quote! { |
| #[allow(non_upper_case_globals)] |
| pub const #name: Symbol = Symbol::new(#counter); |
| }); |
| counter += 1; |
| } |
| |
| // Generate the listed symbols. |
| for symbol in &input.symbols.0 { |
| let name = &symbol.name; |
| let value = match &symbol.value { |
| Some(value) => value.value(), |
| None => name.to_string(), |
| }; |
| check_dup(&value, &mut errors); |
| check_order(&name.to_string(), &mut errors); |
| prefill_stream.extend(quote! { |
| #value, |
| }); |
| symbols_stream.extend(quote! { |
| #[allow(rustc::default_hash_types)] |
| #[allow(non_upper_case_globals)] |
| pub const #name: Symbol = Symbol::new(#counter); |
| }); |
| counter += 1; |
| } |
| |
| // Generate symbols for the strings "0", "1", ..., "9". |
| for n in 0..10 { |
| let n = n.to_string(); |
| check_dup(&n, &mut errors); |
| prefill_stream.extend(quote! { |
| #n, |
| }); |
| digits_stream.extend(quote! { |
| Symbol::new(#counter), |
| }); |
| counter += 1; |
| } |
| |
| if !errors.is_empty() { |
| for error in errors.into_iter() { |
| eprintln!("error: {}", error) |
| } |
| panic!("errors in `Keywords` and/or `Symbols`"); |
| } |
| |
| let tt = TokenStream::from(quote! { |
| macro_rules! keywords { |
| () => { |
| #keyword_stream |
| } |
| } |
| |
| macro_rules! define_symbols { |
| () => { |
| #symbols_stream |
| |
| #[allow(non_upper_case_globals)] |
| pub const digits_array: &[Symbol; 10] = &[ |
| #digits_stream |
| ]; |
| } |
| } |
| |
| impl Interner { |
| pub fn fresh() -> Self { |
| Interner::prefill(&[ |
| #prefill_stream |
| ]) |
| } |
| } |
| }); |
| |
| // To see the generated code generated, uncomment this line, recompile, and |
| // run the resulting output through `rustfmt`. |
| //eprintln!("{}", tt); |
| |
| tt |
| } |