用声明式宏解析 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的更多相关文章
- DEVOPS技术实践_08:声明式管道语法
简介 前面简单的做了管道的实验,看了一下的它的效果 声明式管道是Groovy语法中的一个更简单和结构化的语法.下面主要学习明式管道语法. 一 声明式管道的基本结构 以上节的代码为例 node { de ...
- Jenkins教程——从安装到部署Docker服务(二)声明式流水线HelloWorld
前言 本文通过一个声明式流水线的HelloWorld程序做一下流水线基础入门,对常用的流水线参数进行简要说明 什么是流水线 现实中的流水线 流水线比较好理解,类比于现实生活中的生产流水线,每个流程只做 ...
- 【Kubernetes】深入解析声明式API
在Kubernetes中,一个API对象在Etcd里的完整资源路径,是由:Group(API组).Version(API版本)和Resource(API资源类型)三个部分组成的. 通过这样的结构,整个 ...
- Jenkins pipeline声明式语法
目录 一.最简结构 二.简单例子 一.最简结构 pipeline { agent any stages { stage('pull') { st ...
- spring声明式事务管理详情解析
前沿:通过对spring事务管理有了比较深入学习,本文将不做实例,而是指定具体的类和配置文件进行讲解. 本文内容: 1.了解什么是声明式事务? 2.声明式事务管理分别有哪几种? 3.这几种事务管理之间 ...
- spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)
面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理. 事务的介绍 1.数据库事物特性 原子性多个数据库操作是不可分割的,只有所有的操作 ...
- spring cloud深入学习(四)-----eureka源码解析、ribbon解析、声明式调用feign
基本概念 1.Registe 一一服务注册当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址.端口.运行状况指标的Uri.主页地址 ...
- How Javascript works (Javascript工作原理) (十四) 解析,语法抽象树及最小化解析时间的 5 条小技巧
个人总结:读完这篇文章需要15分钟,文章介绍了抽象语法树与js引擎解析这些语法树的过程,提到了懒解析——即转换为AST的过程中不直接进入函数体解析,当这个函数体需要执行的时候才进行相应转换.(因为有的 ...
- JavaScript在页面中的执行顺序(理解声明式函数与赋值式函数) 转载
JavaScript在页面中的执行顺序 https://blog.csdn.net/superhoy/article/details/52946277 2016年10月27日 15:38:52 阅读数 ...
- 关于CSS自文档的思考_css声明式语言式代码注释
obert C. Martin写的<Clean Code>是我读过的最好的编程书籍之一,若没有读过,推荐你将它加入书单. 注释就意味着代码无法自说明 —— Robert C. Martin ...
随机推荐
- ASP.NET Core - 选项系统之源码介绍
.NET Core 选项系统的主要实现在 Microsoft.Extensions.Options 和 Microsoft.Extensions.Options.ConfigurationExtens ...
- kubernetes(k8s) 安装 Prometheus + Grafana
kubernetes(k8s) 安装 Prometheus + Grafana 组件说明 MetricServer:是kubernetes集群资源使用情况的聚合器,收集数据给kubernetes集群内 ...
- [Linux]CPU架构/指令集:RISC / CISC | arm | amd | X86/i386 | aarch64
1 前言 本文是解决在软件开发.软件交付过程中,常常需要找寻与服务器硬件的CPU架构适配的软件包时,开发者和交付者又时常摸不着头脑.[迷迷糊糊]地就下载了某个所谓"适配".&quo ...
- 新旧版本功能对比 | v1.5.0 全新升级
Hi~社区的小伙伴们大家好呀! CloudQuery 最新 1.5.0 社区版本即将于 4月14日 发布,正式上线前,我们迫不及待与大家分享与 v1.4 相比,v1.5.0 在性能和功能上有哪些更新和 ...
- 多线程socketserver
模块:socketserver tcp协议: 服务端: import socketserver class MyRequestHandle(socketserver.BaseRequestHandle ...
- 【Spring5】框架新功能
Spring5框架新功能 整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除. Spring5自带了通用的日志封装:log4j2 已经移除了log4 ...
- LeetCode 周赛 341 场,模拟 / 树上差分 / Tarjan 离线 LCA / DFS
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末有单双周赛,双周赛我们讲过了,单周赛那天早上有事没参加,后面做了虚拟竞赛,然后整个 ...
- 如何在 .NET Core WebApi 中处理 MultipartFormDataContent 中的文件
在上一篇文章(如何在 .NET Core WebApi 中处理 MultipartFormDataContent)中,我们有描述过如何以最简单的方式在 .NET Core WebApi 中处理 Mul ...
- 小知识:使用errorstack定位特定问题
有客户遇到ORA-2289的报错,同事协助去现场排查,我帮着远程共同check下. 客户只是应用端报出的错误,为了进一步定位,服务端需要开errorstack协助定位具体问题. 下面就以这个ORA-2 ...
- 前端 引用svg图片,支持动态切换颜色
当我们添加一张svg图片显示时,react提示找不到文件. 我们可以在全局文件global.d.ts内,添加图片类型的声明: 详见<TypeScript 引用资源文件后提示找不到的错误处理方案& ...