Директивы парсинга
 

Директивы препроцессора (#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
не будет рассматриваться как промежуточное:

#foo 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 парсер позволяет макросу, перезаписываться, если тело то же самое. Например:

#define a 1
#define a 1
не приводит к дублированному определению. Однако это будет:

#define a 1
#define a 2
С чистыми текстами #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
не будет работать в соответствии с предложением отступа.