跳到主要内容
版本:master

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 开始计数