#![deny(missing_docs)]
use proc_macro::TokenStream as StdTokenStream;
use proc_macro2::TokenStream;
mod error;
mod state;
mod util;
use error::Error;
use error::Result;
use state::StatefulMacroRule;
use util::describe_tokens;
use util::split_meta;
use util::split_tokens;
use util::Describe::{G, I, P};
#[proc_macro]
pub fn stateful_macro_rules(input: StdTokenStream) -> StdTokenStream {
match stateful_macro_rules_fallible(input.into()) {
Ok(t) => t.into(),
Err(e) => e.into(),
}
}
pub(crate) fn stateful_macro_rules_fallible(input: TokenStream) -> Result<TokenStream> {
let mut rule = StatefulMacroRule::default();
#[cfg(feature = "debug")]
let mut debug = false;
for tokens in split_tokens(input, ';') {
let (meta, tokens) = split_meta(&tokens);
match describe_tokens(tokens)[..] {
[I(i), G('(', s), G('{', r)] => {
rule.set_attributes(meta)?;
rule.set_name(i.clone())?;
rule.set_state(s)?;
rule.set_return(None, r)?;
}
[I(i), G('(', s), I(iw), G('{', w), G('{', r)] if iw.to_string() == "when" => {
rule.set_attributes(meta)?;
rule.set_name(i.clone())?;
rule.set_state(s)?;
rule.set_return(Some(w.stream()), r)?;
}
[G('(', pat), P('='), P('>'), G('{', body)] => {
rule.append_rule(pat.stream(), None, body.stream())?;
}
[G('(', pat), I(w), G('{', state), P('='), P('>'), G('{', body)]
if w.to_string() == "when" =>
{
rule.append_rule(pat.stream(), Some(state.stream()), body.stream())?;
}
#[cfg(feature = "debug")]
[I(i)] if i.to_string() == "debug" => {
debug = true;
}
_ => {
return Err(Error::UnexpectedTokens(
tokens.to_vec(),
concat!(
"expect 'macro_name(state_name: (ty) = (default), ...) { ... };',",
" or '(...) => { ... };'",
" or '(...) when { state: (pat), ... } => { ... };'",
" in stateful_macro_rule!"
),
))
}
}
}
let code = rule.generate_code()?;
#[cfg(feature = "debug")]
if debug {
eprintln!("{}", util::to_string(code.clone(), 100));
}
Ok(code)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::dollar;
use quote::quote;
fn to_string(t: TokenStream) -> String {
crate::util::to_string(t, 80)
}
#[test]
fn test_minimal_example() {
let q = stateful_macro_rules_fallible(quote! {
minimal() { "foo" }
})
.unwrap();
assert_eq!(
to_string(q),
r#"
# [macro_export] macro_rules ! minimal
{
{ $ ($ tt : tt) * } => { $ crate :: __minimal_state ! ([$ ($ tt) *] { }) } ;
}
# [doc (hidden)] # [macro_export] macro_rules ! __minimal_state
{
([] { }) => { "foo" } ;
}"#
);
}
#[test]
fn test_attributes() {
let q = stateful_macro_rules_fallible(quote! {
#[cfg(feature = "bar")]
attriute_test() { 1 }
})
.unwrap();
assert_eq!(
to_string(q),
r#"
# [macro_export] # [cfg (feature = "bar")] # [doc = r" Some comment."] #
[
doc = r" Foo bar."
]
macro_rules ! attriute_test
{
{ $ ($ tt : tt) * } =>
{
$ crate :: __attriute_test_state ! ([$ ($ tt) *] { })
}
;
}
# [doc (hidden)] # [macro_export] macro_rules ! __attriute_test_state
{
([] { }) => { 1 } ;
}"#
);
}
#[test]
fn test_when_clause() {
let d = dollar();
let q = stateful_macro_rules_fallible(quote! {
w(b: (#d b:tt) = (false)) when { b: (true) } { "ok" };
(t) when { b: (false) } => { b.set(true) };
})
.unwrap();
assert_eq!(
to_string(q),
r#"
# [macro_export] macro_rules ! w
{
{ $ ($ tt : tt) * } => { $ crate :: __w_state ! ([$ ($ tt) *] { b [false] }) } ;
}
# [doc (hidden)] # [macro_export] macro_rules ! __w_state
{
([] { b [true] }) => { "ok" } ; ([t $ ($ _ddd : tt) *] { b [false] }) =>
{
$ crate :: __w_state ! ([$ ($ _ddd) *] { b [true] })
}
;
}"#
);
}
#[test]
fn test_complex_example() {
let d = dollar();
let q = stateful_macro_rules_fallible(quote! {
#[allow(dead_code)]
foo(
x: (#d (#d i:expr)*) = (1 2),
y: (#d (#d j:ident)*),
z: (#d (#d t:tt)* ) = (x),
) {{
let v1 = vec![#d (#d i),*];
let v2 = vec![#d (stringify!(#d j)),*];
format!("{:?} {:?}", v1, v2)
}};
(y += #d t:ident) => {
y.append(#d t);
};
(y = #d t:ident ...) when { z: (x) } => {
y.set(#d t);
z.append(y);
};
(e(#d d:ident, #d e:expr) ...) => {
x.append(#d e);
y.append(#d d);
z.append(....);
}
})
.unwrap();
assert_eq!(
to_string(q),
r#"
# [macro_export] # [allow (dead_code)] # [doc = r" Foo bar"] macro_rules ! foo
{
{ $ ($ tt : tt) * } =>
{
$ crate :: __foo_state ! ([$ ($ tt) *] { x [1 2] y [] z [x] })
}
;
}
# [doc (hidden)] # [macro_export] macro_rules ! __foo_state
{
([] { x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [$ ($ t : tt) *] }) =>
{
{
let v1 = vec ! [$ ($ i) , *] ; let v2 = vec ! [$ (stringify ! ($ j)) , *] ; format !
(
"{:?} {:?}" , v1 , v2
)
}
}
;
(
[y += $ t : ident $ ($ _ddd : tt) *]
{
x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [$ ($ t : tt) *]
}
)
=>
{
$ crate :: __foo_state !
(
[$ ($ _ddd) *] { x [$ ($ i) *] y [$ ($ j) * $ t] z [$ ($ t) *] }
)
}
;
(
[y = $ t : ident $ ($ _ddd : tt) *]
{
x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [x]
}
)
=>
{
$ crate :: __foo_state ! ([$ ($ _ddd) *] { x [$ ($ i) *] y [$ t] z [x y] })
}
;
(
[e ($ d : ident , $ e : expr) $ ($ _ddd : tt) *]
{
x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [$ ($ t : tt) *]
}
)
=>
{
$ crate :: __foo_state !
(
[$ ($ _ddd) *]
{
x [$ ($ i) * $ e] y [$ ($ j) * $ d] z [$ ($ t) * e ($ d , $ e)]
}
)
}
;
}"#
);
}
}