其他语言宏替换
C 语言宏替换
基本用法
- 标识符别名
- 宏函数
特殊用法
- 单个
#
号:字符串化,用双引号包含 - 两个
#
号##
:参数名称直接被放置到参数位置
缺点
- 不做语法检查
- 会改变求值顺序
- 宏参数的重复调用导致非预期行为
- 不会递归展开
- 宏参数的不完全展开(宏体存在
##
的时候)(否则,宏参数本身如果是宏,会在代入之前也做展开)
ANSI C标准中有几个标准预定义宏:
__LINE__:在源代码中插入当前源代码行号;
__FILE__:在源文件中插入当前源文件名;
__DATE__:在源文件中插入当前的编译日期
__TIME__:在源文件中插入当前编译时间;
__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus:当编写C++程序时该标识符被定义。
_FUNCTION_ 当前所在函数名
Rust 宏
语法解析过程
token --> token-tree --> AST -->
token上下文
每个标识符都被赋予了一个看不见的“句法上下文”。在比较两个标识符时,只有在标识符的明面名字和句法上下文都一致的情况下,两个标识符才能被视作等同。
macro_rules!
macro_rules! $name {
$rule0 ;
$rule1 ;
// …
$ruleN ;
}
规则由如下形式构成(注意小括号和花括号)
($pattern) => {$expansion}
当一个宏被调用时,对应的 macro_rules
解释器将一一依序检查规则
匹配、捕获过程
匹配分为 token-tree 匹配和 捕获
匹配。他们可以混合使用。有如下所示的各种匹配:
ident: 标识符,用来表示函数或变量名
block: 代码块,用花括号包起来的多个语句
expr: 表达式
pat: 模式,普通模式匹配(非宏本身的模式)中的模式,例如 Some(t), (3, 'a', _)
path: 路径,注意这里不是操作系统中的文件路径,而是用双冒号分隔的限定名(qualified name),如 std::cmp::PartialOrd
tt: 单个语法树
ty: 类型,语义层面的类型,如 i32, char
item: 条目,比如函数、结构体、模组等。
meta: 元条目,即被包含在 #[...]及#![...]属性内的东西。
stmt: 单条语句,如 let a = 42;
item: anything.
block: anything.
stmt: => , ;
pat: => , = if in
expr: => , ;
ty: , => : = > ; as
ident: anything.
path: , => : = > ; as
meta: anything.
tt: anything.
匹配重复
$ ( ... ) sep rep
// 括号内是重复的模式
// sep 是分隔符
// rep 表示重复次数 允许的值为 + 或 *
一般而言,在书写宏规则时,应从最具体的开始写起,依次写至最不具体的。
非标识符的标识符
self
作为rust语言里的语言关键词,在宏匹配的时候,可以被看做标识符(identifier)_
是rust语言里的关键词,却不能作为宏里的标识符
调试宏
trace_macros!
:trace_macros(true)
- 编译器
rustc
的-Z trace-macros
参数 log_syntax!
输出所有传递给它的标识符树(通过在文件头添加#![feature(log_syntax)]
启用)- 编译器
rustc
的--pretty
参数
宏的作用域
- 类似于rust代码的词法作用域;同一文件里,定义了宏后面的地方才可以使用
#[macro_use]
导入它所修饰的模块里导出的宏- 修饰
extern crate
导出的宏具有全局位置提升的效果,整个模块可用
- 修饰
- 子模块可以覆盖父模块里可见的同名宏
- 建议在库的最顶层导入所有用到的宏
导入导出
#[macro_export]
导出它所修饰的宏定义#[macro_use(X)]
仅导入宏X