web-dev-qa-db-ja.com

レクサーとパーサー

レクサーとパーサーの理論は本当に違いますか?

正規表現を嫌うのは流行のようです: coding horroranother blog post

ただし、人気のある字句解析ベースのツール: pygmentsgeshi 、または prettify 、すべて正規表現を使用します。彼らは何でもレックスに見える...

いつ字句解析が十分で、いつEBNFが必要ですか?

誰かがこれらのレクサーによって生成されたトークンをbisonまたはantlrパーサージェネレーターで使用しましたか?

291
Naveen

パーサーとレクサーの共通点:

  1. 入力からsymbolsの一部alphabetを読み取ります。

    • ヒント:アルファベットは必ずしも文字である必要はありません。ただし、パーサー/レクサーが理解できる言語では、atomicであるシンボルである必要があります。
    • レキサーのシンボル:ASCII文字。
    • パーサーのシンボル:特定のトークン。文法の終端シンボルです。
  2. これらはシンボルを分析し、理解した言語のgrammarと一致させようとします。

    • 本当の違いは通常ここにあります。詳細は以下をご覧ください。
    • 字句解析者が理解する文法:通常の文法(Chomskyのレベル3)。
    • パーサーが理解する文法:文脈自由文法(Chomskyのレベル2)。
  3. 彼らは見つけた言語の断片にsemantics(意味)を付けます。

    • レクサーは、特定のtokensとしてlexemes(入力からのシンボルの文字列)を分類することにより意味を付加します。例えば。これらすべての語彙:*==<=^は、C/C++ lexerによって「演算子」トークンとして分類されます。
    • パーサーは、特定のnonterminalsとして入力(文)からのトークンの文字列を分類し、parse treeを構築することにより、意味を付加します。例えば。これらのすべてのトークン文字列:[number][operator][number][id][operator][id][id][operator][number][operator][number]は、C/C++パーサーによって「式」非終端記号として分類されます。
  4. 認識された要素に追加の意味(データ)を付加できます。

    • レクサーは、適切な数字を構成する文字シーケンスを認識すると、それをバイナリ値に変換し、「数字」トークンで保存できます。
    • 同様に、パーサーは式を認識すると、その値を計算し、構文ツリーの「式」ノードで保存できます。
  5. それらはすべて、出力で適切なsentencesが認識する言語を生成します。

    • レクサーはtokensを生成します。これは通常の言語sentencesです。各トークンは内部構文を持つことができます(レベル2ではなくレベル3ですが)が、出力データとそれらを読み取るデータには関係ありません。
    • パーサーは構文ツリーを生成します。これはsentencesの表現であり、context-free languageは認識します。通常、ドキュメント/ソースファイル全体が適切なであるため、ドキュメント/ソースファイル全体の大きなツリーは1つだけです。しかし、パーサーが出力で一連の構文ツリーを生成できなかった理由はありません。例えば。プレーンテキストに貼り付けられたSGMLタグを認識するパーサーである可能性があります。 tokenize SGMLドキュメントを一連のトークン[TXT][TAG][TAG][TXT][TAG][TXT]...に変換します。

ご覧のとおり、パーサーとトークナイザーには多くの共通点があります。 1つのパーサーは他のパーサーのトークナイザーになることができます。これは、ある言語の文が他の高レベルのアルファベット記号になるのと同じ方法で、入力トークンを独自のアルファベット(トークンは単にアルファベットの記号)からのシンボルとして読み取ります言語。たとえば、*-がアルファベットのシンボルM(「モールス符号シンボル」として)である場合、これらのドットと行の文字列をモールス符号でエンコードされた文字として認識するパーサーを構築できます。言語「モールスコード」の文は、他のパーサーではtokensである可能性があり、これらのtokensはその言語のアトミックシンボルです(例:「英語の単語」言語)。そして、これらの「英語の単語」は、「英語の文章」言語を理解するより高いレベルのパーサーのトークン(アルファベットの記号)である可能性があります。 これらの言語はすべて、文法の複雑さのみが異なります。これ以上何もない。

