I'm trying to write a macro for a formatting library of sorts. The idea is you can use a mix of string literals, regular functions, and (this is where I get stuck) inferred/scoped functions. I suspect I could get this working with proc macros, but that's more complicated, and I'd like to keep the code for the macro easier to read if possible.
Here is a trimmed down version of the code that I'm writing.
enum TemplateEntry<T> {
Text(String),
Typed(T, String),
}
enum ColorType {
Red(String),
Blue(String),
}
impl ColorType {
pub fn red(s: &str) -> Self {
Self::Red(s.to_string())
pub fn blue(s: &str) -> Self {
Self::Blue(s.to_string())
}
}
impl Into<TemplateEntry<ColorType>> for ColorType {
fn into(&self) -> TemplateEntry<Self> {
match self {
Self::Blue(text) => TemplateEntry::Typed(self.clone(), text.clone()),
Self::Red(text) => TemplateEntry::Typed(self.clone(), text.clone()),
}
}
}
macro_rules! markup_helper {
($temp_vec:ident, $type_id:tt, @ $x:expr) => {{
$temp_vec.push($type_id::$x);
}};
( $temp_vec:ident, $type_id:tt, $x:expr) => {{
$temp_vec.push($x.into());
}};
}
#[macro_export]
macro_rules! markup {
( $type_id:tt, $( $($opt:literal)? $rest:expr ),* ) => {
{
let mut temp_vec: Vec<TemplateEntry<$type_id>> = Vec::new();
$(
markup_helper!(temp_vec, $type_id, $($opt)? $rest);
)*
temp_vec
}
};
}
fn example_call() {
let _example = markup!(ColorType, "regular text", @blue("other text"), "more regular text", regular_func_returning_text());
// What I want it to expand to
let _example = {
let mut temp_vec: Vec<TemplateEntry<ColorType>> = Vec::new();
temp_vec.push("regular text".into());
temp_vec.push(ColorType::blue("other text"));
temp_vec.push("more regular text".into());
temp_vec.push(regular_func_returning_text().into());
temp_vec
};
}
The @
lookup doesn't work properly because the compiler can't tell if string literals are the optional literal or the expression. Also, it doesn't seem to forward the @
to the helper if I try to parse it with a recursively defined macro.
Is there a way I can leverage use
to try and look up associated functions on the base type maybe without the @
indicator?
Is there something obvious I'm missing for getting @
or really any other handy indicator symbol to work that would allow a macro like this to work?
Thanks to @eggyal, here is the final version I ended up with, which is almost entirely what they provided, just refactored for my own personal preferences.
macro_rules! markup_internal {
// Expand leading `@` into `$type_id::`
(($temp_vec:ident:$type_id:ident): [@ $($rest:tt)*]) => {
markup_internal!(($temp_vec:$type_id): [$type_id::$($rest)*])
};
// Handle all other entries
(($temp_vec:ident:$type_id:ident): [$expr:expr$(, $($rest:tt)*)?]) => {
// Actually append the entries
$temp_vec.push($expr.into());
// Recursively expand
$(markup_internal!(($temp_vec:$type_id): [$($rest)*]))?
};
// Supports trailing commas
(($temp_vec:ident:$type_id:ident): []) => {}
}
#[macro_export]
macro_rules! markup {
// set up the array and push each list item into it by recursively invoking this macro
($type_id:ident: [$($rest:tt)*]) => {{
let mut temp_vec = Vec::<TemplateEntry<$type_id>>::new();
markup_internal!((temp_vec:$type_id): [$($rest)*]);
temp_vec
}};
}