use std::collections::HashMap;
use crate::parse_macro_decl::{begins_with_cps_marker, CPSMacroRule, MacroMatch, MacroMatcher};
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use syn::punctuated::Punctuated;
use syn::{parse_quote, ItemMacro, Token};
fn assert_arm_valid(m: &CPSMacroRule) {
assert!(
!begins_with_cps_marker(&m.pattern),
"macro rules cannot begin with @_cps when using the cps attribute"
);
}
pub fn build_next_step(
next_head: impl ToTokens,
next_program: impl ToTokens,
impl_tokens: impl ToTokens,
next_stack: impl ToTokens,
) -> TokenStream {
quote! {
#next_head ! { @_cps |:|
#next_program |:|
{ #impl_tokens } #next_stack
}
}
}
fn add_cps(
macro_name: &Ident,
arm: CPSMacroRule,
) -> (
Vec<CPSMacroRule>,
HashMap<String, (MacroMatcher, Vec<MacroMatcher>)>,
) {
let pattern = arm.pattern.clone();
let impl_tokens = arm.impl_tokens;
let mut output_cases = Vec::new();
let mut output_debug_cases = HashMap::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_cases.push(base_case);
let next_step = build_next_step(
quote! { $_cps_next_head },
quote! { $( ( $_cps_next_tail ) )|* },
impl_tokens,
quote! { $($_cps_stack)* },
);
let inner_base_case: CPSMacroRule = syn::parse2(quote!{
(@_cps |:| ( $_cps_next_head:tt ) $(| ( $_cps_next_tail:tt ) )* |:| #( { #result_patterns } )* { #pattern } | $($_cps_stack:tt)*) => {
#next_step
}
}).expect("could not build cps inner base case");
output_cases.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 path_indirection = binding.macro_name_indirection.clone();
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)*) => {
#path_indirection #binding_macro_path ! { @_cps |:|
( #macro_name ) $(| ( $_cps_next ) )* |:|
{ #binding_macro_args } | #( { #acc_result_clones } )* { #pattern_output_clone } | $($_cps_stack)*
}
}
}).expect("could not build cps inter case");
output_cases.push(inter_case);
let mut valid_patterns = acc_result_patterns
.clone()
.into_iter()
.map(|m: MacroMatch| MacroMatcher {
matches: vec![m.clone()],
})
.collect::<Vec<_>>();
valid_patterns.push(pattern.clone());
let expected_pattern = valid_patterns.remove(0);
let invalid_match: MacroMatcher = syn::parse2(quote!{@_cps |:| $( ( $_cps_next:tt ) )|* |:| { $($unexpected:tt)* } #( { #valid_patterns } )* | $($_cps_stack:tt)*}).expect("could not build cps inter debug match");
output_debug_cases
.entry(invalid_match.to_token_stream().to_string())
.or_insert((invalid_match, vec![]))
.1
.push(expected_pattern);
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_cases.push(entry);
return (output_cases, output_debug_cases);
}
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();
let mut error_rules = HashMap::new();
for rule in rules {
let (mut new_cps_rules, new_error_rules) = add_cps(¯o_name, rule);
new_rules.append(&mut new_cps_rules);
for (s, (error_match, mut expected_patterns)) in new_error_rules {
error_rules.entry(s).or_insert((error_match, vec![])).1.append(&mut expected_patterns);
}
}
let error_rules = error_rules.into_iter().map(|(_, (error_match, expected_patterns))| {
let err_msg = if let [expected_pattern] = &expected_patterns.as_slice() {
format!(
"while evaluating macro {}, expected something that matches `{}` but got `",
macro_name.to_string(),
expected_pattern.to_token_stream()
)
} else {
let parts =
expected_patterns.iter().map(|expected_pattern| format!("`{}`", expected_pattern.to_token_stream())).fold("".to_owned(), |a, b| a + " or " + &b);
format!(
"while evaluating macro {}, expected something that matches one of {} but got `",
macro_name.to_string(),
parts
)
};
syn::parse2::<CPSMacroRule>(quote!{
(#error_match) => {
std::compile_error!(std::concat!(#err_msg, std::stringify!($($unexpected)*) ,"` instead"));
}
}).expect("could not build cps inter debug case")
}).collect::<Vec<_>>();
let fallback_rules = quote! {
(@_cps |:| |:| |) => {
std::compile_error!("base case has no result - this is a bug with the cps crate and should be reported here: https://github.com/LucentFlux/CPS/issues");
};
(@_cps |:| |:| $($stack:tt)* ) => {
std::compile_error!(concat!("base case has invalid stack: `", stringify!($($stack)*), "` - this is a bug with the cps crate and should be reported here: https://github.com/LucentFlux/CPS/issues"));
};
(@_cps |:| $(($call_stack:tt))|* |:| $($data_stack:tt)* ) => {
std::compile_error!(concat!("cps macro evaluation resulted in an invalid state, with call stack `", stringify!($($call_stack)*), "` and data stack `", stringify!($($data_stack)*), "` - this is probably a bug with the cps crate and should be reported here: https://github.com/LucentFlux/CPS/issues"));
};
(@_cps $($everything:tt)*) => {
std::compile_error!(concat!("cps macro evaluation resulted in an invalid state: `", stringify!($($everything)*), "` - this is a bug with the cps crate and should be reported here: https://github.com/LucentFlux/CPS/issues"));
};
};
let attrs = m.attrs;
let path = m.mac.path;
let semi = m.semi_token;
let rebuilt = quote! {
#(#attrs)*
#path ! #macro_name {
#(#new_rules ;)*
#(#error_rules ;)*
#fallback_rules
} #semi
};
rebuilt
}