Macros 递归宏解析Rust中的匹配臂

Macros 递归宏解析Rust中的匹配臂,macros,rust,Macros,Rust,我试图编写一个宏来将一组规则扩展为执行令牌匹配的代码,但无法生成正确的代码而不会导致宏扩展错误。我知道我可以用其他方法处理这个问题,但这里的关键问题不是如何解析令牌,而是如何编写一个宏,该宏可以使用匹配臂递归地扩展令牌树 我们希望从字符串中读取一个标记并将其打印出来。需要添加更多代码以将其转换为更有用的内容,但此示例用于说明这种情况: #[derive(Debug, PartialEq)] enum Digit { One, Two, Three, Ten,

我试图编写一个宏来将一组规则扩展为执行令牌匹配的代码,但无法生成正确的代码而不会导致宏扩展错误。我知道我可以用其他方法处理这个问题,但这里的关键问题不是如何解析令牌,而是如何编写一个宏,该宏可以使用匹配臂递归地扩展令牌树

我们希望从字符串中读取一个标记并将其打印出来。需要添加更多代码以将其转换为更有用的内容,但此示例用于说明这种情况:

#[derive(Debug, PartialEq)]
enum Digit {
    One,
    Two,
    Three,
    Ten,
    Eleven,
}

#[test]
fn test1(buf: &str) {
    let buf = "111";
    let token = parse!(buf, {
        '1' => Digit::One,
        '2' => Digit::Two,
        '3' => Digit::Three,
    });
    assert_eq!(token, Some(Digit::One));
}
我们希望从该示例生成的代码是:

fn test1(buf: &str) {
    let token = {
        let mut chars = buf.chars().peekable();
        match chars.peek() {
            Some(&'1') => {
                chars.next().unwrap();
                Digit::One
            }
            Some(&'2') => {
                chars.next().unwrap();
                Digit::Two
            }
            Some(&'3') => {
                chars.next().unwrap();
                Digit::Three
            }
            Some(_) | None => None,
        }
    };
    assert_eq!(token, Some(Digit::One));
}
fn test2() {
    let buf = "111";
    let token = {
        let mut chars = buf.chars().peekable();
        match chars.peek() {
            Some(&'1') => {
                chars.next().unwrap();
                match chars.peek() {
                    Some(&'0') => {
                        chars.next().unwrap();
                        Some(Digit::Ten)
                    },
                    Some(&'1') => {
                        chars.next().unwrap();
                        Some(Digit::Eleven)
                    },
                    Some(_) | None => Some(Digit::One)
                }
            },
            Some(&'2') => {
                chars.next().unwrap();
                Some(Digit::Two)
            },
            Some(&'3') => {
                chars.next().unwrap();
                Some(Digit::Three)
            },
            Some(_) | None => None,
        }
    };
    assert_eq!(token, Some(Digit::Eleven));
}
忽略这样一个事实:我们没有从字符串中读取更多标记,因此
chars.next().unwrap()
不是很有用。以后会有用的

生成上述代码的宏非常简单:

macro_rules! parse {
    ($e:expr, { $($p:pat => $t:expr),+ }) => {
        {
            let mut chars = $e.chars().peekable();
            match chars.peek() {
                $(Some(&$p) => {
                    chars.next().unwrap();
                    Some($t)
                },)+
                Some(_) | None => None
            }
        }
    };
}
现在,让我们扩展这个示例来处理更高级的匹配,并允许它通过前瞻读取多个字符,因此仅当字符匹配某些模式时。如果不是,则不应读取无关字符。我们以与上一个示例类似的方式创建带有匹配臂的令牌树,但这里我们希望支持递归结构:

#[test]
fn test2() {
    let buf = "111";
    let token = parse!(buf, {
        '1' => {
            '0' => Digit::Ten,
            '1' => Digit::Eleven,
            _ => Digit::One,
        },
        '2' => Digit::Two,
        '3' => Digit::Three
    });
    assert_eq!(token, Some(Digit::Eleven));
}
我们希望从该示例生成的代码是:

fn test1(buf: &str) {
    let token = {
        let mut chars = buf.chars().peekable();
        match chars.peek() {
            Some(&'1') => {
                chars.next().unwrap();
                Digit::One
            }
            Some(&'2') => {
                chars.next().unwrap();
                Digit::Two
            }
            Some(&'3') => {
                chars.next().unwrap();
                Digit::Three
            }
            Some(_) | None => None,
        }
    };
    assert_eq!(token, Some(Digit::One));
}
fn test2() {
    let buf = "111";
    let token = {
        let mut chars = buf.chars().peekable();
        match chars.peek() {
            Some(&'1') => {
                chars.next().unwrap();
                match chars.peek() {
                    Some(&'0') => {
                        chars.next().unwrap();
                        Some(Digit::Ten)
                    },
                    Some(&'1') => {
                        chars.next().unwrap();
                        Some(Digit::Eleven)
                    },
                    Some(_) | None => Some(Digit::One)
                }
            },
            Some(&'2') => {
                chars.next().unwrap();
                Some(Digit::Two)
            },
            Some(&'3') => {
                chars.next().unwrap();
                Some(Digit::Three)
            },
            Some(_) | None => None,
        }
    };
    assert_eq!(token, Some(Digit::Eleven));
}
尝试编写一个宏来处理此问题可能大致如下所示:

