源代码中使用的一些术语(注意双重含义):
- macro:#定义/#宏对象将被扩展为替换文本
- macro:一个类似功能的宏,例如#define m(a, b)
- define:类似对象的宏,例如#define simple
- argless define(应该称为无参数):没有参数的类似函数的宏,例如#define f()
宏如何存储
宏基本上被存储为原始文本,而不是作为令牌运行(例如,在GCC的libcpp中)。没有参数的简单#define的主体存储为一个字符串。具有参数的宏以“宏令牌”的顺序存储。有三种类型的宏令牌:
原始文本,但空格和空行修剪(如#define中没有参数)
声明中使用了宏参数。该索引指定哪一个。在扩展期间,参数(索引)的文本被插入声明中的参数。
- 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 b
#define b a
a
但不在这里:(请注意,fbc将运行无限循环)
#define a a
#define m a
m