如何匹配宏中的点并重建原始标记集?

How to match dot in macro and reconstruct original set of tokens?

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

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个捕获组来获取整个东西。

谢谢!

错误消息不是因为您匹配的点不知何故错误,而是因为您没有 returning 表达式。你想要 return 这个:

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

但是,您的宏目前 return 更类似于此:

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(...)(请注意从开头删除的 space)。为了做你想做的事,你可以在 $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));
}

Playground