gpt4 book ai didi

macros - 在 Rust 中解析匹配武器的递归宏

转载 作者:行者123 更新时间:2023-11-29 08:03:47 24 4
gpt4 key购买 nike

我正在尝试编写一个宏来将一组规则扩展为执行标记匹配的代码,但我无法在不导致宏扩展错误的情况下生成正确的代码。我知道我可以用其他方式处理这个问题,但这里的关键问题不是如何解析标记,而是如何编写一个可以递归扩展具有匹配臂的标记树的宏。

这个想法是我们想从字符串中读取一个标记并将其打印出来。需要添加更多代码才能将其变成更有用的东西,但这个例子可以说明这种情况:

#[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));
}

忽略我们没有从字符串中读取更多标记的事实,因此 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
}
}
};
}

现在让我们扩展此示例以处理更高级的匹配,并允许它通过先行读取多个字符,因此仅当字符匹配特定模式时。如果不是,则不应读取无关字符。我们以与前面示例类似的方式创建带有匹配臂的 token 树,但这里我们要支持递归结构:

#[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 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)
}
};
}

但是,我无法在 inner! 中找到替换 ??? 的东西带有匹配表达式或标记树的内容的宏。

  • 此时 $e:expr 将无法匹配 token 树。

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

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

  • A recursive macro一个一个地匹配项目,例如,沿着 { $p:pat => $t:expr, $($rest:tt)* } 行的模式将无法在inner! 宏中的 match 语句,因为它期望在语法上看起来像 ... => ... 的东西,所以这个扩展给出了一个错误声称它期望在宏之后有 =>:

    match $e.peek() {
    Some(&$p) => ...$t...,
    inner!($rest)
    ^ Expect => here
    }

这看起来像是 syntactic requirements 之一书中提到。

改变匹配部分的语法不允许使用pat要求,因为后面需要跟一个 =>(根据 the macro chapter in the book )。

最佳答案

当你需要像这样在重复中根据不同的匹配进行分支时,你需要做 incremental parsing .

所以。

macro_rules! parse {

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

    ($buf:expr, {$($body:tt)*}) => {
{
let mut chars = $buf.chars().peekable();
parse! { @parse chars, {}, $($body)* }
}
};

终止规则:一旦我们用完输入(模数逗号),将累积的匹配臂代码片段转储到 match 表达式中,并附加最终的 catch-all 臂。

    (@parse $chars:expr, {$($arms:tt)*}, $(,)*) => {
match $chars.peek() {
$($arms)*
_ => None
}
};

或者,如果指定了 catch-all arm,则使用它。

    (@parse $chars:expr, {$($arms:tt)*}, _ => $e:expr $(,)*) => {
match $chars.peek() {
$($arms)*
_ => Some($e)
}
};

这处理递归。如果我们看到一个 block ,我们前进 $chars 并用一个空代码累加器解析 block 的内容。所有这一切的结果是 appended to the current accumulator ( $($arms))。

    (@parse $chars:expr, {$($arms:tt)*}, $p:pat => { $($block:tt)* }, $($tail:tt)*) => {
parse! {
@parse
$chars,
{
$($arms)*
Some(&$p) => {
$chars.next().unwrap();
parse!(@parse $chars, {}, $($block)*)
},
},
$($tail)*
}
};

非递归情况。

    (@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));
}

关于macros - 在 Rust 中解析匹配武器的递归宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47467568/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com