我正在尝试使用Rust的macro_rules
并希望创建一个可以解析HTML语法的宏,并简单地将HTML作为字符串回显。下面的宏大部分都在那里:
macro_rules! html {
() => ("");
($text:tt) => {{
format!("{}", $text)
}};
(<$open:ident>[$($children:tt)*]</$close:ident>$($rest:tt)*) => {{
format!("<{}>{}</{}>{}",
stringify!($open),
html!($($children)*),
stringify!($close),
html!($($rest)*))
}};
}
然后使用宏:
println!("{}",
html!(
<html>[
<head>[
<title>["Some Title"]</title>
]</head>
<body>[
<h1>["This is a header!"]</h1>
]</body>
]</html>
)
);
但是,我真的想删除无关的开始和结束方括号。我试着这样做:
macro_rules! html_test {
() => ("");
($text:tt) => {{
format!("{}", $text)
}};
(<$open:ident>$($children:tt)*</$close:ident>$($rest:tt)*) => {{
format!("<{}>{}</{}>{}",
stringify!($open),
html!($($children)*),
stringify!($close),
html!($($rest)*))
}};
}
然而,当我去使用这个宏时:
println!("{}",
html_test!(
<html>
<head>
<title>"Some Title"</title>
</head>
<body>
<h1>"This is a header!"</h1>
</body>
</html>
)
);
我得到了error: local ambiguity: multiple parsing options: built-in NTs tt ('children') or 1 other option.
我知道这个错误的一般解决方案是添加语法以消除案例歧义(例如添加方括号)。对于这个具体的例子,还有其他方法解决这个问题吗?我知道使用过程宏将是一个极端的解决方案,但如果可能的话,我宁愿使用macro_rules
。
我意识到使用宏来简单地获取包含HTML的字符串是过度的,但这完全是为了这个问题。可能,人们可以使用宏做更多有趣的事情,例如调用函数来构建表示HTML结构的树。
答案 0 :(得分:5)
您希望宏实际可用吗?那就不要。实际上,为什么甚至在这里使用宏呢?无论你做什么,你都会在某些时候与Rust lexer作斗争。只需将HTML写在字符串文字中,如:
r##"<html>
<head>
<title>Some Title</title>
</head>
<body>
<h1>This is a header!</h1>
</body>
</html>"##
那或者接受宏输入不能匹配实际的HTML语法,关闭标签,继续前进。
你还在吗?哦,所以你不关心可用性或性能?您真的希望语法略有改进,无论成本如何? * 卷起袖子 *
小心你的意愿。
您需要使用增量解析器,它允许您绕过一些模糊的解析问题。而不是尝试匹配非分隔组(您不能这样做),而是递归匹配唯一的前缀。这样做会导致:
macro_rules! html_test {
(@soup {$($parts:expr,)*}, [], ) => {
concat!($($parts),*)
};
(@soup $parts:tt, [$head:ident $($stack:ident)*], ) => {
compile_error!(
concat!(
"unexpected end of HTML; the following elements need closing: ",
stringify!($head),
$(",", stringify!($stack),)*
"."
)
)
};
(@soup {$($parts:tt)*}, [$ex_close:ident $($stack:ident)*], </$got_close:ident> $($tail:tt)*) => {
{
macro_rules! cmp {
($ex_close) => {
html_test!(
@soup
{$($parts)* "</", stringify!($ex_close), ">",},
[$($stack)*], $($tail)*
)
};
($got_close) => {
compile_error!(
concat!(
"closing element mismatch: expected `",
stringify!($ex_close),
"`, got `",
stringify!($got_close),
"`"
)
)
};
}
cmp!($got_close)
}
};
(@soup {$($parts:tt)*}, $stack:tt, <img $($tail:tt)*) => {
html_test!(@tag {$($parts)* "<img",}, $stack, $($tail)*)
};
(@soup {$($parts:tt)*}, [$($stack:ident)*], <$open:ident $($tail:tt)*) => {
html_test!(
@tag
{$($parts)* "<", stringify!($open),},
[$open $($stack)*],
$($tail)*
)
};
(@soup {$($parts:tt)*}, $stack:tt, $text:tt $($tail:tt)*) => {
html_test!(@soup {$($parts)* $text,}, $stack, $($tail)*)
};
(@tag {$($parts:tt)*}, $stack:tt, > $($tail:tt)*) => {
html_test!(@soup {$($parts)* ">",}, $stack, $($tail)*)
};
(@tag {$($parts:tt)*}, $stack:tt, $name:ident=$value:tt $($tail:tt)*) => {
html_test!(
@tag
{$($parts)* " ", stringify!($name), "=", stringify!($value),},
$stack, $($tail)*
)
};
($($tts:tt)*) => {
html_test! { @soup {}, [], $($tts)* }
};
}
这可以通过爬行输入令牌,跟踪需要输出的字符串片段(在$($parts)*
中),以及需要关闭的已打开标签(在$($stack)*
中)来实现。一旦它没有输入,并且堆栈为空,它concat!
将所有部分放在一起,产生一个静态字符串文字。
这有四个问题:
这会像疯了一样通过递归级别来咀嚼。如果用完,用户将需要全局提高递归限制。
像这样的宏慢。
错误报告很糟糕。虽然这会检查结束标记是否与相应的开始标记匹配,但是在调用的任何特定位置都没有报告问题。
您仍然无法使用字符串文字。您无法匹配<
或其他表达式后面的表达式,因此匹配字符串必须是(唯一)后备规则。
所以你可以删除分隔符,但我不推荐它。只需引用HTML就像一个理智的人。
顺便说一下,这里有一个alternative version of the macro结构略有不同,它会导致cmp
宏的因素,并且更容易扩展元素而不关闭标签。请注意,我没有写这个版本。