首页 > 解决方案 > 如何匹配宏中的点并重建原始令牌集?

问题描述

我正在尝试编写一个宏来扩展它:

let res = log_request_response!(client.my_call("friend".to_string(), 5));

进入这个:

let res = {
    debug!("Request: {}", args_separated_by_commas);
    let res = client.my_call("friend".to_string(), 5);
    debug!("Response: {}", res);
    res
};

到目前为止,我的尝试是这样的:

#[macro_export]
macro_rules! log_request_response_to_scuba {
    ($($client:ident)?.$call:ident($($arg:expr),*);) => {
        let mut s = String::new();
        $(
            {
                s.push_str(&format!("{:?}, ", $arg));
            }
        )*
        s.truncate(s.len() - 2);
        debug!("Request: {}", s);
        // Somehow reconstruct the entire thing with res = at the start.
        debug!("Response: {}", res);
        res
    };
}

但这无法编译:

error: macro expansion ignores token `{` and any following
  --> src/main.rs:10:13
   |
10 |             {
   |             ^
...
39 |     let res = log_request_response_to_scuba!(client.my_call(hey, 5));
   |               ------------------------------------------------------ caused by the macro expansion here
   |
   = note: the usage of `log_request_response_to_scuba!` is likely invalid in expression context

如果我删除和匹配.之间的中间,它会抛出一个关于不明确匹配的不同错误(这是有道理的)。clientcall

所以我的第一个重要问题是我如何匹配一个点?对我来说,这场比赛看起来是正确的,但显然不是。

除此之外,制作一个可以满足我要求的宏的任何帮助都会很棒。如果它是一个正则表达式,我只想要这个:

.*\((.*)\).*

我只是捕获括号内的东西并将它们分开。然后我使用第 0 个捕获组来获取整个内容。

谢谢!

标签: rustrust-macros

解决方案


错误消息不是因为您以某种方式错误地匹配点,而是因为您没有返回表达式。你想返回这个:

{
    debug!("Request: {}", args_separated_by_commas);
    let res = client.my_call("friend".to_string(), 5);
    debug!("Response: {}", res);
    res
};

但是,您的宏当前返回更类似于此的内容:

debug!("Request: {}", args_separated_by_commas);
let res = client.my_call("friend".to_string(), 5);
debug!("Response: {}", res);
res

注意缺少的花括号。这可以很容易地通过将完整的转录器部分包含在大括号中来解决。


我不确定为什么client在你的匹配器中是可选的。我假设您希望有选择地允许宏的用户调用某个变量的函数或方法。那是对的吗?如果是,那么您的代码当前不允许这样做 - 它匹配client.my_call(...)以及.some_function(...),但不是some_function(...)(注意从开头删除的空格)。为了做你想做的事,你可以匹配$variable:ident$(.$field:ident)?——注意点在这里也是可选的——或者甚至更好$variable:ident$(.$field:ident)*地允许在一个 loval 变量的字段的字段上调用一个方法(所以,像variable.sub_struct.do_something().


生成的代码带有一些示例:

macro_rules! log_request_response {
    ($variable:ident$(.$field:ident)*($($arg:expr),*)) => {
        {
            let mut s = String::new();
            $(
                {
                    s.push_str(&format!("{:?}, ", $arg));
                }
            )*
            s.truncate(s.len() - 2);
            // using println! here because I don't want to set up logging infrastructure
            println!("Request: {}", s);
            let res = $variable$(.$field)*($($arg),*);
            println!("Response: {}", res);
            res
        }
    };
}

fn test_func(_: String, i: i32) -> i32 {
    i
}

struct TestStruct;

impl TestStruct {
    fn test_method(&self, _: String, i: i32) -> i32 {
        i
    }
}

fn main() {
    let _ = log_request_response!(TestStruct.test_method("friend".to_string(), 5));
    let _ = log_request_response!(test_func("friend".to_string(), 5));
}

操场


推荐阅读