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

Jer*_*mas 6 macros rust

我已经使用 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
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

我尝试匹配表达式而不是感叹号,但表达式必须是最后匹配的内容。我还尝试在文字后面匹配 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_));
Run Code Online (Sandbox Code Playgroud)
error: variable 'negate' is still repeating at this depth
 --> src/main.rs:8:20
  |
8 |                 if $negate == "_" {
  |                    ^^^^^^^

Run Code Online (Sandbox Code Playgroud)

任何想法,将不胜感激。谢谢!

Cry*_*jar 3

您建议的带有前导但可选符号(例如 a !)的宏语法很难用 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
}
Run Code Online (Sandbox Code Playgroud)

当然,你也可以使用其他符号,或者你可以使用括号,这可以让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)* , })
    };
}
Run Code Online (Sandbox Code Playgroud)