Директивы препроцессора (#if, #define, #include, и т.д.) анализируются во
время lex.bas:lexSkipToken(), вызывая pp.bas:ppCheck(). После перемещения в
следующий маркер (или загрузке нового маркера), ppCheck () проверит,
является ли новый текущий маркер '# '. Если это так , то проверит, был ли
предыдущий маркер EOL. Если все нормально, то непосредственно анализируется
PP директива , используя тот же интерфейс lexGetToken()/lexSkipToken()
парсера. Это необходимо, потому что некоторый результат PP директивы в
вызовах функций парсера, например parser-identifier.bas:cIdentifier(),
использует #ifdef парсера, чтобы распознать переменные и т.д.:
dim as integer i
#ifdef i
#print yes, the variable will be recognized
#endif
Так, lexSkipToken () рекурсивна из PP. ppCheck () только вызовет
pp.bas:ppParse() для вызовов верхнего уровня к lexSkipToken(), но не тогда,
когда это вызывается рекурсивно из PP. Это позволяет PP анализировать
многострочные директивы #macro... #endmacro или пропускать #if... #endif
блоки, без директив "выполнение" , которые могут содержать структуры.
Обратите внимание на то, что в отличие от C, FB позволяет макросам содержать
PP директивы .
В результате каждый раз когда парсер FB пропускает EOL, lexSkipToken() может
обнаруживать '#' в начале строки и обрабатывать, затем вызвать PP, чтобы
позволить ему анализировать директиву. Это позволяет "спокойно" парсить
больше строк, и парсер не осознает, что PP директивы там. PP Парсинг ,
запущенный из lexSkipToken(), может даже встретиться с #include и вызвать
fb.bas:fbIncludeFile (), чтобы сразу проанализировать его, рекурсивно
запуская parser-toplevel.bas:cProgram() для этого включаемого файла. Парсер
должен быть в состоянии обработать рекурсию, которая могла бы произойти во
время каждого lexSkipToken () в EOL, но, к счастью, это не имеет большого
значения. Парсер нуждается в стеке, чтобы отслеживать составные операторы в
любом случае.
Обратите внимание на то, что PP директивы не обрабатываются во время
маркера, забегая вперед (lex.bas:lexGetLookAhead()). Если бы парсер должен
был забегать вперед через EOL, он мог бы очень хорошо видеть PP директиву .
К счастью, в этом нет необходимости.
Макрорасширение в PP директивах
Начало директив ключевых слов после символа '#',
разбирается без макрорасширения. Это означает переопределение PP ключевого
слова (намеренно) , но не имеет никакого эффекта на директивы PP. Например:
#define define foo
#define bar baz
не будет рассматриваться как промежуточное:
Директивы
#if
и co. используют PP выражение парсера, которое действительно разворачивает
макросы. В конце концов в этом суть выражений PP. Например:
#define foo 1
#if foo = 1
#endif
Директивы
#define и
#macro directives не
делают макрорасширения вообще. Тело макроса записывается как есть.
#define/#macro парсинг
pp.bas:ppDefine()первый парсится
идентификатор макроса. Если есть' (' после, без пробела, то список
параметров анализируется тоже.
Затем тело макроса анализируется. Для каждого маркера его текстовое
представление полученное через lexGetText(), и это добавляется к тексту тела
макроса. Пробелы сохраняются (но обрезаются); комментарии не учитываются; в
многостроковых макросах(#macro) пустые строки удаляются.
Если у макроса будут параметры, то макро-маркеры будут создаваться (как
обсуждалось в Макрорасширении). Чтобы сделать это, макро-параметры
добавляются к временной хэш-таблице, которая связывает названия параметров к
их индексам. Затем ищутся идентификаторы в теле макроса , и когда параметр
распознан, parameter(index) макро-маркера создается, вместо того, чтобы
добавить маркер к предыдущему text() макро-маркера (или создается новый
text() для него). После parameter(index), если есть другой текст , новый
text() макро-маркера создается.
Используя # на параметре приводит к созданию stringify_parameter(index)
макро-маркера. PP оператор слияния ## просто пропускается из тела макроса ,
таким образом, a##b становится 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 парсер позволяет макросу, перезаписываться, если тело то же самое. Например:
не приводит к дублированному определению. Однако это
будет:
С чистыми текстами #defines, сравнение тела - простое
сравнение строк. Эта опция не реализована для макросов с параметрами
в настоящее время.
PP expressions
У препроцессора есть свой собственный (но довольно
маленький и простой) парсер выражений (pp-cond.bas:ppExpression()). Это
работает во многом как parser-expression.bas:cExpression(), за исключением
того, что вместо создания AST узлов, ppExpression () сразу оценивает
выражения.
PP пропускание
Препроцессор использует простой стек, чтобы управлять
блоками #if / #endif. Те могут быть вложены, и могут быть #includes в них,
но они не могут перейти через файлы. Ложные блоки (#if 0, или #else от #if
1) сразу пропускаются при парсинге (pp-cond.bas:ppSkip()), прежде, чем
возвращается 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
могли быть в многострочном макросе. Кроме того, многострочные #macro
объявления не обработаны во время пропуска PP. Это означает, что-то
вроде этого:
#if 0
#macro test()
#endif
#endmacro
будет рассматриваться как:
#if 0
#macro test()
#endif
#endmacro
В результате чего ошибки (#endmacro без #macro).
Таким образом, это:
#if 0
#macro test()
#endif
#endmacro
#endif
не будет работать в соответствии с предложением отступа.