用声明式宏解析 Rust 语法之 enum parser
上一篇用声明式宏解析 Rust 语法
我们的 "macro parser
" 解析了 function
和 struct
, 这篇来尝试 parse 一下更复杂的 enum
为什么说 enum
更复杂?因为它不像 struct
结构内都是 identifier: type
那样规律。
enum
内部的 EnumItem
可能是一个简单的 identifier
, 也可能是 tuple
或 struct
, 还可能是 inttype
Syntax
Enumeration :
enum IDENTIFIER GenericParams? WhereClause? { EnumItems? }EnumItems :
EnumItem ( , EnumItem )* ,?EnumItem :
OuterAttribute* Visibility?
IDENTIFIER ( EnumItemTuple | EnumItemStruct )? EnumItemDiscriminant?EnumItemTuple :
( TupleFields? )EnumItemStruct :
EnumItemDiscriminant :
= Expression
还是直接看具体代码更直观:
enum E1 {
A,
B(u8,),
C{x: u8, },
}
enum E2 {
A = 0,
B = 1,
C = -1,
}
注意 E1
和 E2
默认不能混用,你需要加上 #[repr(inttype)]
, inttype
可以是:
i8
, u8
, i16
, u16
, i32
, u32
, i64
, u64
, i128
, u128
, isize
, usize
#[repr(isize)]
enum E {
A(String, ), // 默认 A(String)=0
B(u8, String) = 1,
C = 3,
}
这篇文章的主要目的是: 以尽量简单的代码记录思考过程。所以先忽略掉 EnumItem
为 inttype
的情况,
同时也忽略掉 EnumItem
的 visibility
(pub) 和 meta
(#[...])属性, 以免代码太杂,难以肉眼 parse
第一次尝试
首先匹配整个 enum
, 先不管内部细节
macro_rules! enum_parser {
(
enum $name: ident {
$($tt: tt)* // 把整个 enum body 当作一串 token tree
}
) => {
enum $name {
$($tt)*
}
};
}
在上面这一步,我们就可以针对 enum
这个整体插入自己的代码了,但是对于内部 EnumItem
还没摸到。
目前要解析的 EnumItem
有三种情况: enum E { A, B(u8), C{x: u8}, }
, 那么我需要定义一个辅助宏,专门来解析 $($tt)*
, 从中萃取出一个个的 EnumItem
就行了
macro_rules! enum_parser_helper {
// enum E{}
() => {};
// A,
(
$field: ident
$(, $($tail: tt)*)?
) => {};
// B(u8,),
(
$field: ident ($($ty: ty),* $(,)?)
$(, $($tail: tt)*)?
) => {};
// C{x:u8, },
(
$field: ident {$($inner_field: ident : $ty: ty),* $(,)?}
) => {};
}
macro_rules! enum_parser {
(
enum $name: ident {
$($tt: tt)*
}
) => {
enum $name {
enum_parser_helper!($($tt)*)
}
};
}
三种情况,加空 enum
的情况都匹配到了,虽然 =>
右边的 {}
里面还没填东西,但是大体的形状是对的。好像也不比 struct
复杂多少嘛,
测试一下
enum_parser! {
enum E {}
}
duang error!!!
error: expected one of `(`, `,`, `=`, `{`, or `}`, found `!`
--> src/main.rs:459:35
|
459 | enum_parser_helper!($($tt)*)
| ^ expected one of `(`, `,`, `=`, `{`, or `}`
...
464 | / enum_parser! {
465 | | enum E {}
466 | | }
| |_____- in this macro invocation
|
= help: enum variants can be `Variant`, `Variant = <integer>`, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }`
= note: this error originates in the macro `enum_parser` (in Nightly builds, run with -Z macro-backtrace for more info)
这啥情况,咋回事,咋不行呢?你这编译器不讲武德,直接给我像 C 语言那样把我的 enum_parser_helper!($($tt)*)
展开不就完事了,干嘛一言不合就报错?
经过一顿抓耳挠腮之后,终于冷静下来。
expected one of `(`, `,`, `=`, `{`, or `}`
? 这是把我的 enum_parser_helper
当成 enum
里的 EnumItem
了呀!
代码应该是被 enum_parser!
展开成这个样子了:
enum E {
enum_parser_helper!($($tt)*)
}
也就是说只有 enum_parser!
这一层做了代码展开,但是 enum_parser_helper!
没干活呀。
一个 macro 里面是可以调用另一 macro 的啊,难道是不能这么玩吗?
于是我在搜索引擎搜 rust macro does not expand in enum
找到了这个: Call macro inside macro repetition
playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2cacd8ce561af93ceabd40b123b6549a
macro_rules! inner {
(ok) => {}
}
macro_rules! outer {
( $($variants:ident),* ) => {
enum Test {
$($variants inner!(ok)),*
}
}
}
outer! {
A,
B
}
fn main() {
}
这跟我遇到的问题简直就是完全一样啊
Solved:
To do this kind of thing, you need what's known as a tt-muncher macro: It builds all of the text for the enum body first,
and then wraps it in the enum as the final step. A good reference for this kind of advanced macro programming is
The Little Book of Rust Macros.
大意是: 要做到这种事情,需要用到一种被称为 tt-muncher
的 macro: 它先把 enum body 的部分组装好,在最后一步再把它塞到 enum 里去
我大概明白了他的意思,我可以先萃取出 enum
的 name
(ident) 和 body
(tt), 然后把 name
当作 tt
(token tree) 存起来(暂且存到一个 [...]
里面),
递归处理 body
部分, 把 field
从 tt
种提取出来, 再放到 [...]
中, 最终整个 enum
又重新变回了 tt
, 然后统一展开 enum $name { $($tt)* }
,
不可谓不 nice!
继续尝试
enum_parser_helper {
// 全部 field 处理完之后, enum 的全部内容就都在 [] 里面了
([
$(#[$meta: meta])*
enum $name: ident
$($tt: tt)*
]) => {
// 最终的组装展开
$(#[$meta])*
enum $name {
$($tt)*
}
};
// 萃取出 A,
(
[$($head: tt)*]
$field: ident
$(, $($tt: tt)*)?
) => {
enum_parser_helper!([ $($head)* $field, ] $($($tt)*)? )
};
// 萃取出 B(u8,),
(
[$($head: tt)*]
$field: ident ($($ty: ty),* $(,)?)
$(, $($tt: tt)*)?
) => {
enum_parser_helper!(
[
$($head)*
$field($($ty),*),
]
$($($tt)*)?
)
};
// 萃取出 B{x: u8,},
(
[$($head: tt)*]
$field: ident {$($inner_field: ident : $ty: ty),* $(,)?}
$(, $($tt: tt)*)?
) => {
enum_parser_helper!(
[
$($head)*
$field{$($inner_field: $ty),*},
]
$($($tt)*)?
)
};
}
macro_rules! enum_parser {
() => {};
(
$(#[$meta: meta])*
enum $name: ident {
$($tt: tt)*
}
) => {
// [] 内存放所有的 tt
enum_parser_helper!( [$(#[$meta])* enum $name] $($tt)* )
};
}
搞定!
测试一下:
enum_parser! {}
enum_parser! {
#[derive(Debug)]
enum E {
A,
B(u8,),
C{x:u8,},
}
}
完事。
下一篇准备写一下过程宏 proc_macro