上一篇用声明式宏解析 Rust 语法

我们的 "macro parser" 解析了 functionstruct, 这篇来尝试 parse 一下更复杂的 enum

为什么说 enum 更复杂?因为它不像 struct 结构内都是 identifier: type 那样规律。

enum 内部的 EnumItem 可能是一个简单的 identifier, 也可能是 tuplestruct, 还可能是 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,
}

注意 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 = <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 里去

我大概明白了他的意思,我可以先萃取出 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

用声明式宏解析 Rust 语法之 enum parser的更多相关文章

  1. DEVOPS技术实践_08:声明式管道语法

    简介 前面简单的做了管道的实验,看了一下的它的效果 声明式管道是Groovy语法中的一个更简单和结构化的语法.下面主要学习明式管道语法. 一 声明式管道的基本结构 以上节的代码为例 node { de ...

  2. Jenkins教程——从安装到部署Docker服务(二)声明式流水线HelloWorld

    前言 本文通过一个声明式流水线的HelloWorld程序做一下流水线基础入门,对常用的流水线参数进行简要说明 什么是流水线 现实中的流水线 流水线比较好理解,类比于现实生活中的生产流水线,每个流程只做 ...

  3. 【Kubernetes】深入解析声明式API

    在Kubernetes中,一个API对象在Etcd里的完整资源路径,是由:Group(API组).Version(API版本)和Resource(API资源类型)三个部分组成的. 通过这样的结构,整个 ...

  4. Jenkins pipeline声明式语法

    目录 一.最简结构 二.简单例子 一.最简结构 pipeline {     agent any     stages {         stage('pull') {             st ...

  5. spring声明式事务管理详情解析

    前沿:通过对spring事务管理有了比较深入学习,本文将不做实例,而是指定具体的类和配置文件进行讲解. 本文内容: 1.了解什么是声明式事务? 2.声明式事务管理分别有哪几种? 3.这几种事务管理之间 ...

  6. spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)

    面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理. 事务的介绍 1.数据库事物特性 原子性多个数据库操作是不可分割的,只有所有的操作 ...

  7. spring cloud深入学习(四)-----eureka源码解析、ribbon解析、声明式调用feign

    基本概念 1.Registe 一一服务注册当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址.端口.运行状况指标的Uri.主页地址 ...

  8. How Javascript works (Javascript工作原理) (十四) 解析,语法抽象树及最小化解析时间的 5 条小技巧

    个人总结:读完这篇文章需要15分钟,文章介绍了抽象语法树与js引擎解析这些语法树的过程,提到了懒解析——即转换为AST的过程中不直接进入函数体解析,当这个函数体需要执行的时候才进行相应转换.(因为有的 ...

  9. JavaScript在页面中的执行顺序(理解声明式函数与赋值式函数) 转载

    JavaScript在页面中的执行顺序 https://blog.csdn.net/superhoy/article/details/52946277 2016年10月27日 15:38:52 阅读数 ...

  10. 关于CSS自文档的思考_css声明式语言式代码注释

    obert C. Martin写的<Clean Code>是我读过的最好的编程书籍之一,若没有读过,推荐你将它加入书单. 注释就意味着代码无法自说明 —— Robert C. Martin ...

随机推荐

  1. golang pprof监控系列(2) —— memory,block,mutex 使用

    golang pprof监控系列(2) -- memory,block,mutex 使用 大家好,我是蓝胖子. profile的中文被翻译轮廓,对于计算机程序而言,抛开业务逻辑不谈,它的轮廓是是啥呢? ...

  2. '林子雨大数据' 实验3 HBase操作与接口编程

    "林子雨大数据" 实验3 HBase操作与接口编程 环境搭建 VM虚拟机和Ubuntu系统的安装 在Windows中使用VirtualBox安装Ubuntu虚拟机(2020年7月版 ...

  3. 创建用户认证授权的 kubeconfig 文件

    创建用户认证授权的 kubeconfig 文件 当我们安装好集群后,如果想要把 kubectl 命令交给用户使用,就不得不对用户的身份进行认证和对其权限做出限制. 下面以创建一个 cby 用户并将其绑 ...

  4. [GIT]辨析/区别: git add -u | git add -A | git add . [转载]

    参考文献 git add -u与-A .三者的区别 - CSDN

  5. CSS样式中颜色与颜色值的应用

    使用CSS描绘页面样式时,颜色是其中不可或缺的,无论是对文本.背景还是边框.阴影,我们都写过无数代码用来增添颜色.而为了让网页的色彩表现更出色,我们很有必要完整梳理下CSS中的色彩. 要讲清楚CSS中 ...

  6. Spring中Bean的实例化详细流程

    还是举个例子,我有一个朋友小汪他远赴南方某城市打工.然后安定下来后他的朋友很想来家里玩,但是呢我这个朋友家里搞的很乱,所以他不好意思请朋友来家里玩.这时我的另一个朋友说那请一个保姆把家里好好整理一下就 ...

  7. linux 安装 node 和 npm 服务

    1.安装文件下载 下载地址:https://nodejs.org/zh-cn/download/ 2.安装步骤 1.将安装包上传到指定位置(我习惯放到:/usr/local/application/目 ...

  8. Yolov8离谱报错

    YoloV8离谱报错 ​ 今天下午给一个研究生小姐姐跑数据集,用的是yolov8在恒源云上租的4070的GPU服务器,跑垃圾分类数据集(https://blog.csdn.net/m0_5488250 ...

  9. 记一次 .NET某医疗器械清洗系统 卡死分析

    一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...

  10. 容易忽视的细节:Log4j 配置导致的零点接口严重超时

    作者:vivo 互联网服务器团队- Jiang Ye 本文详细的记录了一次0点接口严重超时的问题排查经历.本文以作者自身视角极具代入感的描绘了从问题定位到具体的问题排查过程,并通过根因分析并最终解决问 ...