use proc_macro2::{Ident, TokenStream};
use quote::{quote};
use syn::punctuated::Punctuated;
use syn::{ItemMacro, Token, parse_quote};
use crate::parse::{begins_with_cps_marker, CPSMacroRule, MacroMatch};
fn assert_arm_valid(m: &CPSMacroRule) {
assert!(!begins_with_cps_marker(&m.pattern), "macro rules cannot begin with @_cps when using the cps attribute");
}
fn add_cps(macro_name: &Ident, arm: CPSMacroRule) -> Vec<CPSMacroRule> {
let pattern = arm.pattern.clone();
let impl_tokens = arm.impl_tokens;
let mut output = Vec::new();
let mut result_patterns = arm.let_bindings.iter().rev().map(|lb| {
lb.pattern.clone()
}).collect::<Vec<MacroMatch>>();
let base_case: CPSMacroRule = syn::parse2(quote!{
(@_cps |:| |:| #( { #result_patterns } )* { #pattern } | ) => {
#impl_tokens
}
}).expect("could not build cps base case");
output.push(base_case);
let inner_base_case: CPSMacroRule = syn::parse2(quote!{
(@_cps |:| ( $_cps_next_head:tt ) $(| ( $_cps_next_tail:tt ) )* |:| #( { #result_patterns } )* { #pattern } | $($_cps_stack:tt)*) => {
$_cps_next_head ! { @_cps |:|
$( ( $_cps_next_tail ) )|* |:|
{ #impl_tokens } $($_cps_stack)*
}
}
}).expect("could not build cps inner base case");
output.push(inner_base_case);
let mut acc_result_patterns = Vec::new();
let mut acc_result_clones = Vec::new();
let pattern_output_clone = pattern.build_output_clone();
for binding in arm.let_bindings.iter() {
let binding_macro_path = binding.macro_invocation.path.clone();
let binding_macro_args = binding.macro_invocation.tokens.clone();
let inter_case: CPSMacroRule = syn::parse2(quote!{
(@_cps |:| $( ( $_cps_next:tt ) )|* |:| #( { #acc_result_patterns } )* { #pattern } | $($_cps_stack:tt)*) => {
#binding_macro_path ! { @_cps |:|
( #macro_name ) $(| ( $_cps_next:path ) )* |:|
{ #binding_macro_args } | #( { #acc_result_clones } )* { #pattern_output_clone } | $($_cps_stack)*
}
}
}).expect("could not build cps inter case");
output.push(inter_case);
let result_pattern = result_patterns.pop().expect("different number of matches to let bindings");
acc_result_clones.insert(0, result_pattern.build_output_clone());
acc_result_patterns.insert(0, result_pattern);
}
let pattern_out = pattern.build_output_clone();
let entry: CPSMacroRule = syn::parse2(quote!{
(#pattern) => {
#macro_name ! { @_cps |:| |:| { #pattern_out } | }
}
}).expect("could not build cps entry case");
output.push(entry);
return output;
}
pub fn impl_cps(_attr: TokenStream, m: ItemMacro) -> TokenStream {
let err = "expected a macro_rules! macro definition";
assert_eq!(
"macro_rules",
m.mac.path.segments.last().expect(err).ident.to_string(),
"{}",
err
);
let macro_name = m.ident.expect(err);
let rules_tokens = TokenStream::from(m.mac.tokens);
let rules: Punctuated<CPSMacroRule, Token![;]> =
parse_quote!{ #rules_tokens };
for rule in rules.iter() {
assert_arm_valid(rule);
}
let mut new_rules = Vec::new();
for rule in rules {
let mut new_cps_rules = add_cps(¯o_name, rule);
new_rules.append(&mut new_cps_rules);
}
let attrs = m.attrs;
let path = m.mac.path;
let semi = m.semi_token;
let rebuilt = quote! {
#(#attrs)*
#path ! #macro_name {
#(#new_rules);*
} #semi
};
rebuilt
}