克服"局部歧义:多种解析选项:"在Rust Macros

时间:2017-09-10 00:08:12

标签: 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>
    )
);

我得到了error: local ambiguity: multiple parsing options: built-in NTs tt ('children') or 1 other option.

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

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

1 个答案:

答案 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!将所有部分放在一起,产生一个静态字符串文字。

这有四个问题:

  1. 这会像疯了一样通过递归级别来咀嚼。如果用完,用户将需要全局提高递归限制。

  2. 像这样的宏

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

  4. 您仍然无法使用字符串文字。您无法匹配<或其他表达式后面的表达式,因此匹配字符串必须是(唯一)后备规则。

  5. 所以你可以删除分隔符,但我不推荐它。只需引用HTML就像一个理智的人。

    顺便说一下,这里有一个alternative version of the macro结构略有不同,它会导致cmp宏的因素,并且更容易扩展元素而不关闭标签。请注意,我没有写这个版本。