用声明式宏解析 Rust 语法之 enum parser
2023/6/17 18:22:31
本文主要是介绍用声明式宏解析 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 :
{ 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
这篇关于用声明式宏解析 Rust 语法之 enum parser的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23JAVA语音识别项目入门教程
- 2024-11-23Java云原生学习:从入门到实践
- 2024-11-22Java创业学习:初学者的全面指南
- 2024-11-22JAVA创业学习:零基础入门到实战应用教程
- 2024-11-22Java创业学习:从零开始的Java编程入门教程
- 2024-11-22Java对接阿里云智能语音服务学习教程
- 2024-11-22JAVA对接阿里云智能语音服务学习教程
- 2024-11-22Java对接阿里云智能语音服务学习教程
- 2024-11-22Java副业学习:零基础入门到实战项目
- 2024-11-22Java副业学习:零基础入门指南