通过调用
pp.bas:ppCheck()在
lex.bas:lexSkipToken()期间解析了预处理程序指令(
#if,
#define,
#include等)。在移动到下一个令牌(或加载新令牌)之后,
ppCheck()将检查新的当前令牌是否为
'#'.如果是这样,还会检查以前的令牌是否为
EOL.如果是这样,它在行开始时找到一个
'#',并使用解析器使用的相同的
lexGetToken()/
lexSkipToken()接口直接解析PP指令。这是必要的,因为一些PP指令导致调用解析器函数,例如
#ifdef解析器使用
parser-identifier.bas:cIdentifier()来识别变量等。
dim as integer i
#ifdef i
#print yes, the variable will be recognized
#endif
因此,
lexSkipToken()由于PP而递归。
ppCheck()只会调用
pp.bas:ppParse()toplevel 来电
lexSkipToken(),但如果从PP中递归调用,则不会。这样可以让PP解析多行指令,如#macro ...#endmacro或跳过#if ...#endif块,而不执行这些结构可能包含的指令。请注意,与C不同,FB允许宏包含PP指令。
因此,每当FB解析器跳过EOL时,
lexSkipToken()可能会检测到一行
'#',然后调用PP来解析该指令。它可以“默默地”分析更多的行,并且解析器完全不知道PP指令是否都在那里。从
lexSkipToken()启动的PP解析甚至可能会遇到
#include并调用
fb.bas:fbIncludeFile()立即解析,递归地启动该#include文件的
parser-toplevel.bas:cProgram().解析器必须能够处理在
EOL中的每个
lexSkipToken()期间可能发生的递归,但幸运的是,这不是一件大事。解析器需要一个堆栈来跟踪复合语句。
请注意,在令牌展望(
lex.bas:lexGetLookAhead())期间,PP指令不处理。如果解析器要跨越
EOL展望未来,那么很可能会看到一个PP指令。幸运的是,尽管前瞻性地跨越线条并不是必需的。
PP指令中的宏扩展
指令开始后,
'#'之后的关键字被解析而不进行宏扩展。这意味着重新定义PP关键字(有意)对PP指令没有影响。例如:
#define define foo
#define bar baz
将
not 中间显示为:
诸如
#if&co。使用PP表达式解析器,它扩展宏。毕竟这是PP表达式的要点。例如:
#define foo 1
#if foo = 1
#endif
#define和
#macro指令根本不进行宏扩展。宏的身体按原样记录。
#define /#宏解析
pp.bas:ppDefine()首先解析宏的标识符。如果有一个
'(',之间没有空格,那么参数列表也被解析。
然后宏体被解析。对于每个令牌,其文本表示通过
lexGetText()检索,并将其附加到宏主体文本。空间保留(但修剪);评论被遗漏;在多行#macros空行中被删除。
如果宏具有参数,则会创建宏令牌(如宏扩展中所述)。为此,将宏参数添加到临时哈希表中,该表将参数名称与其索引相关联。然后,查找宏主体中的标识符,当识别参数时,将创建参数(索引)宏令牌,而不是将令牌附加到先前的text()宏令牌(或创建新的文本())它)。在该参数(索引)之后,如果再次存在其他文本,则会创建一个新的text()宏令牌。
在参数上使用
#可以创建一个stringify_parameter(index)宏令牌。PP合并运算符
##从宏体中简单地被忽略,所以
a##b在text()宏令牌中变为
ab.参数之前/之后/之间的所有正常文本都将转换为text()宏令牌。
For example:
#define add(x, y) foo bar x + y
And the actions of the #define/#macro parser will be:
'add' - The macro's name
'(' following the name, without space in between: Parse the parameter list.
'x' - Parameter 0.
',' - Next parameter.
'y' - Parameter 1.
')' - End of parameter list.
Create the macro body in form of macro tokens.
' ' - Create new text(" ").
'foo' - Append "foo".
' ' - Append " ".
'bar' - Append "bar".
' ' - Append " ".
'x' - Is parameter 0, create new param(0).
' ' - Create new text(" ").
'+' - Append "+".
' ' - Append " ".
'y' - Is parameter 1, create new param(1).
EOL - End of macro body.
Resulting in this macro body:
text(" foo bar "), param(0), text(" + "), param(1)
#define解析器允许重新定义宏,如果正文是相同的。例如:
不会导致重复的定义。不过这样做会
由于这些是纯文本#define,所以比较是body是简单的字符串比较。当前没有参数的宏不实现此函数。
PP表达式
预处理器具有自己的(但相当小而简单的)表达式解析器(pp-cond.bas:ppExpression())。它的工作原理很像parser-expression.bas:cExpression(),除了创建AST节点,ppExpression()会立即评估表达式。
PP跳过
预处理器使用简单的堆栈来管理
#if/
#endif块。那些可以嵌套,并且可能包含#include,但是它们不能跨文件。在解析#if 0或#else(
pp-cond.bas:ppSkip())之前,会立即跳过错误块(#if 0或#if 1的#else),然后返回
lexSkipToken().
例如:
#if 1 (push to stack: is_true = TRUE, #else not visited yet, return to lexSkipToken())
... (will be parsed)
#else 1) Set the #else visited flag for the current stack node,
so further #else's are not allowed.
2) Since the current stack node has is_true = TRUE,
that means the #else block must be skipped, -> call ppSkip().
... (skipped in ppSkip())
#endif (parsed from ppSkip(), skipping ends, ppSkip() returns to #else parser,
which returns to lexSkipToken())请注意,有一些关于PP跳过的棘手问题。由于宏允许包含PP指令,所以即使在跳过PP时也必须执行宏扩展,因为#else或#endif可能位于多行宏中。此外,在跳过PP时,不会处理多行#macro声明。这意味着,这样的东西:
#if 0
#macro test()
#endif
#endmacro
将被视为:
#if 0
#macro test()
#endif
#endmacro
导致错误(#endmacro没有#macro)。
所以这:
#if 0
#macro test()
#endif
#endmacro
#endif
不能按缩进的建议工作。