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


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

    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 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

  • 相关阅读:
    ARM开发初级-STM32中断系统-学习笔记07
    ARP欺骗
    抖音矩阵系统源码,抖音矩阵系统独立部署定制开发。
    Session会话追踪的实现机制
    Windows原理深入学习系列-访问控制列表
    HTML5 —— 拖放、地理位置、视频和音频的基本使用
    程序员如何学习开源项目,这篇文章告诉你
    艾奇软件怎么下载安装?
    力扣练习——44 路径总和 III
    863. 二叉树中所有距离为 K 的结点
  • 原文地址:https://www.cnblogs.com/hangj/p/17486297.html