macro-rules-rt 0.1.1

Transforms text or TokenStream using Rust's macro_rules syntax.
Documentation
use macro_rules_rt::{Matcher, Rule, Transcriber};
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use quote::quote;
use syn::{parse::Parser, parse2};

macro_rules! check { {
        { $($from:tt)* },
        { $($to:tt)* },
        { $($input:tt)* },
    } => {
        macro_rules! mr {
            { $($from)* } => { quote!($($to)*) };
        }
        check_eq(quote!($($from)*), quote!($($to)*), quote!($($input)*), mr! { $($input)* })
    };
}
#[track_caller]
fn check_eq(from: TokenStream, to: TokenStream, input: TokenStream, expected: TokenStream) {
    let from_str = from.to_string();
    let to_str = to.to_string();
    let input_str = input.to_string();
    let from = parse2::<Matcher>(from).expect("invalid matcher.");
    let to = Transcriber::parse.parse2(to).expect("invalid transcriber.");
    let rule = Rule::new(from, to).expect("new rule failed.");
    let actual = rule.replace_all_tokens(input).to_string();
    let expected = expected.to_string();
    assert_eq!(
        actual, expected,
        "
from  = {from_str},
to    = {to_str}
input = {input_str}"
    );
}

#[test]
fn simple() {
    check! {
        { a },
        { b },
        { a },
    };
    check! {
        { $a:expr },
        { $a },
        { 1 },
    };
    check! {
        { [$a:expr] },
        { $a @@ },
        { [1] },
    };
}

#[test]
fn metavariable_item() {
    check! {
        { $a:item },
        { $a @@ },
        { fn func() {} },
    };
}

#[test]
fn metavariable_block() {
    check! {
        { $a:block },
        { $a @@ },
        { { 1 } },
    };
}

#[test]
fn metavariable_stmt() {
    check! {
        { $a:stmt },
        { $a @@ },
        { let x = 10 },
    };
}

#[test]
fn metavariable_pat_param() {
    check! {
        { $a:pat_param },
        { $a @@ },
        { (_, x) },
    };
}

#[test]
fn metavariable_pat() {
    check! {
        { $a:pat },
        { $a @@ },
        { (_, x) },
    };

    check! {
        { $a:pat },
        { $a @@ },
        { (_, x) | y },
    };
}

#[test]
fn metavariable_expr() {
    check! {
        { $a:expr },
        { $a @@ },
        { 1 },
    };
}

#[test]
fn metavariable_ty() {
    check! {
        { $a:ty },
        { $a @@ },
        { Option<u8> },
    };
}

#[test]
fn metavariable_ident() {
    check! {
        { $a:ident },
        { $a @@ },
        { x },
    };
}

#[test]
fn metavariable_path() {
    check! {
        { $a:path },
        { $a @@ },
        { std::option::Option },
    };
}

#[test]
fn metavariable_tt() {
    check! {
        { $a:tt },
        { $a @@ },
        { @ },
    };
}

#[test]
fn metavariable_meta() {
    check! {
        { $a:meta },
        { $a @@ },
        { derive(Debug) },
    };
}

#[test]
fn metavariable_lifetime() {
    check! {
        { $a:lifetime },
        { $a @@ },
        { 'a },
    };
}

#[test]
fn metavariable_vis() {
    check! {
        { $a:vis },
        { $a @@ },
        { pub },
    };
}

#[test]
fn metavariable_literal() {
    check! {
        { $a:literal },
        { $a @@ },
        { "abc" },
    };
}

#[test]
fn repeat_zero_or_more() {
    check! {
        { $($a:ident)* },
        { $($a),* @@ },
        { a b c },
    };
    check! {
        { $($a:ident),* },
        { $($a)* @@ },
        { a, b, c },
    };
}
#[test]
fn repeat_zero_or_more_fail() {
    check_eq(
        quote!($($a:ident)*@),
        quote!($($a)*@@),
        quote!(@),
        quote!(@@),
    );
    check_eq(
        quote!($($a:ident)*@),
        quote!($($a)*@@),
        quote!(a@),
        quote!(a@@),
    );
    check_eq(
        quote!($($a:ident)*@),
        quote!($($a)*@@),
        quote!(a a@),
        quote!(a a@@),
    );
}

#[test]
fn repeat_one_or_more() {
    check! {
        { $($a:ident)+ },
        { $($a),+ @@ },
        { a b c },
    };
    check! {
        { $($a:ident),+ },
        { $($a)+ @@ },
        { a, b, c },
    };
}

#[test]
fn repeat_one_or_more_fail() {
    check_eq(
        quote!($($a:ident)+@),
        quote!($($a)+@@),
        quote!(@),
        quote!(@),
    );
    check_eq(
        quote!($($a:ident)+@),
        quote!($($a)+@@),
        quote!(a@),
        quote!(a@@),
    );
    check_eq(
        quote!($($a:ident)*@),
        quote!($($a)*@@),
        quote!(a a@),
        quote!(a a@@),
    );
}

#[test]
fn repeat_zero_or_one() {
    check! {
        { $($a:ident)? },
        { $($a)? @@ },
        { a },
    };
}

#[test]
fn repeat_zero_or_one_fail() {
    check_eq(
        quote!($($a:ident)?@),
        quote!($($a)?@@),
        quote!(@),
        quote!(@@),
    );
    check_eq(
        quote!($($a:ident)?@),
        quote!($($a)?@@),
        quote!(a@),
        quote!(a@@),
    );
    check_eq(
        quote!($($a:ident)?@),
        quote!($($a)?@@),
        quote!(a a@),
        quote!(a a@@),
    );
}

#[test]
fn repeat_and_token_first() {
    check! {
        { $($a:ident)* },
        { $(@ $a)* @},
        { a b },
    };
}

#[test]
fn repeat_metavariable_2() {
    check! {
        { $($a:ident : $b:ty),+ },
        { $($a: $b),+ @@ },
        { a: u8, b: u16 },
    };
}

#[test]
fn group() {
    check! {
        { ($a:ident) },
        { ($a) @@ },
        { (a) },
    };
}

#[test]
fn empty_group() {
    check! {
        { () @ },
        { @@ },
        { () @ },
    };
}

#[test]
fn non_empty_group() {
    check! {
        { (a) @ },
        { @@ },
        { (a) @ },
    };
}

#[test]
fn repeat_empty_group() {
    check! {
        { $(())* @ },
        { @@ },
        { ()() @ },
    };
}

#[test]
fn to_token() {
    check! {
        { $a:ident },
        { $a @@ },
        { a },
    };
}

#[test]
fn nested_rep() {
    check! {
        { $($($a:ident),+)@+ },
        { $($($a),+)@+ @@ },
        { a,b@c,d },
    };
}

#[test]
fn lifetime() {
    check! {
        { 'a },
        { 'b },
        { 'a },
    };
}

#[test]
fn none_group() {
    let mut ts = Vec::new();
    ts.push(TokenTree::Group(Group::new(Delimiter::None, quote!(..=))));
    let ts = TokenStream::from_iter(ts);
    check_eq(quote!(a), quote!(a), ts.clone(), ts);
}

#[test]
fn none_group_empty() {
    let ts = TokenStream::from_iter([TokenTree::Group(Group::new(Delimiter::None, quote!()))]);
    check_eq(quote!(), quote!(), ts.clone(), ts);
}