So, first of all, here is the more general version of what you said above:
#[derive(Debug)]
enum Entry<K, V> {
Single { key: K, value: V },
Group { key: K, items: Vec<Entry<K, V>> },
}
macro_rules! stuff {
(@parse [ $($inner:tt)* ], ) => {
vec![$($inner)*]
};
(@parse [ $($acc:tt)* ], $key:literal ( $($inner:tt)* ) $($tail:tt)*) => {
stuff!(
@parse
[
$($acc)*
Entry::Group {
key: $key,
items: stuff!(@parse [], $($inner)*),
},
],
$($tail)*
)
};
(@parse [ $($acc:tt)* ], $key:literal $value:literal $($tail:tt)*) => {
stuff!(
@parse
[
$($acc)*
Entry::Single {
key: $key,
value: $value,
},
],
$($tail)*
)
};
( $($toks:tt)* ) => {
stuff!(@parse [], $($toks)*)
};
}
fn main() {
let data = stuff!(
"this" 42
"that" 66
"bunch of" (
"bananas" 33
"flowers" 44
"grapes" 55
)
);
println!("{data:#?}");
}
/*
== Output ==
[
Single {
key: "this",
value: 42,
},
Single {
key: "that",
value: 66,
},
Group {
key: "bunch of",
items: [
Single {
key: "bananas",
value: 33,
},
Single {
key: "flowers",
value: 44,
},
Single {
key: "grapes",
value: 55,
},
],
},
]
*/
Unlike the previous one, this works by accumulating the contents of each vec![...]
call in the $acc
group before finally outputting it when it's run out of input. You have to do this because you can't return loose tokens that you then stuff back into the vec!
macro after the fact. Macros have to result in complete syntax constructs, so we have to buffer output until we have a complete syntax construct.
Still, it's fundamentally the same basic principle: match the very next part of the input, handle that in isolation, then recurse on the remainder.
Now, I was ~85% of the way through this when I realised the enum you described can't have nested groups. That actually requires a slightly different construction for the macro. To whit:
#[derive(Debug)]
struct Item<K, V> {
key: K,
value: V,
}
#[derive(Debug)]
enum Entry<K, V> {
Single(Item<K, V>),
Group { key: K, items: Vec<Item<K, V>> },
}
macro_rules! stuff {
(@parse [ $($inner:tt)* ], ) => {
vec![$($inner)*]
};
(@parse [ $($acc:tt)* ], $key:literal ( $($inner:tt)* ) $($tail:tt)*) => {
stuff!(
@parse
[
$($acc)*
Entry::Group {
key: $key,
items: stuff!(@inner [], $($inner)*),
},
],
$($tail)*
)
};
(@parse [ $($acc:tt)* ], $key:literal $value:literal $($tail:tt)*) => {
stuff!(
@parse
[
$($acc)*
Entry::Single(Item {
key: $key,
value: $value,
}),
],
$($tail)*
)
};
(@inner [ $($inner:tt)* ], ) => {
vec![$($inner)*]
};
(@inner [ $($acc:tt)* ], $key:literal $value:literal $($tail:tt)* ) => {
stuff!(
@inner
[
$($acc)*
Item {
key: $key,
value: $value,
},
],
$($tail)*
)
};
( $($toks:tt)* ) => {
stuff!(@parse [], $($toks)*)
};
}
The difference here is that there are two distinct "levels" of parsing: outer and inner. This is to account for the different construction syntax for the individual items. There are ways of abstracting that, but it's probably not worth it.
That should be fine. That said, if you use a lot of these, you might want to investigate writing a procedural macro instead. A procedural macro takes longer to build the first time, but will run faster on subsequent rebuilds. Given enough invocations, it'll eventually beat out macro_rules!
.