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::TokenStream;
use syn::{parse::Parser, parse2, parse_str};

#[track_caller]
fn check(from: &str, to: &str, input: &str, expect: &str) {
    check_as(from, to, input, expect, false);
}

#[track_caller]
fn check_as(from: &str, to: &str, input: &str, expect: &str, nest: bool) {
    let msg = format!(
        "
from  = {from},
to    = {to}
input = {input}"
    );
    {
        let from: Matcher = from.parse().unwrap();
        let to: Transcriber = to.parse().unwrap();
        let rule = Rule::new(from, to).unwrap();
        let rule = rule.nest(nest);
        let actual = rule.replace_all(input).unwrap();
        assert_eq!(actual, expect, "replace_all {msg}");
    }
    {
        let from: Matcher = from.parse().unwrap();
        let to: TokenStream = parse_str(to).unwrap();
        let to = Transcriber::parse.parse2(to).unwrap();
        let rule = Rule::new(from, to).unwrap();
        let rule = rule.nest(nest);

        let input: TokenStream = input.parse().unwrap();
        let expect: TokenStream = expect.parse().unwrap();
        let actual = rule.replace_all_tokens(input);
        assert_eq!(
            actual.to_string(),
            expect.to_string(),
            "replace_all tokens_transcriber {msg}"
        );
    }
    {
        let from: TokenStream = parse_str(from).unwrap();
        let from: Matcher = parse2(from).unwrap();
        let to: TokenStream = parse_str(to).unwrap();
        let to = Transcriber::parse.parse2(to).unwrap();
        let rule = Rule::new(from, to).unwrap();
        let rule = rule.nest(nest);
        let actual = rule.replace_all(input).unwrap();
        assert_eq!(actual, expect, "replace_all_tokens {msg}");

        let input: TokenStream = input.parse().unwrap();
        let expect: TokenStream = expect.parse().unwrap();
        let actual = rule.replace_all_tokens(input);
        assert_eq!(
            actual.to_string(),
            expect.to_string(),
            "replace_all_tokens {msg}"
        );
    }
}

#[test]
fn single_line_1() {
    check("+", "-", "1 + 2 + 3", "1 - 2 - 3");
    check("+", "-", "+2+3", "- 2 - 3");
}

#[test]
fn single_line_2() {
    check(
        "$($e:ident)*",
        "$($e @)*",
        "1+2 a b c 3+4",
        "1+2 a @ b @ c @ 3+4",
    );
}

#[test]
fn var() {
    check("$x:ident", "b", "a", "b");
}

#[test]
fn var_to() {
    check("$x:ident", "@ $x @", "a", "@ a @");
    check("$x:ident", "@ $x @", "1 a", "1 @ a @");
}

#[test]
fn lf() {
    check("+", "-", "1\n2+3;\n4", "1\n2 - 3;\n4");
}

#[test]
fn cr() {
    check("+", "-", "1\r2+3;\r4", "1\r2 - 3;\r4");
}

#[test]
fn cr_lf() {
    check("+", "-", "1\r\n2+3\r\n4", "1\r\n2 - 3\r\n4");
}

#[test]
fn non_ascii() {
    check("+", "-", "\"\"+2+3", "\"\" - 2 - 3");
}

#[test]
fn replace_middle() {
    check("+", "-", "1+2", "1 - 2");
}

#[test]
fn replace_end() {
    check("+", "-", "1+", "1 -");
}

#[test]
fn var_expr() {
    check("$e:expr", "$e @", "1", "1 @");
    check("$e:expr", "$e @", "1 + 2", "1 + 2 @");
    check("$e:expr", "$e @", "(1 + 2)", "(1 + 2) @");
    check(
        "$e:expr",
        "$e @",
        "(1 + 2) + (3 + 4)",
        "(1 + 2) + (3 + 4) @",
    );
}

#[test]
fn tt() {
    check("$x:tt", "a", "1 2", "a a");
}

#[test]
fn doc_comment_no_panic() {
    check("$x:tt", "", "///\nstruct X;", "");
}
#[test]
fn doc_comment_save() {
    check("a", "a", "/// xxx\nstruct X;", "/// xxx\nstruct X;");
}
#[test]
fn doc_comment_attr() {
    check("doc =", "xxx =", "/// abc", "# [ xxx = \" abc\" ]");
}

#[test]
fn group() {
    check("(a)", "a @@", "(a)", "a @@");
    check("[a]", "a @@", "[a]", "a @@");
    check("{a}", "a @@", "{a}", "a @@");
}

#[test]
fn group_mismatch_delimiter() {
    check("(a)", "a @@", "[a]", "[a]");
    check("[a]", "a @@", "(a)", "(a)");
}

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

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

#[test]
fn in_group() {
    check("a", "@@", "[a]", "[ @@ ]");
}

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

#[test]
fn keep_spacing_no_match() {
    check("a", "b", "1+2 a 3 + 4", "1+2 b 3 + 4");
}

#[test]
fn keep_spacing_var() {
    check("$e:expr", "$e @@", "1+2", "1+2 @@");
}
#[test]
fn spacing_mismatch() {
    check("=", "@@", "==", "==");
}
#[test]
fn spacing_match() {
    check("==", "@@", "==", "@@");
}

#[test]
fn tt_joint() {
    check("$x:tt", "@@", "==", "@@");
}

#[test]
fn repeat_separator() {
    check("$(a),*", "@", "a,a,a", "@");
}

#[test]
fn repeat_separator_joint() {
    check("$(a)==*", "@", "a==a==a", "@");
}

#[test]
fn rep_empty_content_and_some_sep() {
    check("$(),*", "@", ",,", "@");
}

#[test]
fn keyword_self() {
    check("self", "this", "self", "this");
}

#[test]
fn nest_no() {
    check_as(
        "a + $e:expr",
        "b + $e",
        "a + ( a + x )",
        "b + ( a + x )",
        false,
    );
}

#[test]

fn nest_yes() {
    check_as(
        "a + $e:expr",
        "b + $e",
        "a + ( a + x )",
        "b + ( b + x )",
        true,
    );
}

#[test]
fn to_long_punct() {
    check("x", "..=", "x", "..=");
}

#[test]
fn to_long_punct_tt() {
    check("$t:tt", "$t", "..=", "..=");
}