Rust: How to allow and detect optional punctionuation in macro_rules?

Viewed 295

I have been playing with Rust for a while and decided it is time to start with macros. I want to create a macro that allows for a bitwise and operation on specific bits of unsigned integer variables. Here is what I currently have which is working:

macro_rules! AND {
    ($($val:ident.$bit:literal), *) => {
        {
            let mut val_out = 0x01;
            
            $(
                val_out &= ($val >> $bit);
            )*
            
            val_out & 0x01
        }
    };
}

fn main() {
    let x = 0x01;
    let y = 0x02;
    let z = 0x10;
    
    println!("{}", AND!(x.0, y.1, z.4)); // Prints 1
    println!("{}", AND!(x.0, y.1, z.0)); // Prints 0
}

What I want to do is also allow for a negation operator. This is what I have that compiles, but I cannot figure out how to determine if the exclamation point was matched or not.

macro_rules! AND {
    ($($(!)?$val:ident.$bit:literal), *) => {
        {
            let mut val_out = 0x01;
            
            $(
                val_out &= ($val >> $bit);
            )*
            
            val_out & 0x01
        }
    };
}

fn main() {
    let x = 0x01;
    let y = 0x02;
    let z = 0x10;
    
    println!("{}", AND!(!x.0, !y.1, z.4)); // Prints 1, would like to print 0
    println!("{}", AND!(x.0, y.1, !z.0));  // Prints 0, would like to print 1
}

I have tried to match an expression instead of the exclamation point, but expressions have to be the last thing matched. I also tried to match an ident after the literal, with the idea that an underscore after the literal could denote negation, but when I used an if or match statement to determine if the ident was matched, I get an error:

macro_rules! AND {
    ($($val:ident.$bit:literal$($negate:ident)?), *) => {
        {
            let mut val_out = 0x01;
            
            $(
                if $negate == "_" {
                    val_out &= (!$val >> $bit);
                }
                else {
                    val_out &= ($val >> $bit);
                }
            )*
            
            val_out & 0x01
        }
    };
}

fn main() {
    let x = 0x01;
    let y = 0x02;
    let z = 0x10;
    
    println!("{}", AND!(x.0, y.1, z.4_));
error: variable 'negate' is still repeating at this depth
 --> src/main.rs:8:20
  |
8 |                 if $negate == "_" {
  |                    ^^^^^^^

Any ideas would be appreciated. Thanks!

2 Answers

Your proposed macro syntax with a leading but optional symbol (e.g. a !) is very difficult to express with Rust's macro_rules!. One way to simplify it, is to use a symbol for both cases (e.g. + for positive and - for negated). Then you can use a secondary macro to distinguish the two cases one-by-one:

// Auxiliary macro to distinguish positive and negative cases
macro_rules! and_aux {
    ($var:ident + $val:ident $bit:literal) => {
        $var &= ($val >> $bit);
    };
    ($var:ident - $val:ident $bit:literal) => {
        $var &= !($val >> $bit);
    };
}
// Main macro
macro_rules! AND {
    ($($t:tt $val:ident . $bit:literal), *) => {{
            let mut val_out = 0x01;

            $(
                and_aux!(val_out $t $val $bit);
            )*

            val_out & 0x01
    }};
}

fn main() {
    let x = 0x01;
    let y = 0x02;
    let z = 0x10;

    println!("{}", AND!(+x.0, +y.1, +z.4)); // Prints 1
    println!("{}", AND!(+x.0, +y.1, +z.0)); // Prints 0
    println!("{}", AND!(-x.0, -y.1, +z.4)); // Prints 0
    println!("{}", AND!(+x.0, +y.1, -z.0)); // Prints 1
}

Of course, you can also use other symbols, or you can employ brackets, which can make a lot of things possible in macro_rules!. This is partially because brackets (and that includes (), [], and {}) are the only elements that can delimit tts (tocken-trees), which you typically need for more advanced macro_rules!.


However, you can actually make your original macro syntax work, that is, if you like complex hard-to-debug macros. For instance, you can do a recursive macro that only parses a little bit of your input with each recursion, forwarding some intermediate representation to the next invocation. E.g.:

// Auxiliary macro, does the heavy lifting
macro_rules! and_inner {
    // Finishing rule, assembles the actual output
    ( @ $var:ident { $( $finished:tt )* } from { $(,)? } ) => {
        {
            let mut $var = 0x01;
            
            $( $finished )*
            
            $var & 0x01
        }
    };
    
    // Parse negated case
    ( @ $var:ident {
            $( $finished:tt )*
        } from {
            ! $val:ident . $bit:literal , // only this line is processed here
            $( $rest_input:tt )*
        }
    ) => {
        and_inner!(@ $var {
            $( $finished )*
            $var &= !($val >> $bit);
        } from {
            $( $rest_input )*
        })
    };
    
    // Parse positive case
    ( @ $var:ident {
            $( $finished:tt )*
        } from {
            $val:ident . $bit:literal , // only this line is processed here
            $( $rest_input:tt )*
        }
    ) => {
        and_inner!(@ $var {
            $( $finished )*
            $var &= ($val >> $bit);
        } from {
            $( $rest_input )*
        })
    };
}
// Main macro
macro_rules! AND {
    // Entry rule prepares input for internal macro
    ( $( $input:tt )* ) => {
        and_inner!(@ tmp_var { } from { $($input)* , })
    };
}

You can try what is called a incremental macro muncher:

macro_rules! _AND {
    ($val_out:ident , $val:ident.$bit:literal $($tail:tt)*) => {
        $val_out &= ($val >> $bit);
        _AND!{$val_out $($t)*};
    };
    ($val_out:ident , ! $val:ident.$bit:literal $($tail:tt)*) => {
        $val_out &= (!$val >> $bit);
        _AND!{$val_out $($t)*};
    };
    ($val_out:ident) => { }
}
    
macro_rules! AND {
    ($($tail:tt)*) => {
        {
            let mut val_out = 0x01;
         
            _AND!{val_out , $($tail)*};
            
            val_out & 0x01
        }
    };
}

fn main() {
    let x = 0x01;
    let y = 0x02;
    let z = 0x10;
    
    println!("{}", AND!(!x.0, !y.1, z.4)); // Prints 1, would like to print 0
    println!("{}", AND!(x.0, y.1, !z.0));  // Prints 0, would like to print 1
}

The idea is that you parse the whole contents of the macro as a list of token trees (tt), that are basically anything, and pass them on to a recursive macro, eating a bit of the those in each iteration of that macro.

Related