首页 > 其他分享 >用声明式宏解析 Rust 语法之 enum parser

用声明式宏解析 Rust 语法之 enum parser

时间:2023-06-17 13:33:42浏览次数:51  
标签:ident enum macro tt parser field Rust

上一篇用声明式宏解析 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

标签:ident,enum,macro,tt,parser,field,Rust
From: https://www.cnblogs.com/hangj/p/17486297.html

相关文章

  • 用声明式宏解析 Rust 语法
    在上一篇Rust声明式宏中的Metavariables有哪些的基础上,今天尝试解析一下Rust中的几种item。我们知道一个crate是由item组成的,每一个fnstructenumimplmod等定义都是一个item,这篇文章就简单解析一下Function和structFunction先看一个最简单的函数fnfoo(......
  • python configparser读取配置文件
    #coding:utf-8importconfigparserimportosimportjsonbase_path=os.path.dirname(os.path.abspath(__file__))conf_file=os.path.join(base_path,"config.ini")defread_config():#读取配置文件信息try:cf=configparser.ConfigParser()......
  • How to work around rustup-init failure
    Howtoworkaroundrustup-initfailure(JinQing’sColumn,Mar.,2022)rustup-init.exemayfailifsomeanti-virussoftwareisrunningwithrealtimeprotection.Theerrormessageislikethisaftermanyretries:error:couldnotrenamecomponentfilefrom�......
  • Rust学习笔记——基于官网和Rust语言圣经
    安装rust安装1、官网https://www.rust-lang.org/zh-CN/learn/get-started2、运行后选择1会下载VisualStdio;选择2表示你是高级用户或企业用户;选择3即是采用MinGW编译选择1选1表示默认当前配置安装,选2表示自定义这些配置,选3表示取消安装更新与卸载Rust更新Rustrustupup......
  • Windows上安装Rust
    1.下载RUSTUP-INIT.EXE下载地址:https://www.rust-lang.org/tools/install2.配置环境变量,设置更新源#字节跳动的更新源RUSTUP_DIST_SERVER=https://rsproxy.cnRUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup3.执行RUSTUP-INIT.EXE开始安装,初学者建议按14.等着被恭......
  • Rust 声明式宏中的 Metavariables 有哪些
    Metavariables官方文档确实写得很好,但是缺少一些风味,容易催眠......
  • 枚举的方法、枚举案例 - 状态机、组织枚举、策略枚举、枚举工具类 - EnumSet 和 EnumM
    引入编写季节类(Season),该类只有四个对象(spring,summer,autumn,winter)概念枚举(enum)全称为enumeration,是JDK1.5中引入的新特性。语法publicenumColor{//默认添加publicstaticfinalRED,GREEN,BLUE;}本质尽管枚举看起来像是一种新的数据类型,实际上,枚举就是一种受限制......
  • rust Dockerfile
    Dockerfile:ARGBUILD_DIR=/rust/buildFROMrustasbuildARGBUILD_DIRWORKDIR${BUILD_DIR}COPYsrc./srcCOPYRocket.toml.COPYCargo.lock.COPYCargo.toml.RUNcargobuild-rFROMdebianasdeployARGBUILD_DIRWORKDIR/etc/rustRUNmkdirconfig......
  • VirusTotal——您身边的企业安全专家
    【本文由CloudAce整理发布。CloudAce是谷歌云全球战略合作伙伴,拥有300多名工程师,也是谷歌最高级别合作伙伴,多次获得GoogleCloud合作伙伴奖。作为谷歌托管服务商,我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证培训服务。】​1、企业的信息安全问题不容忽视Cl......
  • Rust 什么是所有权
    创建于2023-5-215:34本笔记主要来自于Rust程序设计语言中文版[4.1],旨在记录个人学习过程中的重点和心得体会。在记录过程中,笔者会对文档中的某些内容进行摘抄或修改,并添加自己的注释或说明。如有不当之处,请指正。“所欧运行的程序都必须管理其使用计算机内存的方式。一......