FreeBASIC マニュアルのトップに戻る

FreeBASIC DevFbcLexerDirectives

目次→FreeBASIC のハッキング→FreeBASIC でのハッキングのための情報Lexer(字句解析器) と Preprocessor(前処理)Directive parsing←オリジナル・サイト

指示の解析



プリプロセッサ指示 (#if, #define, #include, etc.) は、 lex.bas:lexSkipToken() の間、pp.bas:ppCheck() を呼ぶことにより、解析されます。
次のトークンに移る(または新しいトークンをロードする)後に、ppCheck() は、新しい現在のトークンが '#' であるかどうか調べます。
もしそうならば、それは前のトークンが EOL であったかどうかも調べます。
もしそうならば、それは '#' を行の一で見つけます。そして、パーサーで使われる、同じ lexGetToken()/lexSkipToken() インターフェースを用いて、直接 PP 指示を解析します。
これは、いつくかの PP 指示が、呼ばれるパーサー関数になるので、変数などを認めるために、必要です。例えば、parser-identifier.bas:cIdentifier() は、#ifdef パーサーにより用いられます:

dim as integer i
#ifdef i
#print yes, the variable will be recognized
#endif

したがって、lexSkipToken() は、PP のため、再帰的です。
ppCheck() は、 toplevel が、lexSkipToken() を呼ぶために、pp.bas:ppParse() を、呼ぶだけです。しかし、それが PP から再帰的に呼ばれた場合は、そうなりません。
これは、 "executing" 指示が、これらの構造を含んでいなくても、 PP が、 #macro ... #endmacro や skip #if ... #endif ブロックのような、複数行指示を解析できるようにします。
C と違うことに注意してください。FB では、マクロが、PP 指示を含むことができます。

結果、FB パーサーが EOL をスキップするたびに、lexSkipToken() は、行の始めで '#' を見つけると、その指示を解析させるために、PP を呼びます。
それは、さらに多くの行を「黙って」解析するかもしれません。そして、パーサーは、PP 指示がそこにあることを、全く知らないままでいます。
lexSkipToken() から開始される PP 解析は、#include に遭遇して、その #include ファイルのための parser-toplevel.bas:cProgram() を再帰的に開始して、すぐにそれを解析する fb.bas:fbIncludeFile() さえ呼ぶかもしれません。
パーサーは、EOL で、すべての lexSkipToken() で起こるかもしれない再帰を、取り扱うことができなければなりません。しかし、幸運にも、それはたいした事でありません。
パーサーは、いずれにしろ、複合文の経過を追うために、スタックを必要とします。

PP 指示は、先読みトークン(lex.bas:lexGetLookAhead())の間、取り扱われないことに注意すべきです。
パーサーが EOL を通して先読みすると、それは、PP 指示をよく見ることができるでしょう。
しかし、幸運にも、行をまたがった先読みは、決して必要ありません。


PP 指示でのマクロ展開

指令の始めで、'#' に続くキーワードは、マクロ展開なしで解析されます。
これは、PP キーワードの再定義が、(意図的に) PP 指示に効果がないことを意味します。
例 :

#define define foo
#define bar baz

は、間にあって、以下のように見えません:

#foo bar baz

#if とその仲間のような指示は、PP 表現パーサーを利用します。これはマクロを展開します。 結局、これは PP 表現のポイントです。
例 :
#define foo 1
#if foo = 1
#endif

#define#macro 指示は、まったくマクロを展開しません。マクロの本体は、あるがままで記録されます。

#define/#macro parsing

pp.bas:ppDefine() は、最初にマクロの識別子を解析します。
間にスペースなしで、後に '(' が続いていれば、それから、パラメータ・リストは、また解析されます。

そして、マクロ本体が解析されます。
各々のトークンのために、そのテキスト表現は、lexGetText() を通して検索されます。そして、それはマクロ本体テキストに追加されます。
スペースは、保持されます(しかし、トリムされます);コメントは、無視されます; 複数行 #macro では、空白行は取り除かれます。

マクロに、パラメータがあれば、マクロ・トークンが、生成されます(マクロ展開の項を参照下さい)。
こうするために、マクロ・パラメータは、一時的なハッシュ・テーブルに加えられます。そして、一時的なハッシュ・テーブルは、パラメータのインデックスに、パラメータ名を結びつけます。
それから、マクロ本体の識別子は、調べられます。そして、パラメータが認められると、前の text() マクロトークンにトークンを追加する代わりに(またはそのために新しい text() を作成する代わりに)、パラメータ(インデックス)マクロトークンが、生成されます。
そのパラメータ(インデックス)の後に、再び他のテキストがあれば、新しい text() マクロトークンが生成されます。

パラメータで # を使うと、stringify_parameter(index) マクロトークンを生成します。
PP 合併演算子 ## は、単純にマクロ本体から除外されます。このため、a##b は、text() マクロトークンでは、ab になります。
すべての普通のテキスト before/after/between パラメータは、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 なので、本体の比較は、単純な文字列比較です。
この特徴は、現在、パラメータを持つマクロのために、実装されていません。

PP 式

プリプロセッサーには、それ自身の 式パーサー(pp-cond.bas:ppExpression())があります。(しかしかなり小さく、単純な)
それは、parser-expression.bas:cExpression() のように作動します。ただし、AST ノードを作成する代わりに、ppExpression() が、直ちに式を評価する点だけが、異なります。

PP 飛び越し

プリプロセッサーは、#if/#endif ブロックを管理するために単純なスタックを使います。
これらは入れ子にすることができます。また、#if/#endif ブロックの中に #includes を含める事ができます。しかし、#if/#endif ブロックは、ファイルをまたがることはできません。
間違ったブロック (#if 0、や #if 1 の #else) は、lexSkipToken() に戻る前に、#if 0 や #else (pp-cond.bas:ppSkip()) を解析するとき、直ちにスキップされます。

例えば:

#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

インデントによって示されるようには、働きません。

FreeBASIC の開発者用情報 に戻る
目次に戻る
ページ歴史:2016-03-12 13:18:00
日本語翻訳:WATANABE Makoto、原文著作者:DkLwikki

ホームページのトップに戻る

表示-非営利-継承