Macros
 

源代码中使用的一些术语(注意双重含义):
  • macro:#定义/#宏对象将被扩展为替换文本
  • macro:一个类似功能的宏,例如#define m(a, b)
  • define:类似对象的宏,例如#define simple
  • argless define(应该称为无参数):没有参数的类似函数的宏,例如#define f()

宏如何存储

宏基本上被存储为原始文本,而不是作为令牌运行(例如,在GCC的libcpp中)。没有参数的简单#define的主体存储为一个字符串。具有参数的宏以“宏令牌”的顺序存储。有三种类型的宏令牌:
    • text("<text>")
原始文本,但空格和空行修剪(如#define中没有参数)
    • textw("<wstring text>")
与上述相同,仅用于Unicode输入。
    • parameter(index)
声明中使用了宏参数。该索引指定哪一个。在扩展期间,参数(索引)的文本被插入声明中的参数。
    • stringify_parameter(index)
与上述相同,除非扩展期间参数将被字符串化。
注意:宏令牌实际上是symb.bi:FB_DEFTOK结构,它们包含一个id字段,保留FB_DEFTOK_TYPE_*值,以表示它们包含的内容。

For example:

 #define add(x, y) x + y

becomes:

 parameter(0), text(" + "), parameter(1)

And the expansion text will be:

 argument(0) + " + " + argument(1)
将宏存储为文本是一个相当简单的实现,但它需要一次又一次地重新解析宏主体。例如,由于GCC使用预处理令牌和tokenruns,宏被存储为令牌,使得扩展非常快,因为不需要一次又一次地标记宏体。fbc的实现没有那么灵活,也可能没有那么高效,但是并不那么复杂(关于代码和内存管理),并且有一个上升作用:##(PP令牌合并)的实现是微不足道的。##在记录宏的正文时,只要忽略它,而在令牌运行中,令牌需要被明确地合并。

何时展开宏?

由于令牌向前看,宏必须在标记化过程中扩展,否则错误的令牌可能会被加载到令牌队列中。最后,解析器只能看到最后的令牌,即使在展望中。

lexNextToken()中,在symb模块中查找每个字母数字标识符,以检查它是关键字还是宏。宏和关键字保存在相同的哈希表中。请注意,宏不能有关键词的名称; “#define integer”会导致错误。如果检测到宏,则会立即展开宏,一个进程也称为“加载”宏(pp-define.bas:ppDefineLoad())。

宏调用解析

如果宏接受参数,则必须解析宏“调用”,就像函数调用一样,在语法上也是如此。由于宏扩展已经发生在lexNextToken()中,令牌的来源,这里的解析有点棘手。正向移动只能通过替换(并丢失)当前令牌。令牌队列和令牌向前看不能依赖。相反,它只能替换当前的令牌向前移动,同时解析宏的参数。

由于lexNextToken()用于解析参数,所以参数中的宏本身就是递归宏扩展,而参数被解析并以文本格式记录。参数文本存储在扩展期间使用。

所以,在这个宏本身扩展之前,一个宏的参数被扩展,这可以被看作是好的和坏的特征:

#define stringify(s) #s
stringify(__LINE__)
在FB中导致2,但在C中导致__LINE__,因为在C中,与#或##一起使用时,宏参数不会展开。在C中,必须使用两个宏来获取2

#define stringize(s) #s
#define stringify(s) stringize(s)
stringify(__LINE__)

将宏扩展文本

扩展文本是从宏的身体标记构建的字符串。对于宏参数,参数文本将从由宏调用解析器创建的参数数组中检索,使用存储在参数令牌中的索引。参数字符串在这里完成。

有一个专门的内置定义(__LINE____FUNCTION____FB_DEBUG__等):
回调用于检索其“值”。例如:__LINE__的回调只是返回一个包含词法分析器的当前行号的字符串。

扩展

宏扩展文本(deftext)由词法分析器存储,现在它将从那里读取字符一段时间,而不是从文件输入缓冲区读取。在宏文本中跳过字符就像在文件输入中跳过字符:一旦跳过,它就会丢失,没有回退。所以,从来没有“旧”(解析)的宏文本,只有当前的char和被解析的文本。新的宏文本位于现有宏文本的前面。宏的内部宏扩展。

此实现不(容易)允许检测宏递归。很难跟踪宏文本缓冲区中哪个字符属于哪个宏,但是需要能够正确地推送和弹出宏。使用令牌运行实现可以更容易地完成,如GCC的libcpp所示。但是,C首先不允许递归宏:在C中,宏的标识符在该宏的正文内未定义(不会触发扩展)。在fbc中不是这样,因为(再次)一种检测宏体结束的时间没有实现的方法。

目前,fbc仅跟踪第一个(toplevel)宏扩展,因为当达到特定的宏结束时,它很容易被检测到:一旦没有更多的宏文本。

这就是为什么在这里检测到递归:

#define a a
a
也在这里:

#define a b
#define b a
a
但不在这里:(请注意,fbc将运行无限循环)

#define a a
#define m a
m