Handle optional sub-pattern in macro

Hello guys, I am currently struggling with a use case, where I want to achieve the following things:

Basically, I would have a macro to translate customized format to a function

my_macro!(
    name, // is an ident, mandatory
    <expr1>, // is an expr, optional
    [expr2], // is an expr, optional
)

name(Some(expr1), Some(expr2)) // my_marcro!(name, <expr1>, [expr2])
name(None, Some(expr2)) // my_marcro!(name, [expr2])
name(Some(expr1), None) // my_marcro!(name, <expr1>)
name(None, None) // my_marcro!(name)

I know that I can define multiple patterns to represent case1 to case 4 and give a proper template. However, if am wondering if there is a way or some sys-builtin macros (e.g stringify!) which can handler something like:

($name:ident, $(<$expr1>),? $([$expr2]),?)
=> {
    let expr_1 = Some( $( $expr1 )? ) // or None
    let expr_2 = Some( $( $expr2 )? ) // or None

    name(expr_1, expr_2)
}

First of all, <$smth:expr> is not supported on macro_rules!, since when parsing 3 > ... it will be ambiguous whether $smth := 3 or if we are to keep parsing beyond the > for an :expr consisting of a comparison.

Only (), {} and [] are special in that the closing token can be unambiguously parsed after an :expr.

So for the rest of the example, I will instead use the following syntax:

$name:ident (
    $($fst:expr)?
    ,
    $($snd:expr $(,)?)?
)
  • (Feel free to tinker with another supported syntax of your liking)

So, now the question is what to do with the expansion.

The expansion

One neat trick / hack is to (ab)use dummy mutations (or shadowing, but in that case there may be type inference issues) and a potential unused binding to achieve the desired effect:

let mut fst = None;
fst = fst; // to avoid unused_mut warning
$(
    drop(fst); // against unused assignment
    fst = Some($fst);
)?
// ditto for `snd`
$name(fst, snd)

The other option is to use the following weird but super general and thus useful helper macro:

/// Expand to the tokens present in the first
/// braced parameter
macro_rules! ite {(
    { $($tt:tt)* }
    $($ignored:tt)?
) => (
    $($tt)*
)}

and then you can expand to:

$name(
    ite!( $({ Some($fst) })? { None } ),
    ite!( $({ Some($snd) })? { None } ),
)
2 Likes

Thanks Yadros!

First of all, sorry for the abuse of <> in the example, it was a quick pseudocode and I did't test it in the compiler. The idea was just to differentiate the two patterns of expr1 and expr2.

I ended up with a similar helper macro for that, it works fine.

#[macro_export]
macro_rules! some_or_none {
    () => { None };
    ($entity:expr) => { Some($entity) };
}

But still, I wish to create a proper reproducible example by getting ride of <> issue. However, I encounter another ambiguity error...

#[macro_export]
macro_rules! example {
    (
        $name:ident
        $(($expr1:expr, $expr2:expr))?
        $(($expr3:expr))?
    ) => {
        $name(
            some_or_none!($(($expr1, $expr2))?),
            some_or_none!($($expr3)?),
        )
    };
}

#[test]
fn test_ambiguity() {
    example!(
        dummy
        ("sth1", "sth2") // Optional, but this two expression will always appear or not
        ("sht3") // Optional,
    );
}

I wish a result that allows me to do the following

// Expectation: dummy(Some(("sth1", "sth2")), Some("sth3"))
example!(dummy ("sth1", "sth2") ("sht3")) 
// Expectation: dummy(None, Some("sth3"))
example!(dummy ("sht3"))
// Expectation: dummy(Some(("sth1", "sth2")), None)
example!(dummy ("sht1", "sht2")) 
// Expectation: dummy(None, None)
example!(dummy)

When I testing, I receive an error:

error: local ambiguity: multiple parsing options: built-in NTs expr ('expr3') or expr ('expr1').

What I understood is that I am trying to differentiate two patterns by giving them different formats, pattern1 -> (xx, xx) pattern2 -> (xx), but it doesn't seem legal in the compiler ...

OK, the issue for this pattern is that ("sth1", "sht2") and ('sht3') itself are tuple, and it's considered all as an expression in rust. The compiler can not determine this.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.