Timmmm
April 28, 2021, 12:51pm
1
I'm trying to write a macro that will parse the result of some dbg!()
output (so I can easily paste correct results into tests). Here's a simplified version of what I've got so far (there are more fields in the real version):
use std::collections::BTreeMap;
struct CommitInfo {
parents: Vec<String>,
}
macro_rules! commit_info_graph {
(
$(
$hash:literal : CommitInfo {
$($field_name:ident : $field_value:tt),*
}
),*
) => {{
let graph: BTreeMap<String, CommitInfo> = BTreeMap::new();
$(
graph.insert($hash.to_string(), CommitInfo {
$(
commit_info_graph!(@field $field_name $field_value),
)*
});
)*
graph
}};
(@field parents [ $($hash:expr),* ]) => {
parents: vec![$($hash, )*]
};
}
fn main() {
let expected_graph = commit_info_graph!(
"04417144022049d97bc19e759d1955958e21f339": CommitInfo {
parents: [
"a6de41485a5af44adc18b599a63840c367043e39",
],
},
"a6de41485a5af44adc18b599a63840c367043e39": CommitInfo {
parents: [
"d3591307bd5590f14ae24d03ab41121ab94e2a90",
],
},
"d3591307bd5590f14ae24d03ab41121ab94e2a90": CommitInfo {
parents: [],
},
);
}
(Playground )
Unfortunately it doesn't work! I get this error:
error: no rules expected the token `}`
--> src/main.rs:39:9
|
8 | macro_rules! commit_info_graph {
| ------------------------------ when calling this macro
...
39 | },
| ^ no rules expected this token in macro call
I can't figure it out. Can anyone help? I tried trace_macros
but it didn't add any extra information.
You have some extra trailing commas, you can fix it by strategically placing $(,)?
like so: Rust Playground
After that, you'll need to deal with macros being unable to expand to fields in struct literals.
1 Like
You can't have a macro expand to the fields of a struct only; so I'd start with:
macro_rules! commit_info_graph {
(
$(
$hash:literal : CommitInfo {
$($field_name:ident : $field_value:tt),* $(,)?
}
),* $(,)?
) => {{
let graph: BTreeMap<String, CommitInfo> = BTreeMap::new();
$(
- graph.insert($hash.to_string(), CommitInfo {
- $(
- commit_info_graph!(@field $field_name $field_value),
- )*
- });
+ graph.insert($hash.to_string(), commit_info_graph!(@fields[]
+ $(
+ $field_name $field_value,
+ )*
+ ));
)*
graph
}};
- (@field parents [ $($hash:expr),* ]) => {
- parents: vec![$($hash, )*]
- };
+ (@fields
+ [$($acc:tt)*]
+ parent [ $($hash:expr),* $(,)? ],
+ $($rest:tt)*
+ ) => (commit_info_graph! {
+ @fields[$($acc)*
+ parents: vec![$($hash.to_string(), )*],
+ ]
+ $($rest)*
+ });
+ (@fields [$($fields:tt)*] /* nothing left */) => (
+ CommitInfo { $($fields)* }
+ );
}
Timmmm
April 28, 2021, 1:18pm
5
Oooo $(foo),*
doesn't allow trailing commas? That is surprising given they are allowed everywhere else!
After that, you'll need to deal with macros being unable to expand to fields in struct literals.
Ah damn.... is there a workaround for that?
Timmmm
April 28, 2021, 1:21pm
6
Ah I found this but I think I'll just change it to:
let tmp = CommitInfo::default();
$(
tmp.$field = $value;
)*
Lots simpler.
Timmmm
April 28, 2021, 1:26pm
7
Success! Thanks!
use std::collections::BTreeMap;
#[derive(Default, Debug)]
struct CommitInfo {
parents: Vec<String>,
}
macro_rules! commit_info_graph {
(
$(
$hash:literal : CommitInfo {
$($field_name:ident : $field_value:tt),*
$(,)?
}
),*
$(,)?
) => {{
let mut graph: BTreeMap<String, CommitInfo> = BTreeMap::new();
$(
graph.insert($hash.to_string(), {
let mut info: CommitInfo = Default::default();
$(
commit_info_graph!(@set_field info, $field_name, $field_value);
)*
info
});
)*
graph
}};
(@set_field $object:ident, parents, [ $($hash:expr),* $(,)? ]) => {
$object.parents = vec![$($hash.to_string(), )*];
};
}
fn main() {
let expected_graph = commit_info_graph!(
"04417144022049d97bc19e759d1955958e21f339": CommitInfo {
parents: [
"a6de41485a5af44adc18b599a63840c367043e39",
],
},
"a6de41485a5af44adc18b599a63840c367043e39": CommitInfo {
parents: [
"d3591307bd5590f14ae24d03ab41121ab94e2a90",
],
},
"d3591307bd5590f14ae24d03ab41121ab94e2a90": CommitInfo {
parents: [],
},
);
dbg!(expected_graph);
}
system
Closed
July 27, 2021, 1:27pm
8
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.