I am trying to create a simple map
macro that should be called like this:
let c = "Content of c";
map![
a: "Content of a",
c, // auto expand, same as `c: "Content of c"`
d: 0xDC097397
]
and returns a Map(&[(String, String)])
struct.
I wrapped it into a struct to implement some properties like Display
or Debug
, and I'm using a reference to a slice because a slice is not Sized
.
I can write a macro that parses only the full form (a: "b"
) or the short form (just a
) but I can't think of one that will be able to match both.
The one matching the short form:
($($key:ident),*) => {
crate::Map(&[
$((stringify!($key).to_string(), $key.to_string())),*
][..])
};
The one matching the full form:
($($key:ident : $value:expr),*) => {
crate::Map(&[
$((stringify!($key).to_string(), $value.to_string())),*
][..])
};
What I've tried: making two separate macros that match only one line and then passing a tt
to it.
macro_rules! map_one {
($key:ident) => {
(stringify!($key).to_string(), $key.to_string())
};
($key:ident : $value:expr) => {
(stringify!($key).to_string(), $value.to_string())
};
}
macro_rules! map {
($($tt:tt),*) => {
crate::Map(&[
$(map_one!($tt)),*
][..])
};
}
I get a compile error:
no rules expected the token `:`
while trying to match `,`.
The tt
matcher matches a token tree, which is either a single token (ident, operator, :
, etc) or a set of tokens grouped within a pair of parenthesis, square brackets, or curly braces.
So key: "value"
is actually three tt
s. The way to work around this is to match optionally on the : $value:expr
part with a $()?
group, them pass the whole thing along to your map_one
macro:
macro_rules! map {
($($key:ident $(: $value:expr)?),*) => {
crate::Map(&[
$(map_one!($key $(: $value)?)),*
][..])
};
}
I would also recommend merging the two macros using the @prefix
pattern:
macro_rules! map {
(@one $key:ident) => {
(stringify!($key).to_string(), $key.to_string())
};
(@one $key:ident : $value:expr) => {
(stringify!($key).to_string(), $value.to_string())
};
($($key:ident $(: $value:expr)?),*) => {
crate::Map(&[
$(map!(@one $key $(: $value)?)),*
][..])
};
}