宏
GS 支持类似C语言的宏定义和宏替换。
宏定义
| 语法 | |
|---|---|
| 1 | #define 标识符 替换序列可选 |
| 2 | #define 标识符 ( 宏的形参列表 ) 替换序列可选 |
#define 是一条预处理指令,能够将一个标识符定义为宏。编译器在后续代码中如果遇到了这个标识符,将按照 替换规则 对源码执行文本替换。
GS 支持两种宏,即如 语法(1) 所示的对象宏和如 语法(2) 所示的仿函数宏。
仿函数宏的形参列表
对于 语法(2) 中的仿函数宏,可以在宏的形参列表中定义零到若干项 形参,并在形参列表的末尾允许以 ... 为结尾,表示此宏接受多余参数作为变长参数。
#define EXAMPLE_MACRO(a, b, ...) writeln(fmt, __VA_ARGS__)
仿函数宏的形参应当是一个标识符;形参与形参之间,以及形参与 ... 之间使用 , 作为分隔符。
调用仿函数宏
| 语法 |
|---|
| 标识符 ( 宏的实参列表 ) |
仿函数宏的调用看起来像是一个普通的函数调用(这也是它被称为仿函数宏的原因)
仿函数宏的实参仅使用 , 分割,逗号左右两边的部分将被切分为不同的实参:
#define EXAMPLE_MACRO(a, b, c) a, b, c
writeln(EXAMPLE_MACRO("Hello", "World", "!")); // 调用仿函数宏
如果一个逗号被实参列表内的括号包含,那么这个逗号不会被视为实参分隔符:
#define EXAMPLE_MACRO(a, b) a, b
writeln(EXAMPLE_MACRO("Hello", (["World", "!"]))); // 被替换为 writeln("Hello", (["World", "!"]));
writeln(EXAMPLE_MACRO("Hello", ["World", "!"])); // 编译错误,因为逗号被视为实参分隔符,此处传入了三个实参
宏的替换规则
除了特例情况之外,当编译器在源码文本中发现了一个已经被定义为宏的标识符时:
-
对象宏 的替换
直接将源文本中的该标识符替换为宏定义时指定的替换序列
如果没有指定替换序列,则相当于替换为空字符串
#define EXAMPLE_MACRO "Hello, World!"
#define EMPTY_MACRO
writeln(EXAMPLE_MACRO); // 被替换为 writeln("Hello, World!");
writeln(EMPTY_MACRO); // 被替换为 writeln(); -
仿函数宏的替换
将传入的实参和宏的形参一一配对,如果仿函数宏有变长形参,将多余形参数量的其余实参传递给
__VA_ARGS__;除非变长参数接收了多余参数,否则 GS 会将实参数量与形参数量不相等视为编译错误
将宏定义时指定的替换序列中出现的形参标识符,一一对应地以实参替换;如果有变长参数,则将
__VA_ARGS__替换为所有多余实参的列表。将此替换过后得到的新替换序列,替代覆盖源码文本中出现的整个仿函数调用。
#define EXAMPLE_MACRO(a, b, ...) a, b, __VA_ARGS__
writeln(EXAMPLE_MACRO("Hello", "World", "!")); // 被替换为 writeln("Hello", "World", "!");对于变长参数的仿函数宏,如果没有实参被传递给
__VA_ARGS__,则替换序列时,__VA_ARGS__前方紧邻的,会被删除。#define EXAMPLE_MACRO(a, b, ...) a, b, __VA_ARGS__
writeln(EXAMPLE_MACRO("Hello", "World")); // 被替换为 writeln("Hello", "World");- # 和 ##
在替换形参时,如果替换序列中的形参以
#开头,则该形参会被替换为实参文本对应点的字符串字面量。#define EXAMPLE_MACRO(a) #a
writeln(EXAMPLE_MACRO(Hello World)); // 被替换为 writeln("Hello World");如果替换序列的形参两侧(或其中一侧)有
##,则替换序列时,该形参会被替换为实参文本对应点的标识符,并与##另一侧的文本进行拼接,形成一个新的标识符。#define EXAMPLE_MACRO(a) prefix_##a##_suffix
writeln(EXAMPLE_MACRO(Hello)); // 被替换为 writeln(prefix_Hello_suffix);
在完成替换之后,使用相同的规则继续对替换产生的序列递归地执行替换,直到替换序列中不再包含任何宏标识符为止。
需要注意的是,与C语言的宏不同,在递归栈开时,如果遇到已经展开过的宏,展开不会自动停止,因此避免在GS代码中引入需要递归展开的宏定义。
不展开宏的特殊情况
在以下情况下,在遇到被定义为宏的标识符时,编译器不会执行宏的替换:
- 在 import 语句中
import MACRO; // 不会展开 MACRO - 在 component 语句中
component MACRO; // 不会展开 MACRO - 在其他预处理命令中
#include MACRO
#pragma MACRO
#define MACRO 5 // 不会展开 MACRO
常用的内置宏
| 宏 | 替换结果 | 说明 |
|---|---|---|
__FILE__ | 相对于 /r 设置的根路径的当前文件路径 + 当前文件名 | 如 /path/test.gs |
__PURE_FILE__ | 当前文件的文件名 | 如 test.gs |
__PURE_FILE_NAME__ | 当前文件的文件名(不含扩展名) | 如 test |
__CLASS_NAME__ | 当前类的名称 | 仅在类的作用域中有效 |
__DIR__ | 相对于 /r 设置的根路径的当前文件路径 | 如 /path/ |
__LINE__ | 当前所在的行号 | 如 3 |
__LOCATION__ | __FILE__ + ":" + __LINE__ | 如 /path/test.gs:3 |
__FUN__ | 当前所在函数的名字 | 如 test_func |
__SRC_PREFIX__ | "#pragma location " + __FILE__ + ":" + __LINE__ | 如 #pragma location /path/test.gs:3 |
__COUNTER__ | 在此之前的 __COUNTER__ 的数量 | 从 0 开始计数 |