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 进程宏
每月199次下载
用于 3 个crate (直接使用2个)
45KB
956 代码行
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