上一篇用聲明式宏解析 Rust 語法我們的 "macro parser" 解析了 function和 struct, 這篇來嘗試 parse 一下更復(fù)雜的 enum為什么說 enum更復(fù)雜?因?yàn)樗幌?struct結(jié)構(gòu)內(nèi)都是 identifier: type那樣規(guī)律。enum內(nèi)部的 EnumItem可能是一個(gè)簡(jiǎn)單的 identifier, 也可能是 tuple或 struct, 還可能是 inttype
SyntaxEnumeration :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默認(rèn)不能混用,你需要加上 #[repr(inttype)], inttype可以是:i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize
#[repr(isize)]enum E { A(String, ), // 默認(rèn) A(String)=0 B(u8, String) = 1, C = 3,}這篇文章的主要目的是: 以盡量簡(jiǎn)單的代碼記錄思考過程。所以先忽略掉 EnumItem為 inttype的情況,同時(shí)也忽略掉 EnumItem的 visibility(pub) 和 meta(#[...])屬性, 以免代碼太雜,難以肉眼 parse
首先匹配整個(gè) enum, 先不管內(nèi)部細(xì)節(jié)
macro_rules! enum_parser { ( enum $name: ident { $($tt: tt)* // 把整個(gè) enum body 當(dāng)作一串 token tree } ) => { enum $name { $($tt)* } };}在上面這一步,我們就可以針對(duì) enum這個(gè)整體插入自己的代碼了,但是對(duì)于內(nèi)部 EnumItem還沒摸到。目前要解析的 EnumItem有三種情況: enum E { A, B(u8), C{x: u8}, }, 那么我需要定義一個(gè)輔助宏,專門來解析 $($tt)*, 從中萃取出一個(gè)個(gè)的 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的情況都匹配到了,雖然 =>右邊的 {}里面還沒填東西,但是大體的形狀是對(duì)的。好像也不比 struct復(fù)雜多少嘛,測(cè)試一下
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 = `, `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)*)展開不就完事了,干嘛一言不合就報(bào)錯(cuò)?
經(jīng)過一頓抓耳撓腮之后,終于冷靜下來。
expected one of `(`, `,`, `=`, `{`, or `}` ? 這是把我的 enum_parser_helper當(dāng)成 enum里的 EnumItem了呀!代碼應(yīng)該是被 enum_parser!展開成這個(gè)樣子了:
enum E { enum_parser_helper!($($tt)*)}也就是說只有 enum_parser!這一層做了代碼展開,但是 enum_parser_helper!沒干活呀。一個(gè) macro 里面是可以調(diào)用另一 macro 的啊,難道是不能這么玩嗎?
于是我在搜索引擎搜 rust macro does not expand in enum找到了這個(gè): 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() {}這跟我遇到的問題簡(jiǎn)直就是完全一樣啊
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 isThe Little Book of Rust Macros.
大意是: 要做到這種事情,需要用到一種被稱為 tt-muncher的 macro: 它先把 enum body 的部分組裝好,在最后一步再把它塞到 enum 里去
我大概明白了他的意思,我可以先萃取出 enum的 name(ident) 和 body(tt), 然后把 name當(dāng)作 tt(token tree) 存起來(暫且存到一個(gè) [...]里面),遞歸處理 body部分, 把 field從 tt種提取出來, 再放到 [...]中, 最終整個(gè) enum又重新變回了 tt, 然后統(tǒng)一展開 enum $name { $($tt)* },不可謂不 nice!
enum_parser_helper { // 全部 field 處理完之后, enum 的全部?jī)?nèi)容就都在 [] 里面了 ([ $(#[$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)* } ) => { // [] 內(nèi)存放所有的 tt enum_parser_helper!( [$(#[$meta])* enum $name] $($tt)* ) };}搞定!
測(cè)試一下:
enum_parser! {}enum_parser! { #[derive(Debug)] enum E { A, B(u8,), C{x:u8,}, }}完事。
下一篇準(zhǔn)備寫一下過程宏 proc_macro
標(biāo)簽:



