在 rust 中扩展递归宏,类似于 serde_json 但对于 HTML 元素

Expanding a recursive macro in rust, similar to serde_json but for HTML elements

#[macro_export]
macro_rules! reactant {
    // Converts (function) {...} into element
    ( $f:ident $t:tt ) => {
        {
            let mut elem = HtmlElement::new($f);
            reactant!(@expand elem $t);
            elem
        }
    };

    // Expands {...} and parses each internal node individually
    ( @expand $self:ident { $($t:tt),* } ) => {
        $(
            reactant!(@generate $self $t);
        )*
    };

    // Takes a (function) {...} node, feeds it back recursively, and pushes it into parent
    ( @generate $self:ident $t1:tt $t2:tt) => {
        {
            $self.push_inner(reactant!($t1 $t2));
        }
    };

    // Takes a literal and sets the parent internal to a string
    ( @generate $self:ident $l:literal) => {
        {
            $self.set_inner(String::from($l)).unwrap();
        }
    };

}

#[allow(unused_macros)]
#[cfg(test)]
mod tests {
    use crate::html::types::*;
    use crate::html::HtmlElement;
    use crate::reactant;

    #[test]
    fn test() {
        // Doesn't work, not expecting '{' after second div, although the first one works fine
        let x = reactant!(div {
            div {
                "test"
            }
        });

        // Works, outputs <div>thing</div>
        let y = reactant!(div {
            "hello",
            "thing"
        });

    }
}

我正致力于在 Rust 中制作一个没有创意的命名 HTML 库,同时也在学习宏(宏文档令人困惑)。该项目的一部分是制作一个宏,该宏递归地生成 HTML 元素以制作文档,其外观与 serde_json 相似。语法显示在测试用例中。基本上,每个 HTML 元素(div、h1 等)都映射到一个函数,该函数输出我为 HTML 元素制作的结构。我设法让宏以一种方式工作,但它只允许 HTML children 当我希望它也使用文字来填充时,比如说,带有字符串的 h1(测试用例 1) .测试用例 2 显示了它不起作用的地方,我不明白为什么它不能读取 {.

让我们试着跟踪宏展开时发生了什么:

reactant!(div { div { "test" } });

这会触发第一条规则:$f:ident $t:tt 和:

  • $f 设置为 div
  • $t 设置为 { div { "test" } }

扩展为:

reactant!(@expand elem { div { "test" } });

我相信您打算在第二步触发规则 2:@expand $self:ident { $($t:tt),* } 具有:

  • $self 设置为 elem
  • $t 设置为 div { "test" }

将扩展为:

reactant!(@generate elem div { "test" });

但是 div { "test" }实际上是两个 tt所以不能被这个规则解析(或任何其他规则)。


如果我对你的意图的解释是正确的,你将需要有单独的规则来处理每种情况并迭代处理列表:

    // Expands {...} and parses each internal node individually
    ( @expand $self:ident { $t1:tt $t2:tt, $($tail:tt)* } ) => {
            reactant!(@generate $self $t1 $t2);
            reactant!(@expand $self { $($tail)* })
    };
    ( @expand $self:ident { $t:tt, $($tail:tt)* } ) => {
            reactant!(@generate $self $t);
            reactant!(@expand $self { $($tail)* });
    };
    // Duplicate rules to handle the last item if it's not followed by a ","
    ( @expand $self:ident { $t1:tt $t2:tt } ) => {
            reactant!(@generate $self $t1 $t2);
    };
    ( @expand $self:ident { $t:tt } ) => {
            reactant!(@generate $self $t);
    };

Playground