#macro-derive #low-level #high-level #macro #deserialize #validation

macroex

基于提取器的低级宏解析包,通过 derive 宏提供高级解析支持。

8个版本

0.2.0 2023年11月13日
0.1.6 2023年11月10日
0.1.5 2023年10月24日

#682 in Rust模式


macroex-extras 中使用

MIT/Apache

92KB
1.5K SLoC

macroex

Crates.io Docs

基于提取器的低级宏解析包,通过 derive 宏提供高级解析支持。

FromMacroExtractor

FromMacro 是这个包的精髓。它提供了 from_onefrom_many 函数,分别解析 TokenTreeTokenStream

我们通常假设 from_many 将包含两个或更多的 TokenTrees。如果不是,则认为提取器存在错误。

FromMacro 的所有实现者都是单个 TokenTreeExtractor。当直接在 TokenStream 迭代器上使用时,它们将消耗一个 TokenTree 并尝试使用 from_one 进行解析。

let mut iter = quote!{45, Hello; true false}.into_iter();
let a: i32 = iter.extract()?;
let b: Punct = iter.extract()?;
// Extracts a string value from an ident
let IdentString(c) = iter.extract()?;
// Validates a semicolon
let d: PunctOf<';'> = iter.extract()?;
let e: bool = iter.extract()?;
// Validates a false literal
let f: LitFalse = iter.extract()?;

这非常棒!因为大多数事情都可以表示为一个单独的 TokenTree

// This is a single TokenTree::Group
{
    name: "Tom".
    age: 45,
    children: [
        "Tim", "Tam"
    ],
}

然而,还有其他一些 TokenTree 无法处理的事情。

// This fails because -45 is two tokens
let a: i32 = quote!{-45}.into_iter().extract().unwrap();

将其他 Extractor 包裹在 FromMacro 实现者中,允许它们解析额外的 TokenTrees,并且如果匹配到多个 TokenTree,还可以利用 from_many 方法。

// Note -45 is two tokens
let mut iter = quote!{-45}.into_iter();
// All extracts everything from a stream
let All(a) = iter.extract()?;
assert_eq!(a, -45i32);

let mut iter = quote!{-45, 21, 9.5,}.into_iter();
// CommaExtractor extracts until a comma or end of stream is found.
let CommaExtractor(a) = iter.extract()?;
let CommaExtractor(b) = iter.extract()?;
let CommaExtractor(c) = iter.extract()?;
// EndOfStream is a unit struct extractor and this asserts iter is empty
let EndOfStream = iter.extract()?;
assert_eq!(a, -45);
assert_eq!(b, 21);
assert_eq!(c, 9.5);

Derive

我们提供了类似 serde::Deserialize 的 derive 宏 FromMacroFromAttrs,这使得根据特定数据格式进行结构体和枚举的解析变得更为便捷。

FromMacro可以解析类似于原生Rust的语法,而FromAttrs则解析宏属性中常用的语法。

为什么不使用serde_tokenstream呢?

因为我们没有使用serde数据模型。在宏的上下文中,我们的数据模型要强大得多。我们可以提取所有FromMacro实现者,包括TokenStreamIdentGroup等。

FromMacro

FromMacro可以解析类似于原生Rust的语法。

类型 from_one from_many
单元结构体 结构体名称 --
元组结构体 (元组, ..) 结构体名称(元组, ..)
命名结构体 {字段:, ..} 结构体名称{字段:, ..}
单元枚举变体 变体名称 --
元组枚举变体 -- 变体名称(元组, ..)
命名枚举变体 -- 变体名称{字段:, ..}

示例

类型 Rust类型 from_one from_many
单元结构体 struct Red; Red --
元组结构体 struct Vec2 (i32, i32) (4, 5) Vec2(4, 5)
命名结构体 struct Vec2 {x: i32, y: i32} {x: 4,y: 5} Vec2{x: 4,y: 5}
单元变体 enum Color {Black,White} Black --
元组变体 enum Animals {Dog(String),Sheep(usize)} -- Dog("Rex")
命名变体 enum Shapes {Square{x: f32},Rect{x: f32,y: f32}} -- Rect{x: 4,y: 5}

用例

由于我们很可能在宏中解析配置,如果找不到字段,我们将提供一个Default::default()值。

如果你的类型没有实现Default,则需要使用#[macroex(required)]来选择退出。

#[derive(FromMacro)]
pub struct Person {
    pub name: String,
    pub age: i32,
    pub height: f32,
    // This works as long as Gender implements `FromMacro` and `Default`
    pub gender: Gender,
    // Using Option is idiomatic to handle the default case.
    pub hair_color: Option<NumberList<[f32;4]>>,
    // We can extract macro based things
    pub do_something: Option<TokenStream>,
}

示例宏输入

person! {
    name: "Lina",
    age: 23,
    gender: Female,
    hair_color: [0.7, 0.4, 0],
    do_something: {
        let w = "world";
        println!("Hello, {}!", w)
    },
}

属性

FromMacro宏支持以下属性

#[derive(FromMacro)]
// We use the same casing names as serde.
#[macroex(rename_all="SCREAMING-KEBAB-CASE")]
pub struct Animal {
    // Errors if not specified.
    #[macroex(required)]
    pub name: String,
    // Evaluate an expression instead of `Default::default()`
    #[macroex(default="0.0")]
    pub height: f32,
    #[macroex(default=r#""dog".to_owned()"#)]
    pub species: String,
    // Take strings as inputs, and collects them into a vec.
    #[macroex(repeat)]
    // and rename "nicknames" to "nickname" during parsing.
    #[macroex(rename="nickname")]
    pub nicknames: Vec<String>,
}

FromAttrs

FromAttrs为与宏属性相关的语法生成简单的FromMacro实现。

此宏仅允许在命名结构体上使用,并支持3种基本语法

  • .., name, ..解析为name: true,这匹配布尔值。
  • .., name = expr, ..解析为name: expr
  • .., name(..), .. 转换为 name: T{ .. }

其他类型,如无字段的枚举,可以使用 FromMacro 来生成兼容的 FromMacro 实现,以供此宏使用。

示例

我们使用与 FromMacro 相同的属性集。

#[derive(FromAttrs)]
#[macroex(rename_all="snake_case")]
pub struct Animal {
    #[macroex(required)]
    pub name: String,
    #[macroex(default="0.0")]
    pub height: f32,
    #[macroex(default=r#"Ident::new("Dog", Span::call_site())"#)]
    pub species: Ident,
    #[macroex(repeat, rename="mascot")]
    pub mascot_infos: Vec<MascotInfo>,
}

示例属性

#[animal(name = "Ferris", species = Crab, mascot(language = "Rust"))]

我们可以解析以下内容:

(name = "Ferris", species = Crab, mascot(language = "Rust"))

使用 from_one,或者

name = "Ferris", species = Crab, mascot(language = "Rust")

使用 from_many,通常与 syn 一起提取。

宏链和卫生

我们将输入视为字符串类型,并将尝试在解析过程中平铺所有遇到的 None 定界符组。

依赖项

~0.5–1MB
~22K SLoC