macros - 克服 Rust 宏中的 "local ambiguity: multiple parsing options:"

标签 macros rust

我正在试验 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>
    )
);

我收到错误:局部歧义:多个解析选项:内置 NTs tt('children')或 1 个其他选项。

我知道这个错误的一般解决方案是添加语法来消除歧义(例如添加方括号)。对于这个特定示例,还有其他方法可以解决此问题吗?我知道使用过程宏是一种极端的解决方案,但如果可能的话,我更愿意使用 macro_rules

我意识到使用宏来简单地获取包含 HTML 的字符串是多余的,但这只是为了这个问题。潜在地,可以使用宏做更多有趣的事情,例如调用函数来构建表示 HTML 结构的树。

最佳答案

您想让宏真正可用吗?那就不要。实际上,为什么还要在这里使用宏呢?无论你做什么,总有一天你会与 Rust 词法分析器作斗争。只需将 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!将所有部分放在一起,生成一个静态字符串文字。

这有四个问题:

  1. 这疯狂地咀嚼递归级别。如果用完了,用户需要全局调高递归限制。

  2. 像这样的宏很慢

  3. 错误报告很糟糕。虽然这将检查结束标记是否与相应的开始标记相匹配,但不会在调用的任何特定位置报告问题。

  4. 您仍然无法避免使用字符串文字。您不能匹配后跟 < 的表达式或另一个表达式,因此匹配字符串必须是(唯一的)回退规则。

所以您可以删除定界符,但我不推荐这样做。像一个正常人一样引用 HTML。


顺便说一句,这是一个 alternative version of the macro结构略有不同,可以分解出 cmp宏,并且在不关闭标签的情况下更容易扩展元素。请注意,我没有写这个版本。

关于macros - 克服 Rust 宏中的 "local ambiguity: multiple parsing options:",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46136169/

相关文章:

c++ - 调用宏时参数太多

c - 如何在 linux C 内联汇编中使用宏

rust - 有条件地迭代几个可能的迭代器之一

rust - 三明治管使用rust 了怎么办?

rust - 如何在 actix 处理程序中设置查询参数的默认选项?

rust - Trait 实现了 Iterator,但不能将实现我的 trait 的结构用作 Iterator

methods - 有没有一种方法可以使用后缀表示法来调用 Rust 中的函数而不需要定义新的特征?

macros - 在 Racket 中使用语法参数

c++ - Qt 列出类的属性

macros - 将 Rust 宏类型转换为表达式