当前位置: 首页  >  >聚焦 > > 正文

用声明式宏解析 Rust 语法之 enum parser_世界头条

2023-06-17 13:34:20    来源:博客园

上一篇用声明式宏解析 Rust 语法我们的 "macro parser" 解析了 functionstruct, 这篇来尝试 parse 一下更复杂的 enum为什么说 enum更复杂?因为它不像 struct结构内都是 identifier: type那样规律。enum内部的 EnumItem可能是一个简单的 identifier, 也可能是 tuplestruct, 还可能是 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,}

注意 E1E2默认不能混用,你需要加上 #[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,}

这篇文章的主要目的是: 以尽量简单的代码记录思考过程。所以先忽略掉 EnumIteminttype的情况,同时也忽略掉 EnumItemvisibility(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 = `, `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 isThe Little Book of Rust Macros.

大意是: 要做到这种事情,需要用到一种被称为 tt-muncher的 macro: 它先把 enum body 的部分组装好,在最后一步再把它塞到 enum 里去

我大概明白了他的意思,我可以先萃取出 enumname(ident) 和 body(tt), 然后把 name当作 tt(token tree) 存起来(暂且存到一个 [...]里面),递归处理 body部分, 把 fieldtt种提取出来, 再放到 [...]中, 最终整个 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

关键词:

«上一篇:上交所:本周对退市未来、退市辅仁等退市整理股票进行重点监控_微动态 »下一篇: 最后一页