では、これらの「Chomskyの文法レベル」とは何ですか?ノーム・チョムスキーは、文法をその複雑さに応じて4つのレベルに分類しました。

  • レベル3:通常の文法

    正規表現を使用します。つまり、アルファベット(ab)の記号、連結(ababaのみで構成できます。 、bbb etd。)、または代替(例a|b)。
    NFA(非決定性有限オートマトン)またはより優れたDFA(決定性有限オートマトン)などの有限状態オートマトン(FSA)として実装できます。
    通常の文法は、ネストされた構文では処理できません。適切にネストされた/一致した括弧(()()(()()))、ネストされたHTML/BBcodeタグ、ネストされたブロックなど。それを処理する状態オートマトンは、無限に多くのネストレベルを処理するために無限に多くの状態を持たなければならないからです。
  • レベル2:文脈自由文法

    これらは、構文ツリーにネストされた再帰的な自己相似ブランチを持つことができるため、ネストされた構造をうまく処理できます。
    スタックを使用した状態オートマトンとして実装できます。このスタックは、構文のネストレベルを表すために使用されます。実際には、それらは通常、マシンのプロシージャコールスタックを使用してネストレベルを追跡し、構文内の非終端記号ごとに再帰的に呼び出されるプロシージャ/関数を使用する、トップダウンの再帰下降パーサーとして実装されます。
    しかし、context-sensitive構文では処理できません。例えば。式x+3があり、あるコンテキストではこのxが変数の名前になり、別のコンテキストでは関数の名前になります。
  • レベル1:文脈依存文法

  • レベル0:無制限の文法
    再帰的に列挙可能な文法とも呼ばれます。

455
SasQ

はい、それらは理論と実装が大きく異なります。

レクサーは、言語要素を構成する「単語」を認識するために使用されます。これは、そのような単語の構造が一般に単純だからです。正規表現は、この単純な構造の処理に非常に優れており、レクサーの実装に使用される非常に高性能な正規表現照合エンジンがあります。

パーサーは、言語フレーズの「構造」を認識するために使用されます。通常、このような構造は「正規表現」が認識できる範囲をはるかに超えているため、そのような構造を抽出するには「コンテキスト依存」パーサーが必要です。コンテキスト依存パーサーは構築が難しいため、エンジニアリング上の妥協点は、「コンテキストフリー」文法を使用し、パーサーにハック(「シンボルテーブル」など)を追加して、コンテキスト依存部分を処理することです。

字句解析技術も構文解析技術もすぐになくなることはないでしょう。

いわゆるスキャナーレスGLRパーサーで現在調査されているように、「解析」テクノロジーを使用して「単語」を認識することを決定することで、これらのが統合される場合があります。多くの場合、それを必要としない問題で、通常はオーバーヘッドで支払います。多くの空きサイクルがある場合、そのオーバーヘッドは重要ではありません。大量のテキストを処理する場合、オーバーヘッドは重要であり、古典的な正規表現パーサーは引き続き使用されます。

97
Ira Baxter

質問に答える(他の回答に表示される内容を過度に繰り返すことなく)

レクサーとパーサーは、受け入れられた答えが示唆するように、それほど違いはありません。どちらも単純な言語形式に基づいています。レクサー用の通常言語と、ほとんどの場合、パーサー用のコンテキストフリー(CF)言語です。どちらも、かなり単純な計算モデル、有限状態オートマトン、プッシュダウンスタックオートマトンに関連付けられています。通常の言語はコンテキストフリー言語の特殊なケースであるため、レクサーはやや複雑なCF技術で作成できます。しかし、少なくとも2つの理由から、それは良い考えではありません

プログラミングの基本的なポイントは、システムコンポーネントが最も適切なテクノロジを備えているため、作成、理解、および保守が容易であることです。技術は、必要以上に複雑でコストのかかる技術を使いすぎたり、力の限界に達してはならないため、目的の目標を達成するために技術的なゆがみが必要です。

それが「正規表現を嫌うのが流行のように見える」理由です。彼らは多くのことを行うことができますが、それを実現するために非常に読みにくいコーディングが必要になることがあります。実装のさまざまな拡張や制限が理論上の単純さをいくらか低下させるという事実は言うまでもありませんレクサーは通常それを行わず、通常、トークンを解析するためのシンプルで効率的で適切なテクノロジーです。トークンにCFパーサーを使用するのは過剰ですが、可能ですが。

字句解析にCF形式を使用しないもう1つの理由は、CFの全機能を使用したくなるかもしれないということです。しかし、それはプログラムの読み取りに関する構造的な問題を引き起こす可能性があります。

基本的に、プログラムテキストの構造のほとんどは、そこから意味が抽出され、ツリー構造です。構文規則から構文解析文(プログラム)が生成される方法を表します。意味論は、構文規則を構成して構文解析ツリーを構築する方法から、合成手法(数学指向の準同型)によって導き出されます。したがって、ツリー構造は不可欠です。トークンが通常のセットベースのレクサーで識別されるという事実は、レギュラーで構成されたCFがまだCFを与えるため、状況を変えません(私は、文字のストリームをトークンのストリームに変換する通常のトランスデューサーについて非常に大まかに言っています)。

