Directive parsing
 

通过调用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 中间显示为:

#foo bar baz
诸如#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 a 1
#define a 1
不会导致重复的定义。不过这样做会

#define a 1
#define a 2
由于这些是纯文本#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
不能按缩进的建议工作。