macro_rules! expand {
    ($t:tt) => {{
        chars.next().unwrap();
        inner!($t)
    }};
    ($e:expr) => {{
        chars.next().unwrap();
        Some($e)
    }};
}

macro_rules! inner {
    ($i:ident, { $($p:pat => ???),+ }) => {
        match $i.peek() {
            $( Some(&$p) => expand!($i, ???), )+
            Some(_) | None => None
        }
    };
}

macro_rules! parse {
    ($e:expr, $t:tt) => {
        {
            let mut chars = $e.chars().peekable();
            inner!(chars, $t)
        }
    };
}
但是,我在
内部找不到可以替换
的东西 与表达式或标记树匹配的宏

  • 类似于
    $e:expr
    的内容此时将无法匹配令牌树

  • 类似于
    $t:tt
    的内容与枚举常量
    Digit::Two
    不匹配,这是一个完全有效的表达式

  • $($rest:tt)*
    这样的通用匹配器将失败,因为Kleene星形闭包是贪婪的,并且将尝试匹配以下逗号

  • 一个项目一个项目地匹配,例如沿着
    {$p:pat=>$t:expr,$($rest:tt)*}
    行的模式将无法在
    内部的
    match
    语句中展开宏,因为它期望语法上看起来像
    …=>,因此此扩展提供了一个错误,声称它希望宏后面有一个
    =>

    match $e.peek() {
         Some(&$p) => ...$t...,
         inner!($rest)
                       ^ Expect => here
    }
    
这看起来像书中提到的其中一个

更改匹配部件的语法不允许使用
pat

需求,因为这需要后面跟着一个
=>
(根据)。

当您需要基于类似这样的重复中的不同匹配进行分支时,您需要这样做

所以

这是宏的入口点。它设置最外层,并将输入输入输入到通用解析规则中。我们向下传递
字符
,以便更深层的层可以找到它

    ($buf:expr, {$($body:tt)*}) => {
        {
            let mut chars = $buf.chars().peekable();
            parse! { @parse chars, {}, $($body)* }
        }
    };
终止规则:一旦输入用完(对一些逗号进行模化),将累积的匹配arm代码片段转储到
match
表达式中,并附加最终的catch all arm

    (@parse $chars:expr, {$($arms:tt)*}, $(,)*) => {
        match $chars.peek() {
            $($arms)*
            _ => None
        }
    };
或者,如果指定了“全部捕捉”臂,则使用该臂

    (@parse $chars:expr, {$($arms:tt)*}, _ => $e:expr $(,)*) => {
        match $chars.peek() {
            $($arms)*
            _ => Some($e)
        }
    };
这将处理递归。如果我们看到一个块,我们前进
$chars
,并用空的代码累加器解析该块的内容。所有这些的结果是(即,
$($arms)

非递归情况

    (@parse $chars:expr, {$($arms:tt)*}, $p:pat => $e:expr, $($tail:tt)*) => {
        parse! {
            @parse
            $chars,
            {
                $($arms)*
                Some(&$p) => Some($e),
            },
            $($tail)*
        }
    };
}
为了完整性,测试代码的其余部分。请注意,我必须更改
test1
,因为它不是有效的测试

#[derive(Debug, PartialEq)]
enum Digit { One, Two, Three, Ten, Eleven }

#[test]
fn test1() {
    let buf = "111";
    let token = parse!(buf, {
        '1' => Digit::One,
        '2' => Digit::Two,
        '3' => Digit::Three,
    });
    assert_eq!(token, Some(Digit::One));
}

#[test]
fn test2() {
    let buf = "111";
    let token = parse!(buf, {
        '1' => {
            '0' => Digit::Ten,
            '1' => Digit::Eleven,
            _ => Digit::One,
        },
        '2' => Digit::Two,
        '3' => Digit::Three,
    });
    assert_eq!(token, Some(Digit::Eleven));
}

我测试了您的示例,效果很好,但当我尝试将其更改为使用单独的宏
内部时
没有添加
@parse
标记,而是在0之后的
=>
上出现错误。我检查了代码并与您的代码进行了比较,但我不明白为什么它不能处理与输入相同的结构。你有什么解释吗?@MatsKindahl:嗯,我不是通灵者,所以我不知道你改变了什么。显然有些重要的事情。另外,我也不建议使用两个宏;将宏的所有功能都放在一个地方通常会更干净。下面的参数完全有效,但我想知道是否有一些宏解析的语义我遗漏了。如果添加
@parse
在这方面没有什么特别之处,我就把它归咎于我犯的一些错误。@MatsKindahl:如果你不告诉我你是如何更改它的,我就帮不了你。我无法诊断我看不见的代码。这是一个愚蠢的错误,语义中没有任何东西需要使用关键字,尽管我同意这样做的原因。谢谢你的帮助!