ただし、CFで構成されたCF(CFトランスデューサーを介して...申し訳ありませんが)、必ずしもCFを与えるわけではなく、物事をより一般的にするかもしれませんが、実際には扱いにくくなります。そのため、CFは使用できますが、レクサーに適したツールではありません。

レギュラーとCFの大きな違いの1つは、レギュラー言語(およびトランスデューサー)がさまざまな方法でほぼすべての形式と非常によく構成されているのに対し、CF言語(およびトランスデューサー)はそうではなく、いくつかの例外を除きます)

(通常のトランスデューサには、構文エラー処理手法の形式化など、他の用途がある場合があることに注意してください。)

BNFは、CF文法を提示するための単なる特定の構文です。

EBNFはBNFの構文糖であり、通常の表記の機能を使用してBNF文法のより簡潔なバージョンを提供します。常に同等の純粋なBNFに変換できます。

ただし、通常の表記は、語彙要素の構造に対応する構文のこれらの部分を強調するためだけにEBNFでよく使用され、レクサーで認識される必要があり、残りはむしろストレートBNFで表示されます。しかし、それは絶対的な規則ではありません。

要約すると、トークンのより単純な構造は、通常の言語のより単純な技術でよりよく分析される一方、(プログラム構文の)言語のツリー指向構造は、CF文法によってより適切に処理されます。

AHRの答え も見ることをお勧めします。

しかし、これは質問を開いたままにします:なぜ木ですか?

ツリーは、構文を指定するための優れた基盤です。

  • テキストに単純な構造を与えます

  • 上記のように、構造に基づいてセマンティクスをテキストに関連付け、数学的に十分に理解されている技術(準同型による合成)を使用してテキストを関連付けるのに非常に便利です。これは、数学的形式のセマンティクスを定義するための基本的な代数ツールです。

したがって、Abstract Syntax Trees(AST)の成功が示すように、これは優れた中間表現です。多くの専門家(LLやLRなど)が使用する解析技術はCF文法のサブセットにのみ適用されるため、ASTは解析ツリーとは異なることが多いため、ASTで修正される文法上の歪みを強制します。これは、CF文法を受け入れるより一般的な解析技術(動的プログラミングに基づく)で回避できます。

プログラミング言語はCFではなくコンテキスト依存(CS)であるという事実に関する声明は、arbitrary意的で議論の余地があります。

問題は、構文とセマンティクスの分離がarbitrary意的であることです。宣言または型の一致をチェックすることは、構文の一部またはセマンティクスの一部と見なされる場合があります。自然言語の性別と数の一致についても同じことが言えます。しかし、複数の一致が単語の実際の意味に依存する自然言語が存在するため、構文に適合しません。

表示的セマンティクスにおけるプログラミング言語の多くの定義は、セマンティクスに宣言と型チェックを配置します。したがって、 Ira Baxter によって行われるように述べることは、構文で必要とされるコンテキスト感度を得るためにCFパーサーがハッキングされているということは、せいぜい状況のarbitrary意的な見方です。一部のコンパイラではハックとして編成される場合がありますが、そうである必要はありません。

また、CSパーサー(ここの他の回答で使用されている意味で)を構築するのが難しく、効率が低いだけではありません。また、必要となる可能性のある状況依存性の種類をはっきりと表現するには不十分です。また、プログラムのセマンティクスを導出するのに便利な構文構造(解析ツリーなど)を自然に生成しません。つまり、コンパイルされたコードを生成します。

11
babou

コンパイラの分析部分が通常、字句解析と解析(構文解析)フェーズに分けられる理由はいくつかあります。

  1. 設計の単純さは最も重要な考慮事項です。字句解析と構文解析の分離により、多くの場合、これらのタスクの少なくとも1つを簡素化できます。たとえば、構文単位としてコメントと空白を処理する必要があるパーサーはそうなります。コメントや空白が語彙アナライザーによってすでに削除されていると想定できるものよりもかなり複雑です。新しい言語を設計している場合、字句と構文の問題を分離することで、全体的な言語設計がよりクリーンになります。
  2. コンパイラーの効率が改善されました。別の字句解析プログラムを使用すると、字句解析タスクではなく、字句解析タスクのみに役立つ特殊な手法を適用できます。さらに、入力文字を読み取るための特殊なバッファリング手法により、コンパイラの速度が大幅に向上します。
  3. コンパイラの移植性が強化されています。入力デバイス固有の特性は、字句解析器に限定できます。

resource ___Compilers(2nd Edition)written by- Alfred V. Abo Columbia University Monica S. Lam Stanford University Ravi Sethi Avaya Jeffrey D.Ullman Stanford University

6
AHR