克服Rust Macros中的"局部歧义:多个解析选项:"

dav*_*eil 1 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)*))
    }};
}
Run Code Online (Sandbox Code Playgroud)

然后使用宏:

println!("{}",
    html!(
        <html>[
            <head>[
                <title>["Some Title"]</title>
            ]</head>
            <body>[
                <h1>["This is a header!"]</h1>
            ]</body>
        ]</html>
    )
);
Run Code Online (Sandbox Code Playgroud)

但是,我真的想删除无关的开合方括号.我尝试这样做如下:

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)*))
    }};
}
Run Code Online (Sandbox Code Playgroud)

但是,当我去使用这个宏时:

println!("{}",
    html_test!(
        <html>
            <head>
                <title>"Some Title"</title>
            </head>
            <body>
                <h1>"This is a header!"</h1>
            </body>
        </html>
    )
);
Run Code Online (Sandbox Code Playgroud)

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

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

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

DK.*_*DK. 5

你想让宏实际可用吗?那就不要.实际上,为什么甚至在这里使用宏呢?无论你做什么,你都会在某些时候与Rust lexer作战.只需在字符串文字中写下HTML:

r##"<html>
    <head>
        <title>Some Title</title>
    </head>
    <body>
        <h1>This is a header!</h1>
    </body>
</html>"##
Run Code Online (Sandbox Code Playgroud)

那或者接受宏输入无法匹配实际的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)* }
    };
}
Run Code Online (Sandbox Code Playgroud)

这通过爬行输入标记,跟踪需要输出的字符串(in $($parts)*)以及需要关闭(in $($stack)*)的打开标记来工作.一旦它没有输入,并且堆栈为空,它将concat!所有部分放在一起,产生一个静态字符串文字.

这有四个问题:

  1. 这通过疯狂的递归水平来咀嚼.如果用完,用户将需要全局提升递归限制.

  2. 像这样的宏很.

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

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

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


顺便说一句,这里是替代版本,其结构略有不同,可以将cmp宏分解出来,并且更容易扩展元素而不关闭标记.请注意,我没有写这个版本.