【问题标题】:Overcoming "local ambiguity: multiple parsing options:" in Rust Macros克服 Rust 宏中的“局部歧义:多个解析选项:”
【发布时间】:2017-09-10 00:08:12
【问题描述】:

我正在尝试使用 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 结构的树。

【问题讨论】:

    标签: macros rust


    【解决方案1】:

    您希望宏真正可用吗?那就不要。实际上,为什么还要在这里使用宏呢?无论你做什么,你都会在某个时候与 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!s 将所有部分放在一起,生成一个静态字符串文字。

    这有四个问题:

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

    2. 这样的宏很慢

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

    4. 您仍然无法避免需要使用字符串文字。您无法匹配后面跟着&lt; 或其他表达式的表达式,因此匹配字符串必须是(唯一的)后备规则。

    所以您可以删除分隔符,但我不建议这样做。像一个理智的人一样引用 HTML。


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

    【讨论】:

    • 感谢您的回答。我意识到使用这个宏来简单地构建一个字符串有点荒谬,但这仅仅是为了这个问题。我可以想象做更多有趣的事情。我已经编辑了我的问题来提及这一点。对我来说最大的痛点是方括号,我可以忽略引用字符串文字的需要。你知道 rusts new macro 系统是否支持这个?您会建议为此使用程序宏吗?
    • @user7400966:Rust 的新宏系统还不存在,时间太短,我无法回答。至于使用过程宏,您可能可以通过一些扭曲来绕过它们,仅支持派生。
    猜你喜欢
    • 2020-04-30
    • 1970-01-01
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-13
    • 2013-10-04
    相关资源
    最近更新 更多