#macro #utility #macro-rules #maintainable #readable #syntax #expressions

macro cps

辅助创建可读性和可维护性强的 macro_rules! 宏

5个版本

0.2.3 2023年6月14日
0.2.2 2023年3月5日
0.2.1 2022年11月5日
0.2.0 2022年10月22日
0.1.0 2022年7月17日

#1731 in 进程宏

Download history 72/week @ 2024-03-11 38/week @ 2024-03-18 39/week @ 2024-03-25 24/week @ 2024-04-01 14/week @ 2024-04-08 14/week @ 2024-04-15 74/week @ 2024-04-22 57/week @ 2024-04-29 42/week @ 2024-05-06 28/week @ 2024-05-13 48/week @ 2024-05-20 28/week @ 2024-05-27 47/week @ 2024-06-03 48/week @ 2024-06-10 62/week @ 2024-06-17 41/week @ 2024-06-24

每月199次下载
用于 3crate (直接使用2个)

MIT 许可证

45KB
956 代码行

Crates.io

CPS(又称宏变量 & let 表达式)

此crate允许使用Rust宏语法编写更传统的“内外部”函数,允许在不切换到进程宏的情况下编写可维护的宏。

TLDR

use cps::cps;

#[cps] // Add this macro to unlock additional syntax
macro_rules! foo {
    (1) => { BaseCase1 };
    (2) => { BaseCase2 };

    () =>
    // !!! NEW SYNTAX HERE !!!
    let $x:tt = foo!(1) in
    let $y:tt = foo!(2) in
    {
        concat!($x, " ", $y)
    };
}


fn main() {
    assert_eq!(foo!(), "BaseCase1 BaseCase2");
}

为什么?

原因1 - 可读性和可维护性

宏执行顺序令人困惑。因为每个宏都传递一个标记树,所以宏是按从外到内的顺序执行的。例如

macro_rules! dog {
    () => {
        woof
    };
}

macro_rules! dog_says {
    () => 
    {
        stringify!(dog!())
    };
}

fn main() {
    println!("{}", dog_says!()); // Prints "dog!()", not "woof"
}

将上面的代码按经典函数的方式阅读,你可能期望这个程序打印 woof。然而,不幸的是,它打印了 dog!(),就像 println! 展开了它的宏,而 stringify! 没有这样做。这使得宏难以维护。

《宏的小书》描述了 回调,其中宏将下一个要执行的宏作为参数。这导致上面示例的以下改进版本

macro_rules! dog {
    ($cont:ident) => {
        $cont!(woof)
    };
}

macro_rules! dog_says {
    () => 
    {
        dog!(stringify)
    };
}

fn main() {
    println!("{}", dog_says!()); // Prints "woof" but is hard to read
}

虽然现在有了正确的行为,但由于执行流程仍然令人困惑,所以难以维护。使用CPS代替我们得到

#[cps]
macro_rules! dog {
    () => {
        woof
    };
}

#[cps]
macro_rules! dog_says {
    () => 
    let $x::tt = dog!() in
    {
        stringify!($x)
    };
}

fn main() {
    println!("{}", dog_says!()); // Prints "woof"
}

原因2 - 可扩展性

CPS宏中的 let 表达式必须由其他CPS宏构建,而主体则不必。这允许我们向其他人的宏中添加要替换的计算。例如

// Non-CPS macro from another crate
macro_rules! load_thing {
    ($path:expr) => {
        ...
    };
}

#[cps]
macro_rules! my_load_thing {
    ($path:expr) => 
    let $new_path::expr = cps::concat!("src/", $path) in
    {
        load_thing!($new_path)
    };
}

此crate附带了一组CPS宏,这些宏是标准库中宏的副本,可以用于以可维护的方式对标记树进行编译时计算。

依赖项

~1.5MB
~39K SLoC