Rust:如何允许和检测 macro_rules 中的可选标点符号?

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

我玩 Rust 有一段时间了,决定是时候从宏开始了。我想创建一个宏,允许对无符号整数变量的特定位进行按位和运算。这是我目前正在使用的东西:

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
}

我想做的也是允许否定运算符。这是我编译的内容,但我不知道如何确定感叹号是否匹配。

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
}

我尝试匹配表达式而不是感叹号,但表达式必须是最后匹配的内容。我也尝试在文字后面匹配一个ident,认为文字后面的下划线可以表示否定,但是当我使用if或match语句来确定ident是否匹配时,我得到一个错误:

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 == "_" {
  |                    ^^^^^^^

如有任何想法,我们将不胜感激。谢谢!

你提出的带有前导但可选符号(例如 !)的宏语法很难用 Rust 的 macro_rules! 表达。简化它的一种方法是对两种情况都使用一个符号(例如 + 表示肯定,- 表示否定)。那么就可以用一个二级宏来一一区分这两种情况:

// 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
}

当然,你也可以使用其他符号,或者你可以使用括号,这可以使macro_rules!中的很多事情成为可能。这部分是因为方括号(包括 ()[]{})是唯一可以分隔 tts(标记树)的元素,您通常需要更高级的 macro_rules!.


但是,如果您喜欢复杂且难以调试的宏,您实际上可以让您的原始宏语法起作用。例如,你可以做一个递归宏,每次递归只解析一点输入,将一些中间表示转发到下一个调用。例如:

// 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)* , })
    };
}

你可以试试所谓的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
}

这个想法是将宏的全部内容解析为 令牌树 (tt) 的列表,基本上可以是任何东西,然后传递它们到递归宏,在该宏的每次迭代中吃掉一些。