#quote #proc-macro #macro #syn

template-quote

一款时尚的quote宏实现,拥有类似漂亮模板引擎的语法

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过程宏 中排名

Download history 39/week @ 2024-04-15 61/week @ 2024-04-22 22/week @ 2024-04-29 42/week @ 2024-05-06 1/week @ 2024-05-13 70/week @ 2024-05-20 29/week @ 2024-05-27 30/week @ 2024-06-03 33/week @ 2024-06-10 19/week @ 2024-06-17 13/week @ 2024-06-24 22/week @ 2024-07-15 9/week @ 2024-07-22 112/week @ 2024-07-29

143 每月下载次数
用于 8 个crate(5个直接使用)

MIT 许可证

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

您可以在内联语句中放置控制语句,如breakcontinue,但这有一定的风险。

如果在块内部(如{ ... }( ... ))中使用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