在 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);
};
#[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);
};