我正在尝试编写一个宏来将一组规则扩展为执行令牌匹配的代码,但是在不导致宏扩展错误的情况下无法生成正确的代码。我知道我可以处理其他方式,但这里的关键问题不是如何解析标记,而是如何编写一个可以递归扩展带有匹配臂的标记树的宏。
我们想要从字符串中读取令牌并将其打印出来。需要添加更多代码才能将其转换为更有用的代码,但此示例用于说明这种情况:
#[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
}
}
};
}
现在让我们展开这个例子来处理更高级的匹配,并允许它用lookahead读取多个字符,所以只有字符匹配某些模式。如果不是,则不应读取无关字符。我们以与上一个示例类似的方式创建一个带匹配臂的令牌树,但是在这里我们要支持一个递归结构:
#[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
之类的内容无法与令牌树匹配。
$t:tt
之类的内容与枚举常量Digit::Two
不匹配,这是一个完全有效的表达式。
像$($rest:tt)*
这样的通用匹配器会失败,因为Kleene-star闭包是贪婪的并且会尝试匹配以下逗号。
逐个匹配项目的recursive macro,例如,{ $p:pat => $t:expr, $($rest:tt)* }
行中的模式将无法在match
inner!
语句中展开因为宏以来期望语法上看起来像... => ...
的东西,所以这个扩展会给出一个错误,声称它在宏之后需要=>
:
match $e.peek() {
Some(&$p) => ...$t...,
inner!($rest)
^ Expect => here
}
这看起来就像书中提到的syntactic requirements之一。
更改匹配部分的语法不允许使用pat
要求,因为需要=>
(根据the macro chapter in the book)。
答案 0 :(得分:5)
当您需要根据此类重复内容的不同匹配进行分支时,您需要执行incremental parsing。
所以
macro_rules! parse {
这是宏的入口点。它设置最外层,并将输入提供给一般的解析规则。我们向下传递chars
,以便更深层可以找到它。
($buf:expr, {$($body:tt)*}) => {
{
let mut chars = $buf.chars().peekable();
parse! { @parse chars, {}, $($body)* }
}
};
终止规则:一旦我们用尽输入(以逗号为模),将累积的匹配臂代码片段转储到match
表达式中,并附加最终的全能臂。
(@parse $chars:expr, {$($arms:tt)*}, $(,)*) => {
match $chars.peek() {
$($arms)*
_ => None
}
};
或者,如果指定了全能臂,请使用它。
(@parse $chars:expr, {$($arms:tt)*}, _ => $e:expr $(,)*) => {
match $chars.peek() {
$($arms)*
_ => Some($e)
}
};
这会处理递归。如果我们看到一个块,我们前进$chars
并使用空代码累加器解析块的内容。所有这些的结果是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));
}