ホームページ >バックエンド開発 >PHPチュートリアル >PHP 構文アナライザー: RE2C && BISON の概要_PHP チュートリアル
この前に、PHP コードから拡張機能を自動的に生成するプロジェクトを試してみました
PHP にコンパイルされたものを phptoc と呼びます。
しかし、諸事情によりこのプロジェクトは中止となりました。
この記事を書いたのは、第一にこの分野の情報が少なすぎるため、第二に、PHP 構文解析を理解できるように、学んだことを要約するためです。
PHPソースコードの研究をより高いレベルに高めていきます^.^…できるだけわかりやすく、わかりやすく書くように心がけています。
このプロジェクトのアイデアは、Facebook のオープンソース プロジェクト HipHop から生まれました。
実際のところ、私はこのプロジェクトの 50% ~ 60% のパフォーマンス向上については懐疑的です。根本的に言えば、PHP が APC キャッシュを使用するとパフォーマンスが低下するのではないかと考えています。
ヒップホップについては、まだテストしていないので、確かなことは言えません。
PHPtoc、私は C プログラマーを解放したいだけであり、PHPer が PHP コードを使用して PHP 拡張機能のパフォーマンスに近い拡張機能を作成できるように、それが達成されることを願っています。
プロセスは次のとおりです: PHP ファイルを読み取り、PHP コードを解析し、構文アナライザーを実行し、対応する ZendAPI を生成し、それを拡張機能にコンパイルします。
本題へ
ここで最も難しいのは構文アナライザーです。PHP にも独自の構文アナライザーがあることを誰もが知っているはずです。現在のバージョンでは re2c と Bison が使用されています。
ということで、当然私もこの組み合わせを使いました。
PHP の構文アナライザーを使用したい場合は、zend_ language_parser.y と zend_ language_scanner.l を変更して再コンパイルする必要があるため、現実的ではありません。これは難しいだけでなく、PHP 自体に影響を与える可能性もあります。
そこで、独自の構文解析ルールのセットを書き直すことにしました。この関数は、PHP の構文アナライザーを書き直すのと同じであり、もちろん、一部の珍しいものは破棄されます。
re2c && yacc/bison は、対応するファイルを参照して *.c ファイルにコンパイルし、最後に gcc でコンパイルすると
が生成されます
独自のプログラムに。したがって、これらは基本的に構文解析プログラムではなく、私たちのルールに基づいて独立した C テキストを生成するだけですこの c ファイルは、私たちが必要とする実際の構文解析プログラムです。私はこれを構文ジェネレーターと呼びたいと思います。以下に示すように:
注: 画像の a.c は、スキャナーによって生成された最終コードです。 。
re2c スキャナー、作成したスキャン ルール ファイルが scanner.l と呼ばれる場合、作成した PHP ファイルのコンテンツをスキャンし、
に従ってスキャンします。
私たちが作成したルールはさまざまなトークンを生成し、それらを解析に渡します。私たちが書いた (f)lex 文法ルールは、たとえば、Parse.y と呼ばれます
yacc/bison を通じて parse.tab.h、parse.tab.c ファイルにコンパイルされます。Parse はさまざまなトークンに従ってさまざまな操作を実行します。
たとえば、PHP コードは「echo 1」です。
スキャンには次のルールがあります:
「エコー」{
T_ECHO を返します;
}スキャナー関数 scan は「echo 1」文字列を取得し、このコード部分をループして、エコー文字列が見つかった場合は、トークンをキーワード T_ECHO,
として返します。parse.y と Scanner.l はそれぞれ、scanner.c と parse.tab.c という 2 つの C ファイルを生成し、それらを gcc で一緒にコンパイルします。
以下で詳しくお話します
ご興味がございましたら、中国語版も翻訳しましたので、ぜひご覧ください。
まだ終わっていないので、また後日載せます。re2c はいくつかのマクロ インターフェイスを提供していますが、私はそれを翻訳しただけなので、原文が必要な場合は、上記のアドレスにアクセスしてください。
インターフェースコード:
他のスキャナ プログラムとは異なり、re2c は完全なスキャナを生成しません。ユーザーはインターフェイス コードを提供する必要があります。ユーザーは、次のマクロまたはその他の対応する構成を定義する必要があります。
YYCONDTYPE
-c モードでは、-to パラメーターを使用してファイルを生成できます。列挙型を含む条件を使用します。各値はルール セットの条件として使用されます。
YYCタイプ
入力シンボルを維持するために使用されます。通常は char または unsigned char です。
YYCTXマーカー
*タイプ YYCTYPE の式の場合、生成されたコード トレースバック情報のコンテキストは YYCTXMARKER に保存されます。スキャナ ルールでコンテキスト内で 1 つ以上の正規表現を使用する必要がある場合、ユーザーはこのマクロを定義する必要があります。
YYカーソル
*YYCTYPE 型の式ポインターは現在入力されているシンボルを指し、生成されたコードは最初にシンボルとして照合され、YYCURSOR は現在のトークンの最初の文字を指すと想定されます。最後に、YYCURSOR は次のトークンの最初の文字を指します。
YYDEBUG(状態,現在)
これは、-d フラグが指定されている場合にのみ必要です。ユーザー定義関数を呼び出すときに、生成されたコードをデバッグするのは非常に簡単です。
この関数には、void YYDEBUG(int state,char current) というシグネチャが必要です。最初のパラメータは state を受け入れ、デフォルト値は -1 です。2 番目のパラメータは入力の現在位置を受け入れます。
YYFILL(n)
バッファーを埋める必要がある場合、生成されたコードは YYFILL(n) を呼び出し、少なくとも n 文字を提供します。 YYFILL(n) は、必要に応じて YYCURSOR、YYLIMIT、YYMARKER、および YYCTXMARKER を調整します。一般的なプログラミング言語では、n は最長のキーワードの長さに 1 を加えたものに等しいことに注意してください。ユーザーは /*!max:re2c*/ で YYMAXFILL を 1 回定義して、最大長を指定できます。 -1 を使用すると、YYMAXFILL は /*!re2c*/ の後に 1 回ブロックされます。
YYGETCONDITION()
-c モードを使用した場合、この定義はスキャナ コードの前に設定された条件を取得します。この値は、列挙型 YYCONDTYPE の型に初期化する必要があります。
YYGETSTATE()
-f モードが指定されている場合、ユーザーはこのマクロを定義する必要があります。その場合、スキャナーの開始時に保存された状態を取得するために、生成されたコードは YYGETSTATE() を呼び出します。この値が -1 の場合、YYGETSTATE() は符号付き整数を返す必要があります。初回実行。それ以外の場合、この値は以前の YYSETSTATE によって保存された状態と同じになります。それ以外の場合、スキャナは操作を再開した直後に YYFILL(n) を呼び出します。
イリミット
式 *YYCTYPE のタイプは、バッファーの終わりをマークします (YYLIMIT(-1) はバッファーの最後の文字です)。生成されたコードは、YYCORSUR と YYLIMIT を継続的に比較して、いつバッファーを埋めるかを決定します。
YYSETCONDITION(c)
このマクロは、-c モードが指定され、変換ルールが使用される場合にのみ、変換ルールの条件を設定するために使用されます。
YYSET州
ユーザーは、-f モードを指定する場合にのみこのマクロを定義する必要があります。その場合、生成されたコードは、YYFILL(n) の前に YYSETSTATE(s) を呼び出します。YYSETSTATE のパラメーターは、固有の ID と呼ばれる符号付き整数です。 ) 実例。
YYマーカー
*YYCTYPE タイプの式。生成されたコードはトレースバック情報を YYMARKER に保存します。一部の単純なスキャナは役に立たない場合があります。
スキャナーは、その名前が示すように、ファイルをスキャンしてキーコードを見つけます。
スキャナファイル構造:
/* #include ファイル*/
/*マクロ定義*/
//スキャン関数
int scan(char *p){
/*スキャナールールエリア*/
}
//scan 関数を実行し、トークンを yacc/bison に返します。
int yylex(){
int トークン;
char *p=YYCURSOR;//YYCURSOR は PHP テキスト コンテンツを指すポインターです
while(token=scan(p)){//ポインタ p は、上で定義したスキャナであるかどうかを判断するためにここに移動します...
トークンを返す;
}
}
int main(int argc,char**argv){
BEGIN(初期);///
YYCURSOR=argv[1];//YYCURSOR は PHP テキスト コンテンツを指すポインターです
yyparse();
}
BEGIN は定義されたマクロです
#define YYCTYPE char //入力記号の種類
#define STATE(名前) yyc##name
#define BEGIN(n) YYSETCONDITION(STATE(n))
#define LANG_SCNG(v) (sc_globals.v)
#SCNG LANG_SCNG を定義します
#define YYGETCONDITION() SCNG(yy_state)
#define YYSETCONDITION(s) SCNG(yy_state)=s
yyparse 関数は yacc で定義されています
その中に重要なマクロがあります: YYLEX
#define YYLEX yylex()
スキャナーのyylexを実行します
少しねじれている可能性がありますので、結び直してください:
scanner.l では、parse.y パーサー関数 yyparse を呼び出すことにより、この関数は scanner.l の yylex を呼び出して、キー コード トークン yylex を生成します
スキャナーから戻ってきました
トークンは parse.y に返され、parse は異なるトークンに基づいて異なるコードを実行します。
例:
スキャナー.l
#「scanner.h」を含める
#include "parse.tab.h"
int scan(char *p){
/*!re2c
}
「エコー」{
}
[0-9]+{
T_LNUMBER;
を返します
}
*/
}
int yylex(){
int c;
int トークン;
char *p=YYCURSOR;
while(token=scan(p)){
トークンを返す;
}
}
BEGIN(INITIAL);//初期化
YYCURSOR=argv[1];//ユーザーが入力した文字列をYYCURSORに入れる
yyparse();//yyparse() -》yylex()-》yyparse()
0 を返す;
}
こんな簡単なスキャナーが出来ました
パーサーとして flex と bison を使用します。 。 。
flexのファイル構造について:
%{
/*
C コードセグメントは、lex コンパイル後に生成される C ソースファイルにそのままコピーされます
いくつかのグローバル変数、配列、関数ルーチンなどを定義できます...
*/
#含める
#「scanner.h」を含める
extern int yylex();//scanner.l で定義されています。 。
void yyerror(char *);
#YYPARSE_PARAM tsrm_ls を定義します
#YYLEX_PARAM tsrm_ls を定義します
%}
{トークンが定義される定義セクション}
//これがキーです。トークン プログラムはこれに基づいて切り替えを行います。
%トークンT_OPEN_TAG
%トークンT_ECHO
%トークン T_LNUMBER
%%
{ルールセクション}
開始:
T_OPEN_TAG{printf("startn") }
|開始ステートメント
;
ステートメント:
T_ECHO expr {printf("echo :%sn",$3)}
;
式:
T_LNUMBER {$$=$1;}
%%
{ユーザー コード スニペット}
void yyerror(char *msg){
printf("エラー:%sn",msg);
}
ルールセクションでは、start が始まりです。scan が PHP 開始タグを認識すると、T_OPEN_TAG を返し、括弧内のコードを実行して start を出力します。
scanner.l では、scan の呼び出しは while ループであるため、PHP コードの最後までチェックされます。
であることを検出します。
マクロ #line を介して parse.y に対応する 21 行目、T_OPEN_TAG の位置にマッピングされ、実行されます
では、TOKEN は yyparse に返された後、何をしたのでしょうか?
より直感的にするために、gdb トラッキングを使用します。
このとき、yycharは258です。258とは何ですか?
258はbisonが自動生成する列挙型データです。
続けるYYTRANSLATE マクロは yychar を受け入れ、対応する値を返します
#define YYTRANSLATE(YYX) ((unsigned int) (YYX)
/* YYTRANSLATE[YYLEX] -- YYLEX に対応する Bison シンボル番号。 */
static const yytype_uint8 yytranslate[] =
{
0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 27, 2,
22, 23, 2, 2, 28, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 21,
2, 26, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 24, 2, 25, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15、 16、 17、 18、 19、 20
};
yyparse拿到这个值,断続翻訳,
bison 会は多用して映る数グループを生成し、最終的な翻訳を yyn に保存します
この样bison就能找到トークン所对应的代码
スイッチ (yyn)
{
ケース 2:
/* yacc.c の 1455 行目 */
#30行目「parse.y」
{printf("startn"); ;}
休憩;
このように、不断循環、トークン条項の逐次実行を生成し、その後、所望の zend 関数等を解析し、对のop保護存在哈希表を生成します。これらはここでは取り上げません、