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 { let name = input.parse()?; input.parse::()?; let value = input.parse()?; input.parse::()?; Ok(Keyword { name, value }) } } struct Symbol { name: Ident, value: Option, } impl Parse for Symbol { fn parse(input: ParseStream<'_>) -> Result { let name = input.parse()?; let value = match input.parse::() { Ok(_) => Some(input.parse()?), Err(_) => None, }; input.parse::()?; Ok(Symbol { name, value }) } } /// A type used to greedily parse another type until the input is empty. struct List(Vec); impl Parse for List { fn parse(input: ParseStream<'_>) -> Result { let mut list = Vec::new(); while !input.is_empty() { list.push(input.parse()?); } Ok(List(list)) } } struct Input { keywords: List, symbols: List, } impl Parse for Input { fn parse(input: ParseStream<'_>) -> Result { input.parse::()?; let content; braced!(content in input); let keywords = content.parse()?; input.parse::()?; 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::::new(); let mut prev_key: Option = None; let mut errors = Vec::::new(); let mut check_dup = |str: &str, errors: &mut Vec| { if !keys.insert(str.to_string()) { errors.push(format!("Symbol `{}` is duplicated", str)); } }; let mut check_order = |str: &str, errors: &mut Vec| { 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 }