6个版本 (3个重大更新)
新 0.4.0 | 2024年8月2日 |
---|---|
0.3.1 | 2023年10月8日 |
0.3.0 | 2023年4月2日 |
0.2.0 | 2022年12月22日 |
0.1.1 | 2022年12月22日 |
#156 在 过程宏 中排名
143 每月下载次数
用于 8 个crate(5个直接使用)
25KB
107 行
此crate提供了Rust的准引用宏,旨在在proc-macro中使用,以生成 TokenStream
,实现变量插值和模板展开。
此宏基于proc_macro
crate构建。
插值
完全支持原始quote!
宏语法。请参阅quote的文档。
为了向后兼容,插值规则与传统quote!
宏相同。插值使用#var
(类似于macro_rules!
中的变量$var
)进行。大多数Syn
crate中的变量都使用::proc_quote::ToTokens
trait进行插值。
规则
重复使用类似于#(...)*
或#(...),*
的语法完成。它重复此语法内的变量(#var
),实现了::proc_quote::Repeat
。
#(...)*
- 重复...无分隔符。...中至少应包含一个变量#(...),*
- 与之前相同,但使用分隔符','进行插值。
问题
插入规则较为粗糙,因此我实现了新的“模板”语法。例如,以下代码将不允许执行,因为#var1
不能进行双重迭代。
# use template_quote::quote;
let var1 = vec!['a', 'b'];
let var2 = vec![vec![1, 2], vec![3, 4]];
let tokens = quote!{
#(#(#var1 #var2)*)*
};
assert_eq!("'a' 1i32 'a' 2i32 'b' 3i32 'b' 4i32", tokens.to_string());
模板语法
模板语法是一种类似过程的语法,它允许你在宏内部使用结构化语句。
如果语法
此代码将围绕#i
(带有插入)进行迭代,并在数字满足条件时将i32
输出到TokenStream
。
# use template_quote::quote;
let i = vec![1, 2, 3];
let tokens = quote!{
#(
#(if i > &2) {
#i
}
)*
};
assert_eq!("3i32", tokens.to_string());
也允许使用if-else和if-else-if。
# use template_quote::quote;
let i = vec![1, 2, 3];
let tokens = quote!{
#(
#(if i > &2) {
+ #i
}
#(else) {
- #i
}
)*
};
assert_eq!("- 1i32 - 2i32 + 3i32", tokens.to_string());
# use template_quote::quote;
let i = vec![1, 2, 3, 4, 5];
let tokens = quote!{
#(
#(if i % &2 == 0) {
+ #i
}
#(else if i % &3 == 0) {
- #i
}
#(else) {
#i
}
)*
};
assert_eq!("1i32 + 2i32 - 3i32 + 4i32 5i32", tokens.to_string());
对于语法
对于语法围绕变量(类似于插入)进行迭代,但指定要迭代的变量。
# use template_quote::quote;
let v1 = vec![1, 2];
let v2 = vec!['a', 'b'];
let tokens = quote!{
#(for i1 in &v1) {
#(for i2 in &v2) {
#i1 -> #i2
}
}
};
assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string());
内部循环可以用插入替换
# use template_quote::quote;
let v1 = vec![1, 2];
let v2 = vec!['a', 'b'];
let tokens = quote!{
#(for i1 in &v1) {
#(
#i1 -> #v2
)*
}
};
assert_eq!("1i32 -> 'a' 1i32 -> 'b' 2i32 -> 'a' 2i32 -> 'b'", tokens.to_string());
您还可以使用for语句指定分隔符。
# use template_quote::quote;
let v = vec![1, 2];
let tokens = quote!{
#(for i in v) | { #i }
};
assert_eq!("1i32 | 2i32", tokens.to_string());
插入与在for语法中绑定的变量不可用。例如,
# use template_quote::quote;
let v = vec![vec![1, 2], vec![3]];
let tokens = quote!{
#(
#(for i in v) { #i }
),*
};
assert_eq!("1i32 2i32 , 3i32", tokens.to_string());
将失败,因为没有变量可用在插入语法中。
error: proc macro panicked
--> ***
|
6 | let tokens = quote!{
| ______________^
7 | | #(
8 | | #(for i in v) { #i }
9 | | )*
10 | | };
| |_^
|
= help: message: Iterative vals not found
在这种情况下,您可以使用#(for i in #v)
语法来指定使用插入迭代的变量。
# use template_quote::quote;
let v = vec![vec![1, 2], vec![3]];
let tokens = quote!{
#(
#(for i in #v) { #i }
),*
};
assert_eq!("1i32 2i32 , 3i32", tokens.to_string());
当语法
# use template_quote::quote;
let mut v = vec![1, 2].into_iter();
let tokens = quote!{
#(while v.next().is_some()) { hello }
};
assert_eq!("hello hello", tokens.to_string());
当-Let语法
# use template_quote::quote;
let mut v = vec![1, 2].into_iter();
let tokens = quote!{
#(while let Some(i) = v.next()) { #i }
};
assert_eq!("1i32 2i32", tokens.to_string());
与'for'语法相同,'while'中绑定的变量不能用插入语法迭代。例如,
# use template_quote::quote;
let mut v = vec![1, 2].into_iter();
quote!{
#(
#(while let Some(i) = v.next()) { #i }
)*
};
将失败。
Let语法
Let语法绑定新变量,可以在块中使用。
# use template_quote::quote;
let v = vec![(1, 'a'), (2, 'b')];
let tokens = quote!{
#(for i in v), {
#(let (n, c) = i) {
#n -> #c
}
}
};
assert_eq!("1i32 -> 'a' , 2i32 -> 'b'", tokens.to_string());
在这里,#n
和#c
不能用插入语法迭代。
内联表达式
您可以在quote!
宏中放置内联表达式。
# use template_quote::quote;
let v = vec![1, 2];
let tokens = quote!{
#(for i in v){
#i -> #{ i.to_string() }
}
};
assert_eq!("1i32 -> \"1\" 2i32 -> \"2\"", tokens.to_string());
以下示例将无法编译,因为它不知道要插入哪个变量
# use template_quote::quote;
let v = vec![1, 2];
let tokens = quote!{
#(
#{ v.to_string() }
)*
};
assert_eq!("\"1\" \"2\"", tokens.to_string());
在这种情况下,您可以在内联表达式中使用#i
语法来指定使用插入迭代的变量。
# use template_quote::quote;
let v = vec![1, 2];
let tokens = quote!{
#(
#{ #v.to_string() }
)*
};
assert_eq!("\"1\" \"2\"", tokens.to_string());
内联语句
您可以将任意语句放置在这个宏中。例如,
# use template_quote::quote;
let v = vec![1, 2, 3];
let tokens = quote!{
#(
#v
#{ eprintln!("debug: {}", &v); }
)*
};
assert_eq!("1i32 2i32 3i32", tokens.to_string());
将打印
debug: 1
debug: 2
debug: 3
为了可区分,所有语句都必须以';'结尾。例如,内联语句语法中的'if'语句应该放置额外的';'。
# use template_quote::quote;
let v = vec![1, 2, 3];
quote!{
#(
#v
#{ if v >= &2 { eprintln!("debug: {}", &v); } ; }
)*
};
Break, Continue
您可以在内联语句中放置控制语句,如break
或continue
,但这有一定的风险。
如果在块内部(如{ ... }
或( ... )
)中使用break;
,break
将突然放弃输出整个组,并且不会输出任何内容。例如,以下代码不会输出任何组
# use template_quote::quote;
let v = vec![1, 2, 3];
let tokens = quote!{
#(for i in v) {
#i // this is emitted once
// The block is not emitted
{
#i
#{ break; }
}
}
};
assert_eq!("1i32", tokens.to_string());
break
也会影响插入语法,如
# use template_quote::quote;
let v = vec![1, 2, 3];
let tokens = quote!{
#(
#v
#{ break; }
),*
};
assert_eq!("1i32", tokens.to_string());
遗憾的是,break
会泄漏到quote!
宏之外。以下示例展示了内部的break
影响了放置在quote!
宏之外的'for'循环。
# use template_quote::quote;
let mut v = Vec::new();
for _ in 0..3 {
let tokens = quote!{
#{ break; }
};
v.push(tokens);
}
assert_eq!(v.len(), 0);
这个crate提供了类似于quote的准引用宏。这个crate与原始的quote!
宏具有向后兼容性,并提供了类似于模板引擎的新语法。
这个crate从proc-quote中获得了一些灵感。
使用此软件包
此软件包适用于开发 proc-macro。通常,使用 template_quote 的 proc-macro 软件包在以下 Cargo.toml
中放置
[package]
name = "your_crate_name"
version = "0.0.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
template-quote = "0.2"
proc-macro2 = "1.0"
以及以下 src/lib.rs
代码
extern crate proc_macro;
extern crate proc_macro2;
extern crate template_quote;
use template_quote::quote;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
#[proc_macro]
pub fn my_macro(_: TokenStream) -> TokenStream {
quote! { /* something here */ }.into()
}
然后您就可以像这样使用它了
extern crate your_crate_name;
use your_crate_name::my_macro;
my_macro!()
限制
- 如果宏体中'#'之前的标点符号是
Spacing::Join
,则输出的标点符号也具有相同的间距,无论'#'符号是否由宏处理。
依赖项
~1.5MB
~36K SLoC