well.この文書は、regexエンジンの本質とその仕組みに光を当てる試みです。regexエンジンはperlのコードベースのかなりの部分を表していますが、比較的よく理解されていません。この文書は、この状況に対処するためのわずかな試みです。5-porterメーリングリストへのフィードバック、そして間違いなく他の場所からも導き出されています。perlreapi.注意!ここで説明した動作と構造は、著者が執筆時点で理解していたエンジンの状態を表していることを明確に理解しておく必要があります。これはNOTAPI定義であり、regexエンジンをハックしたり、regexエンジンがどのように動作するかを理解したい人のための、純粋に内部ガイドです。この文書の読者は、perlのregex構文とその使い方を詳細に理解していることが期待されています。Perlの正規表現の基礎について知りたい場合は、perlreを参照してください。regexエンジンを独自のものに置き換えたい場合は、perlreapiを参照してください。why."regexp"と"regex"のどちらと呼ぶかについては議論がありますが、この文書では特別な理由がない限り"regex"という用語を使います。特別な理由がある場合はその理由を説明します。"REFERENCES").regexについて話すときには、ソースコード形式と内部形式を区別する必要があります。このドキュメントでは、"パターン"という用語をテキストで、ソースコード形式で、"プログラム"という用語を内部表現で使用します。"REFERENCES")に関する論文で採用しているS-regexおよびB-regexという用語に対応しています。language.正規表現エンジンは、ミニ言語で指定された一連の制約を受け取り、それらの制約をターゲット文字列に適用し、文字列が制約を満たすかどうかを判断するプログラムです。この言語の完全な定義については、perlreを参照してください。itself.それほど大げさではありませんが、ジョブの最初の部分は、パターンをコンピューターがストリング内の一致点を見つけるために効率的に使用できるものに変換することであり、2番目の部分は検索そのものを実行することです。efficiently.そのためには、テキストを構文解析してプログラムを作成する必要があります。次に、プログラムを実行して、マッチする文字列内の点を見つける必要があります。すべてを効率的に行う必要があります。years:これは少し混乱しており、この用語に反対する人もいるが、regexp.hに何年もあるコメントを見る価値がある。technology).これは本質的に非決定性有限状態マシン(構文チャート、あるいは構文解析技術における「鉄道正規形」)の線形符号化です。track.「鉄道標準形式」という用語はやや難解であり、「構文図/チャート」または「鉄道図/チャート」がより一般的な用語です。これはregexプログラムの有用なメンタルイメージを提供します:各ノードは1つのエントリとほとんどの場合1つの終了ポイントを持つトラックの単位と考えることができ(分岐するトラックの断片がありますが、統計的には多くありません)、全体は1つのエントリと1つの終了ポイントを持つレイアウトを形成します。マッチングプロセスは、トラックに沿って移動する車と考えることができます。システム内の特定のルートは、各コネクタポイントで読み取られた文字によって決定されます。車はどのポイントでもトラックから落ちることができますが、トラックと一致する場合にのみ進むことができます。chart:したがって、パターン/foo(?:\w+\d+\s+)bar/は次のチャートと考えることができます。closely.実際のところ、最近のperlの正規表現はこの種の構造よりもはるかに複雑ですが、この方法で表示することは、状況を把握しようとする際に役立ち、現在の実装と非常によく一致します。"regops".より正確には、regexプログラムはグラフのエンコードであると言います。グラフ内の各ノードは、元のregexパターンの一部(リテラル文字列や分岐など)に対応しており、次に一致するコンポーネントを表すノードへのポインタを持っています。"node"と"opcode"はperlソース内ですでに他の意味を持っているため、regexプログラム内のノードを"regops"と呼びます。structures.プログラムはregnode構造体の配列で表され、その1つ以上がプログラムの1つのregopを表します。regnode構造体は必要な最小構造体であり、他のすべてのより大きな構造体と共有されるフィールド構造体を持っています。precedence.]BRANCH以外のすべてのregopsの"next"ポインタは連結を実装します。両端にBRANCHがある"next"ポインタは、2つの選択肢を接続します。[ここでは、微妙な構文依存関係の1つを紹介します。個々のBRANCH(それらの集合ではなく)は、演算子の優先順位のために何とも連結されません。branch.regopのオペランドには、リテラル文字列が含まれているものと、サブプログラムに通じるregopが含まれているものがあります。BRANCHノードのオペランドは、分岐の最初のregopです。side.注意:鉄道メタファが示唆するように、これはnot木構造である:枝の尾はBRANCHesの集合に続くものに接続している。これは鉄道の単線のようなもので、駅や鉄道ヤードに入るときに分かれ、反対側に出るときに再結合する。follows:regopの基本構造は、regexp.hで次のように定義されます。description.他のより大きなregnode様構造体は、regcomp.hで定義されます。これらは、regnodeと同じフィールドを持ち、構造体に追加フィールドが続く可能性があり、いくつかの基本フィールドの特定の意味(および名前)がオーバーライドされる場合があるという点で、サブクラスとほとんど同じです。以下に、より詳細な説明を示します。instead:regnode_1構造体は同じヘッダを持ち、その後に単一の4バイト引数が続きます。regnode_2構造体は代わりに2バイト引数を2つ含みます。bytes:リテラル文字列に使用されるregnode_string構造体は、1バイト長のヘッダと文字列データの後に続きます。文字列の末尾には0バイトが埋め込まれ、ノード全体の長さは4バイトの倍数になります。class.文字クラスはregnode_charclass構造体によって表されます。この構造体には、4バイトの引数と、クラスに含まれる文字を示す32バイト(256ビット)のビットマップがあります。charクラスを表すために使用されるcharクラス構造体のより大きな形式もあります。charクラスが含まれているかを示す4バイト(32ビット)のビットマップが追加されています。field.regnodes.hは、regarglen[]と呼ばれる配列を定義します。regnode(4バイト)単位で各オペコードのサイズを表します。マクロは、str_lenフィールドに基づいてEXACTノードのサイズを計算するために使用されます。used.regopsは、regcomp.plによってregcomp.symから生成されるregnodes.hで定義されます。現在、個別のregopsの最大数は256に制限されており、約1/4がすでに使用されています。types.マクロのセットを使用すると、フィールドへのアクセスがより簡単で一貫性のあるものになります。マクロには、regnode様構造の型を決定するために使用されるOP()、次のノードへのオフセットであるNEXT_OFF()(これについては後で詳しく説明します)、引数を読み取ったり設定したりするためのARG()、ARG1()、ARG2()、ARG_SET()などがあります。文字列やregopを含む型を操作するためのSTR_LEN()、STRING()、OPERAND()などがあります。clear.regexエンジンの「next」には3つの異なる概念がありますが、それらを明確にしておくことが重要です。so.特定のregnodeからの「次のregnode」があります。この値は、値の点で他のいずれかと一致する場合や、コードが常に一致すると仮定する場合を除き、ほとんど役に立たない値です。large.指定されたregop/regnodeから「次のregop」があります。これは、現在のregopの後に物理的に位置するregopであり、現在のregopのサイズによって決定されます。これは、構造体をダンプするときなど、この順序を使用してトラバースする場合に便利です。regnode大きいと仮定する場合があります。meaning.与えられたregopから"regnext"があります。これは、NEXT_OFF()の値だけ前方にジャンプすることによって到達するregopです。少数の場合には、regnode_1構造体のarg1場によってより長いジャンプをすることによって到達するregopです。サブルーチンregnext()はこれを透過的に処理します。これはノードの論理的な後継ノードであり、場合によってはBRANCHregopのように特別な意味を持ちます。steps:大まかに言えば、パターンに対するストリングのマッチングを実行するには、次のステップが含まれます。much.これらのステップがPerlプログラムの実際の実行のどこで発生するかは、パターンに文字列変数の補間が含まれているかどうかによって決まります。補間が発生すると、コンパイルは実行時に行われます。補間が発生しない場合は、コンパイルはコンパイル時に実行されます。(/o修飾子はこれを変更します。qr//もある程度変更します。)エンジンはあまり気にしません。regnodes.h.このコードは、ヘッダファイルregcomp.h、regexp.h、regnodes.hと共に、主にregcomp.cに存在します。optimisation.コンパイルはpregcomp()から始まります。これは主に初期化ラッパーであり、ファームは重い処理のための他の2つのルーチンに対して作業を行います:1つ目は構文解析の開始ポイントであるreg()、2つ目は最適化を担当するstudy_chunk()です。pointer/structure.pregcomp()での初期化には、主に特殊な構造体RExC_state_t(regcomp.cで定義されています)の作成とデータ入力が含まれます。regcomp.hで内部的に使用されるほとんどすべてのルーチンは、pRExC_stateという名前のこれらの構造体の1つへのポインタを最初の引数として取ります。この構造体はコンパイル状態を保存するために使用され、多くのフィールドを含みます。同様に、この変数を操作するマクロもたくさんあります:RExC_xxxxのようなものはすべて、このポインタ/構造体を操作するマクロです。program.このパスでは、入力パターンを解析して、生成する必要がある各regopに必要なスペースを計算します。このサイズは、プログラムで長いジャンプが必要かどうかを判断するためにも使用されます。set.この段階は、設定されているマクロSIZE_ONLYによって制御されます。else.構文解析は、ほとんどのルーチンがサイズフィールドRExC_sizeを変更するために短絡され、それ以外は何も行われないことを除いて、構築フェーズ中とまったく同じように進行します。occur.プログラムのサイズが決定されると、パターンは再び解析されますが、今回は実際に解析されます。ここでSIZE_ONLYは偽となり、実際の構築が行われます。")".reg()は解析プロセスの開始です。これは、文字列の末尾まで、またはパターン内で遭遇する最初の閉じ括弧まで、任意のパターンの塊を解析します。これは、トップレベルの正規表現、またはグループ化括弧内の任意のセクションを解析するために使用できることを意味します。perlの正規表現が持つ"特殊括弧"も処理します。/x(?:foo)y/を解析する場合、reg()は"?"記号から")"記号までを解析するために一度呼び出されます。sees.さらに、reg()は、パターンからの1つまたは複数の分岐を解析し、次のポインタを正しく設定することによって「終了」させる役割を果たします。解析を実行するために、regbranch()を繰り返し呼び出します。reg()は、最初に検出したシンボルまでを処理します。reg().regbranch()は次にregpiece()を呼び出し、"things"とそれに続く数量詞を処理します。"things"を解析するために、regatom()が呼び出されます。クラス、$のような特殊記号を解析します。regatom()が"("文字に遭遇すると、reg()が呼び出されます。easier.ルーチンregtail()は、reg()とregbranch()の両方から、"tailポインタを正しく設定"するために呼び出されます。実行中に分岐の終端に到達した場合、グループ化括弧に続くノードに到達する必要があります。解析時には、そこに到達するまで終端がどこになるか分からないため、到達したときには、オフセットを適切に更新する必要があります。これを容易にするためにregtailが使用されています。form.構文解析プロセスの微妙さは、/foo/のような正規表現が、最初は単一の分岐を持つ変形に構文解析されることを意味します。オプティマイザが単一の分岐の変形をより単純な形式に変換するのは、後になってからです。this:コールグラフは次のようになります。this:文法形式は次のようになります。=>'PARSE'>>を使用して、解析プロセスに関するトレース情報を確認することができます。ここでは、いくつかの単純なパターンから始めて、より複雑なパターンを作成します。analysis./foo/を解析すると、次の表のようなものが表示されます。左側は解析対象を示し、数字は次のregopがどこに行くかを示します。右側はグラフのトレース出力です。名前は画面上で密度を低くするために短く選ばれています。'tsdy'は特別な解析を行うregtail()の特別な形式です。like:結果のプログラムは次のようになります。array.ご覧のように、私たちは枝とピースを解析しましたが、それは最終的には原子だけでした。最後のプログラムは、物事がどのように機能するかを示しています。EXACTregopがあり、その後にENDregopが続きます。括弧内の数字は、ノードのregnextの位置を示しています。ENDregopのregnextは使用されていません。ENDregopsは正常に一致したことを意味します。左側の数字はregnode配列内のregopの位置を示しています。twice.では、もっと難しいパターンを試してみましょう。数量詞を追加するので、パターン/foo+/ができました。regbranch()がregpiece()を2回呼び出すことがわかります。didn't).ここで特別なケースがあります。EXACTregopのregnextは0です。これは、EXACTregopが一致した場合には、再度自身との一致を試みる必要があるためです。6に進み、一致しなかった場合は失敗します)。string.ここでは、さまざまな最適化が実行されている、はるかに複雑なプログラムを見ることができます。10では、1文字しか含まれていない文字クラスがEXACTノードに変換された例が見られます。変更全体がTRIE-EXACTノードに変換された場所も見られます。その結果、一部のregnodesは最適化されていないとマークされています。$シンボルがEOLregop(\nまたは文字列の末尾を検索する特殊なコード)に変換されていることがわかります。failed.BRANCHへのnextポインタは、分岐が失敗した場合に実行すべき場所を指すという点で興味深いものです。実行時に、エンジンが分岐から分岐ではないregnextへトラバースしようとすると、エンジンは分岐のセット全体が失敗したことを認識します。pattern.正規表現エンジンは、使用するための重要なツールになる可能性があります。長い文字列や複雑なパターンでは、一致を見つけるために多くの作業を行う必要があり、一致が不可能であると判断するためにさらに多くの作業を行う必要があります。次のパターンのような状況を考えてみてください。b)*部分は、文字列内のすべての文字で一致し、文字列内にzが存在しないため、毎回失敗する可能性があります。したがって、文字列内にzが存在しない限り、regexエンジンの使用を回避できることは明らかです。同様に、次のようなパターンで実行します。matches.この場合、文字列にはfooが含まれ、その後にbarが続く必要があることがわかります。Boyer-Mooreマッチングを使用して、これらの文字列の場所を見つけることができます。それらが存在しない場合は、はるかに高価なregexエンジンに頼る必要はありません。それらが存在する場合は、それらの位置を使用して、regexエンジンがカバーする必要があるサーチスペースを減らし、パターン全体が一致するかどうかを判断できます。lines:最適化を促進するために使用できるパターンには、次のようなさまざまな側面があります。node.発生する可能性のある別の形態の最適化は、構文解析後の"のぞき穴"最適化であり、非効率的な構造体がより効率的な構造体に置き換えられる。構文解析中にブランチの終わりとグループの終わりをマークするために使用されるTAILregopsは、この例である。これらのregopsは構築中にプレースホルダとして使用され、"常に一致"するため、TAILを指すものをTAILが指すものを指すようにしてノードを"スキップ"することで、"最適化された"ことができる。regop.もう1つの最適化は、2つの連続するEXACTノードが1つのregopにマージされる「EXACTマージ」の最適化です。BRANCH.EXACT形式の分岐シーケンスをTRIE-EXACTregopに変換できることです。goes.これらはすべてルーチンstudy_chunk()で行われます。ルーチンCは特殊な構造体scan_data_tを使用して実行した分析を格納し、"のぞき見穴"最適化を行います。:-)study_chunk()に含まれるコードは非常に暗号化しています。interpreter.通常、regexの実行には2つのフェーズがあります。1つ目はストリング内でマッチすべき開始点を見つけるフェーズで、2つ目はregopインタープリターを実行するフェーズです。interpreter.有効な開始点がないことがわかれば、インタプリタを実行する必要はありません。同様に、解析フェーズから開始位置へのショートカットを検出できないことがわかっている場合は、そのままインタプリタに進みます。both.re_intuit_start()とpregexec()の2つのエントリポイントは、re_intuit_start()とpregexec()です。これらのルーチンは、それぞれの関数が重複するという多少近親相姦的な関係を持っています。ソルト()は単独でre_intuit_start()を呼び出すこともできます。perlソースコードの他の部分は、どちらか、または両方を呼び出すことができます。Mitchell氏の努力のおかげで、それは変わりました:内部スタックがヒープ上で維持され、ルーチンは完全に反復的になりました。これは、コードが格納する状態に関して非常に保守的であり、コード内の2つの連続した行が、シミュレートされた再帰のために、実際にはまったく異なるコンテキストで実行される可能性があるため、注意が必要になる可能性があります。Analysis"に記述されている)分析の結果によって決定された、開始点およびマッチしない最適化を処理する責任がある。whatever.このルーチンの基本的な構造は、パターンがマッチする場所の開始点および/または終了点を見つけようとし、文字列がパターンにマッチするのに十分な長さであることを確認しようとすることです。このルーチンは、効率の低いメソッドよりも効率の高いメソッドを使用しようとし、制約条件のかなりのクロスチェックを行って、マッチする文字列の場所を見つけることがあります。特定の固定文字列が存在するだけでなく、文字列の終わりの前に特定の数の文字が存在する必要があることなどを判断しようとするかもしれません。Mooreマッチングを行うfbm_instr()や、プログラム内の最初の必須regopを使ってbyclassを見つけるfind_start()など、他のいくつかのルーチンを呼び出します。match.最適化基準が満たされると、reg_try()が呼び出されてマッチングが実行される。regtry().pregexec()は、regexを実行するための主要なエントリポイントです。これには、regexインタプリタの状態を初期化するサポート、必要に応じてre_intuit_start()を実行するサポート、および必要に応じて文字列のさまざまな開始位置からインタプリタを実行するサポートが含まれています。regexインタプリタを使用する必要がある場合、pregexec()はregtry()を呼び出します。regmatch().regtry()は、regexインタプリタへのエントリポイントです。引数として、regmatch_info構造体へのポインタと文字列へのポインタを受け取ります。成功した場合は整数1を返し、失敗した場合は0を返します。基本的にはregmatch()の設定ラッパーです。code.regmatchはインタプリタの主要な"再帰ループ"です。これは基本的にステートマシンを実装する巨大なswitch文であり、可能な状態はregops自体といくつかの中間状態と障害状態です。いくつかの状態はサブルーチンとして実装されていますが、バルクはインラインコードです。discussion.)8ビット文字セットで表現できない文字を含む文字列を扱う場合、perlはUnicodeのUTF-8エンコーディング[2]の許容バージョンである内部表現を使用します。これはASCII文字セットの文字を表現するのに1バイトを使用し、他のすべての文字は2バイト以上のシーケンスを使用します(UTF-8とperlのエンコーディングutf8との関係についての詳細はperlunitutを参照してください。この説明では違いは重要ではありません)。matching).どのような見方をしても、regexエンジンではUnicodeのサポートは厄介なものになります。可能な文字が256文字ある場合にはうまくいくかもしれないトリックは、UTF-8文字セットのサイズに対応できないことがよくあります。ASCIIで当然と考えられることは、Unicodeには当てはまらない可能性があります。sizeof(char2)と仮定しても安全ですが、UTF-8ではそうではありません。szlig,E<SF>)はローカライズされた大文字小文字を区別しないマッチングでは'SS'と一致する必要があります)。wasn't.事態をさらに悪化させているのは、UTF-8サポートがregexエンジンに後から追加された(perlの場合と同じように)ため、事態は必然的に複雑になりました。明らかに、最初からUnicodeサポートを念頭に置いてregexエンジンを設計する方が、そうでないものに後から組み込むよりも簡単です。well.入力文字列を参照するほとんどすべてのregopsには、UTF-8とUTF-8以外の2つのケースがあります。実際、パターンもUTF-8である可能性があるため、これはより複雑なことがよくあります。mismatched.変更を行う際には、コンパイル時と実行時(文字列とパターンが一致しない場合を含む)の両方で、UTF-8が正しく処理されるように注意する必要があります。be:regcomp.hの次のコメントは、これがいかに厄介かを正確に示す例を示しています。regexp_internal.perlreapiで説明されているregexp構造体は、すべてのregexエンジンに共通です。この構造体の2つのフィールドは、パターンをコンパイルしたregexエンジンのプライベートな使用を目的としています。これらはintflagsメンバとpprivateメンバです。pprivateは、任意の構造体へのvoidポインタであり、その使用と管理はコンパイルエンジンの責任で行われます。perlはこれらの値を変更することはありません。ストックエンジンの場合、pprivateが指す構造体はregexp_internalと呼ばれます。engine.pprivateとintflagsフィールドには、各エンジンに固有のデータが含まれています。qr//.コンパイルされた正規表現を格納するために使用される構造体は2つあります。1つは、perlreapiに記述されているregexp構造体が現在使用されているエンジンによって取り込まれ、そのフィールドのいくつかがperlによって読み取られてqr//のstringificationのようなものを実装します。expression;もう1つの構造体は、regexp構造体のpprivateであり、正規表現をコンパイルしたregexエンジンのプロパティと考えられる同じ構造体のintflagsに追加されます。of.regexp構造体には、正規表現で適切に動作するためにperlが認識する必要があるすべてのデータが含まれています。この構造体には、perlがregexエンジンを実際に使用すべきかどうかを判断するために使用できる最適化に関するデータや、さまざまなコンテキストでパターンを適切に実行するために必要なその他のさまざまな制御情報(パターンが何らかの方法で固定されているか、コンパイル時にどのフラグが使用されたか、プログラムがperlが認識する必要がある特殊な構造体を含んでいるかどうかなど)が含まれています。values.さらに、パターンをコンパイルしたregexエンジンのプライベートな使用を目的とした2つのフィールドが含まれています。これらはintflagsメンバとpprivateメンバです。pprivateは任意の構造体へのvoidポインタで、その使用と管理はコンパイルエンジンの責任となります。perlはこれらの値を変更することはありません。implementation.先に述べたように、デフォルトエンジンの場合、pprivateはregexp_internal構造体へのポインタになります。この構造体は、コンパイルされたプログラムと、regexエンジンの実装にとってプライベートな追加データを保持します。implementations.以下の構造体は、perlのregexエンジンでpprivate構造体として使用されています。これはperlに特有のものなので、他のエンジンの実装では興味深い価値しかありません。null.swapは、regexp_paren_ofs構造体に保存されたstartp/endpの特別なセットです。これは、最後に成功したマッチが現在のパターンと同じパターンからのものであった場合に使用され、部分的なマッチが前のマッチの結果を上書きしないようにします。このフィールドにデータが入力されると、マッチングエンジンはすべてのマッチが試行される前にバッファをスワップします。マッチが失敗した場合は、バッファをスワップして戻します。マッチが成功した場合は、バッファを残します。このフィールドは要求時に設定され、デフォルトではnullです。debugger.Offsetsは、program内のoffsetからprecomp文字列内のoffsetへのマッピングを保持します。regexデバッガでのみ使用されます。optimiser.re_intuit_start()が特定の位置でパターンが一致するかどうかをチェックするために使用する特殊なregop。regexエンジンがパターンが'Z'で始まる必要があることを認識している場合、regexエンジンは文字列をスキャンして検出し、regexエンジンを起動します。これを処理するルーチンはfind_by_class()と呼ばれます。このフィールドがプログラムに組み込まれているregopを指す場合もあれば、オプティマイザによって構築された独立した合成regopを指す場合もあります。followsこのフィールドは、次のように定義されるreg_data構造体を指します。regop.この構造体は、コンパイルされた製品でのクローン操作またはフリー操作中にregexエンジンが特別に処理する必要があるデータ構造体を処理するために使用されます。データ配列内の各要素には、what配列内の対応する要素があります。コンパイル時に、特別な構造体を格納する必要があるregopsは、add_data()ルーチンを使用して各配列に要素を追加し、regopにインデックスを格納します。blob.コンパイルされたプログラム。構造体にインライン化されているため、構造体全体を1つのblobとして扱うことができます。Association。