Reputation: 6353
I'm trying to write a macro that will expand this:
let res = log_request_response!(client.my_call("friend".to_string(), 5));
Into this:
let res = {
debug!("Request: {}", args_separated_by_commas);
let res = client.my_call("friend".to_string(), 5);
debug!("Response: {}", res);
res
};
My attempt so far is something like this:
#[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
};
}
But this fails to compile:
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
If I remove the .
in between the client
and call
match it throws a different error about an ambiguous match (which makes sense).
So my first nitty gritty question is how do I match a dot? To me this match looks correct but apparently not.
Beyond that any help with making a macro that does what I want would be great. If it were a regex I'd just want this:
.*\((.*)\).*
Where I just capture the stuff inside the parentheses and split them. Then I use the 0th capture group to get the whole thing.
Thanks!
Upvotes: 2
Views: 717
Reputation: 3679
The error message is not because you are matching the dot somehow wrong, but because you are not returning an expression. You want to return this:
{
debug!("Request: {}", args_separated_by_commas);
let res = client.my_call("friend".to_string(), 5);
debug!("Response: {}", res);
res
};
However, your macro currently returns something more akin to this:
debug!("Request: {}", args_separated_by_commas);
let res = client.my_call("friend".to_string(), 5);
debug!("Response: {}", res);
res
Note the missing curly braces. This can be remedied quite easily by enclosung the complete transcriber part in braces.
I am not sure why client
is optional in your matcher. I assume that you want to optionally allow the user of the macro to either call a function or a method on some variable. Is that correct? If yes, then your code currently does not allow that – it matches client.my_call(...)
as well as .some_function(...)
, but NOT some_function(...)
(note the removed space from the beginning). To do what you want, you could match on $variable:ident$(.$field:ident)?
– note that the dot is here optional as well – or even better $variable:ident$(.$field:ident)*
to allow to call a method on a field of a field of a loval variable (so, something like variable.sub_struct.do_something()
.
The resulting code with some examples:
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));
}
Upvotes: 5