上一篇用宣告式宏解析 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 :
{ StructFields? }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