Search code examples
rustmacrosrust-macros

Can I specify an operator or other syntactic literal within a macro input?


First, I know this is not a good use of macros but I'm learning what I can do.

I have a struct Rational:

pub struct Rational{
    pub n: i128,
    pub d : i128
}

I have a macro that builds these:

macro_rules! rat{
    ($n : expr,  $d : expr) => {
        Rational{n : $n,d:  $d}
    }
}

The syntax for calling that macro looks like : rat!(1, 3). I would like it to visually show a fractional format, so you would call it like rat!(1 / 3) instead, and the macro would parse that expression to yield the same result. (Note that if it first evaluates 1/3, even as a float type, it will not exactly correspond as 1/3 does not correspond exactly to any float.)

I'm hoping there's some syntax like:

macro_rules! rat{
    ($n : expr `/` $d : expr) => {
        Rational{n : $n,d:  $d}
    }
}

where I can specify syntax to be used in the call. (That attempt does not compile, of course.)

Again, obviously this is silly and an X/Y problem and all that. For context, I'm considering building an equation wrangling system, and at some point I'd like to be able to parse things from a math-y syntax, which might actually justify the use of macros. This is just a baby step.

Does such a thing exist using declarative macros? If not, is it possible with procedural macros? Finally, I know that in Scala macros there would be no way to make that work for literal values, because the expression 1/3 would be resolved so early in the compilation process the AST would be gone by the time even macros are called. Is that also the case in Rust?


Solution

  • Yes, you can use the / token directly in the rule:

    #[derive(Debug)]
    struct Rational{
        n: i128,
        d: i128
    }
    
    macro_rules! rat {
        ($n:literal / $d:literal) => {
            Rational { n: $n, d: $d }
        };
    }
    
    fn main() {
        println!("rat!(1 / 3) = {:?}", rat!(1 / 3));
    }
    
    rat!(1 / 3) = Rational { n: 1, d: 3 }
    

    However, notice I have changed the arguments from expr to literal. Your question seems to imply this is fine for your use-case, but I bring it up because at least the first parameter $n cannot be an expr. It would cause a parsing ambiguity because / is a valid continuation of an expression and Rust simply doesn't allow / as a separator after a expr. And this goes for all operators; from the follow-set ambiguity restrictions, only =>, ,, or ; may follow an expr.

    If you do want to allow any expr, a common trick is to require parenthesis:

    macro_rules! rat {
        ($n:literal / $d:expr) => {
            Rational { n: $n, d: $d }
        };
        (($n:expr) / $d:expr) => {
            Rational { n: $n, d: $d }
        };
    }
    
    fn main() {
        println!("rat!(1 / 3) = {:?}", rat!(1 / 3));
        println!("rat!((1 + 2) / 3) = {:?}", rat!((1 + 2) / 3));
    }