レクサーとパーサーは、理論的には本当に違いますか?
正規表現を嫌うのはおしゃれなようです。コーディングホラー、別のブログ投稿。
ただし、人気のある字句ベースのツールであるpygments、geshi、またはprettifyはすべて正規表現を使用します。彼らは何かを語っているようです...
字句解析が十分な場合、EBNFはいつ必要ですか?
これらのレクサーによって生成されたトークンをバイソンまたはアントラーパーサージェネレーターで使用した人はいますか?
レクサーとパーサーは、理論的には本当に違いますか?
正規表現を嫌うのはおしゃれなようです。コーディングホラー、別のブログ投稿。
ただし、人気のある字句ベースのツールであるpygments、geshi、またはprettifyはすべて正規表現を使用します。彼らは何かを語っているようです...
字句解析が十分な場合、EBNFはいつ必要ですか?
これらのレクサーによって生成されたトークンをバイソンまたはアントラーパーサージェネレーターで使用した人はいますか?
回答:
パーサーとレクサーに共通するもの:
*
、==
、<=
、^
C / C ++レクサーによってトークン「演算子」として分類されます。[number][operator][number]
、[id][operator][id]
、[id][operator][number][operator][number]
C / C ++パーサによって非終端「表現」として分類されます。[TXT][TAG][TAG][TXT][TAG][TXT]...
ご覧のとおり、パーサーとトークナイザーには多くの共通点があります。1つのパーサーを他のパーサーのトークナイザーにすることができます。これは、ある言語からの文が他のより高いレベルのアルファベット記号になることができるのと同じ方法で、入力トークンを独自のアルファベットからの記号(トークンは単にアルファベットの記号です)として読み取ります。言語。たとえば、*
と-
がM
(「モールス符号記号」として)アルファベットの記号である場合、これらのドットとラインの文字列をモールス符号でエンコードされた文字として認識するパーサーを構築できます。言語「モールス符号」の文章は、可能性があり、トークン他のいくつかのパーサーの、のためにこれらのトークンその言語の原子記号です(例:「英単語」言語)。そして、これらの「英単語」は、「英語の文」言語を理解する一部の高レベルのパーサーのトークン(アルファベットの記号)になる可能性があります。そして、これらすべての言語は文法の複雑さだけが異なります。これ以上何もない。
では、これらの「チョムスキーの文法レベル」についてはどうでしょうか。そうですね、Noam Chomskyは文法をその複雑さに応じて4つのレベルに分類しました。
a
、b
)、彼らの連結は(ab
、aba
、bbb
。ETD)、または代替(例えばa|b
)。(()()(()()))
、ネストされたHTML / BBcodeタグ、ネストされたブロックなど。x+3
あり、あるコンテキストではこれx
が変数の名前になり、別のコンテキストでは関数の名前になる場合があります。STMT_END
、構文の最後の記号(パーサー用)を使用して、命令の終わりを示すことができます。これで、同じ名前のトークンが関連付けられ、レクサーによって生成されます。しかし、それが表す実際の語彙素を変更することができます。例えば。あなたが定義することができるSTMT_END
よう;
、ソースコードのように- C / C ++を持っています。またはend
、Pascalスタイルに似たものになるように定義することもできます。または'\n'
、Pythonのように、命令を行末で終了するように定義することもできます。ただし、命令(およびパーサー)の構文は変更されません:-)変更する必要があるのはレクサーのみです。
はい、それらは理論上および実装において非常に異なります。
語彙素は、言語要素を構成する「単語」を認識するために使用されます。これは、そのような単語の構造は一般に単純であるためです。正規表現はこの単純な構造の処理に非常に優れており、レクサーの実装に使用される非常に高性能な正規表現マッチングエンジンがあります。
パーサーは、言語句の「構造」を認識するために使用されます。そのような構造は一般に「正規表現」が認識できるものをはるかに超えているため、そのような構造を抽出するには「コンテキスト依存」パーサーが必要です。状況依存パーサーは構築が難しいため、技術上の妥協点は「コンテキストフリー」文法を使用し、パーサー(「シンボルテーブル」など)にハックを追加して状況依存部分を処理することです。
字句解析技術も構文解析技術もすぐになくなるとは思われません。
いわゆるスキャナーレスGLRパーサーで現在検討されているように、「解析」テクノロジーを使用して「単語」を認識することを決定することで、それらを統合できます。より一般的な機械を必要としない問題に適用することが多いため、これには実行時のコストがかかります。通常、その費用はオーバーヘッドで負担します。多くの空きサイクルがある場合、そのオーバーヘッドは問題にならない場合があります。大量のテキストを処理する場合は、オーバーヘッドが問題となり、従来の正規表現パーサーが引き続き使用されます。
字句解析が十分な場合、EBNFはいつ必要ですか?
EBNFは実際には文法の能力に多くを追加しません。これは、標準のチョムスキーの正規形(CNF)文法規則に対する、便利/ショートカット表記/ 「構文糖」にすぎません。たとえば、EBNFの代替:
S --> A | B
CNFでは、代替の各プロダクションを個別にリストするだけで達成できます。
S --> A // `S` can be `A`,
S --> B // or it can be `B`.
EBNFのオプション要素:
S --> X?
null可能なプロダクション、つまり空の文字列で置き換えることができるプロダクション(ここでは空のプロダクションのみで表されます。他のものはイプシロンまたはラムダまたは交差した円を使用します)を使用してCNFで達成できます。
S --> B // `S` can be `B`,
B --> X // and `B` can be just `X`,
B --> // or it can be empty.
B
上記の最後のような形式のプロダクションは、他のプロダクションにあるものをすべて消去できるため(他のものではなく空の文字列を生成する)、「消去」と呼ばれます。
EBNFのゼロ回以上の繰り返し:
S --> A*
再帰的なプロダクション(つまり、どこかに自分自身を埋め込むプロダクション)を使用してobtanを実行できます。それは2つの方法で行うことができます。最初のものは左再帰です(トップダウン再帰下降パーサーはそれを解析できないため、通常は回避する必要があります)。
S --> S A // `S` is just itself ended with `A` (which can be done many times),
S --> // or it can begin with empty-string, which stops the recursion.
(最終的に)空の文字列が生成され、その後に0個以上A
のsが続くことがわかっているため、同じ文字列(同じ言語ではない!)はright-recursionを使用して表現できます。
S --> A S // `S` can be `A` followed by itself (which can be done many times),
S --> // or it can be just empty-string end, which stops the recursion.
そして、+
EBNFからの1回以上の繰り返しになると、
S --> A+
これを1つに分解し、以前A
と*
同じように使用することで実行できます。
S --> A A*
これは、CNFでそのように表現できます(ここでは正しい再帰を使用しています。もう1つは練習として自分で考えてみてください)。
S --> A S // `S` can be one `A` followed by `S` (which stands for more `A`s),
S --> A // or it could be just one single `A`.
これを知っていれば、正規表現の文法(つまり、正規文法)は、終端記号のみから構成される単一のEBNFプロダクションで表現できるものとして認識できるはずです。より一般的には、次のようなプロダクションを見ると、通常の文法を認識できます。
A --> // Empty (nullable) production (AKA erasure).
B --> x // Single terminal symbol.
C --> y D // Simple state change from `C` to `D` when seeing input `y`.
E --> F z // Simple state change from `E` to `F` when seeing input `z`.
G --> G u // Left recursion.
H --> v H // Right recursion.
つまり、空の文字列、終端記号、置換と状態変更のための単純な非終端記号のみを使用し、再帰を使用して繰り返しを実現します(反復は単なる線形再帰であり、ツリーのように分岐しません)。これらよりも高度なものは何もないので、通常の構文であり、レクサーだけでそれを実行できます。
ただし、構文で自明でない方法で再帰を使用すると、次のようなツリーのような自己相似のネストされた構造が生成されます。
S --> a S b // `S` can be itself "parenthesized" by `a` and `b` on both sides.
S --> // or it could be (ultimately) empty, which ends recursion.
そうすれば、これを正規表現では実行できないことを簡単に確認できます。これは、それを単一のEBNFプロダクションに解決することができないためです。最終的にはS
無期限に代用されることになり、常に両側にa
とが追加さb
れます。レクサーは(具体的に:有限状態オートマトンは、レクサーで使用される)は、それらがどのように多くを知らないので、任意の数(?彼らは有限で、覚えておいてください)に数えることができないa
ので、多くので均等にそれらを一致させるためにあったのb
秒。このような文法は(少なくとも)文脈自由文法と呼ばれ、パーサーが必要です。
文脈自由文法は解析がよく知られているため、プログラミング言語の構文を記述するために広く使用されています。しかし、それだけではありません。同時に、より一般的な文法が必要な場合があります-同時に、独立して数えることがもっと多い場合。たとえば、丸かっこと角かっこを交互に使用できる言語を記述したいが、相互に正しくペアにする必要がある場合(中かっこで中かっこ、丸いかで丸める)。この種の文法は文脈依存と呼ばれます。左(矢印の前)に複数の記号があることでそれを認識できます。例えば:
A R B --> A S B
左側にあるこれらの追加のシンボルは、ルールを適用するための「コンテキスト」と考えることができます。いくつかの前提条件があるかもしれません、など事後条件は、例えば、上記のルールが置き換えられますR
にS
、それが間にいたときだけA
とB
それらを残し、A
そしてB
変わらずに自分自身を。この種の構文は本格的なチューリングマシンが必要なため、解析が非常に困難です。まったく別の話なので、ここで終わります。
質問されたとおりに質問に答えるため(他の答えに現れるものを過度に繰り返さずに)
受け入れられた回答で示唆されているように、レクサーとパーサーはそれほど違いはありません。どちらも単純な言語形式に基づいています。レクサー用の通常の言語と、ほとんどの場合、パーサー用のコンテキストフリー(CF)言語です。どちらも、かなり単純な計算モデルである有限状態オートマトンとプッシュダウンスタックオートマトンに関連付けられています。通常の言語はコンテキストフリー言語の特殊なケースであるため、やや複雑なCFテクノロジーを使用してレクサーを作成できます。しかし、少なくとも2つの理由から、それは良い考えではありません。
プログラミングの基本的なポイントは、システムコンポーネントが最も適切なテクノロジを備えている必要があるということです。これにより、作成、理解、および保守が容易になります。技術は過剰にすべきではなく(必要以上に複雑で費用がかかる技術を使用して)、その能力の限界にあるべきではないため、望ましい目標を達成するために技術的なゆがみが必要です。
それが「正規表現を嫌うのが流行りそう」な理由です。彼らは多くのことを行うことができますが、それを実現するために非常に読みにくいコーディングが必要な場合があります。実装のさまざまな拡張機能や制限により、理論上の単純さが多少低下することは言うまでもありません。レクサーは通常これを行わず、トークンを解析するためのシンプルで効率的な適切なテクノロジーです。トークンにCFパーサーを使用することは可能ですが、やり過ぎです。
レクサーにCF形式を使用しないもう1つの理由は、CFの全機能を使いたくなる可能性があるためです。しかし、それはプログラムの読みに関して構造上の問題を引き起こすかもしれません。
基本的に、意味が抽出されるプログラムテキストの構造のほとんどはツリー構造です。構文規則から構文解析文(プログラム)がどのように生成されるかを表します。セマンティクスは、構文規則を構成して構文解析ツリーを構築する方法から、構成手法(数学指向の同型)によって導出されます。したがって、ツリー構造は不可欠です。トークンが通常のセットベースのレクサーで識別されるという事実は、状況を変更しません。これは、通常で構成されたCFがCFを与えるためです(私は、文字のストリームをトークンのストリームに変換する通常のトランスデューサーについて大まかに話します)。
ただし、CFで構成されたCF(CFトランスデューサーを介して...計算は申し訳ありません)は、必ずしもCFを与えるわけではなく、物事をより一般的にする可能性がありますが、実際には扱いにくくなります。したがって、CFは使用できますが、レクサーには適切なツールではありません。
通常の言語とCFの主な違いの1つは、通常の言語(およびトランスデューサー)がさまざまな方法でほとんどすべての形式で非常にうまく構成するのに対し、CF言語(およびトランスデューサー)はそれ自体ではなく(いくつかの例外を除き)構成しません。
(通常のトランスデューサーには、一部の構文エラー処理手法の形式化など、他の用途がある場合があることに注意してください。)
BNFは、CF文法を表示するための特定の構文にすぎません。
EBNFはBNFの構文シュガーであり、通常の表記法の機能を使用して、BNF文法のより簡潔なバージョンを提供します。常に同等の純粋なBNFに変換できます。
ただし、通常の表記法はEBNFで使用されることが多く、字句要素の構造に対応する構文のこれらの部分を強調するためにのみ使用され、レクサーで認識される必要があります。しかし、それは絶対的なルールではありません。
要約すると、(プログラム構文の)言語のツリー指向の構造はCF文法でより適切に処理されますが、トークンのより単純な構造は、通常の言語のより単純なテクノロジーでより適切に分析されます。
しかし、これは疑問を残します:なぜ木?
ツリーは構文を指定するための優れた基盤です。
彼らはテキストに単純な構造を与えます
上に示したように、数学的によく理解されているテクノロジー(準同型による構成)を使用して、その構造に基づいてセマンティクスをテキストに関連付けるのに非常に便利です。これは、数学的形式のセマンティクスを定義するための基本的な代数ツールです。
したがって、抽象構文木(AST)の成功が示すように、これは優れた中間表現です。多くの専門家(LLやLRなど)が使用する解析技術はCF文法のサブセットにのみ適用されるため、ASTは解析ツリーとは異なる場合が多いことに注意してください。これは、任意のCF文法を受け入れる(動的プログラミングに基づく)より一般的な構文解析テクノロジで回避できます。
プログラミング言語がCFではなく状況依存(CS)であるという事実についての陳述は恣意的であり、議論の余地があります。
問題は、構文とセマンティクスの分離が任意であることです。宣言または型の一致のチェックは、構文の一部またはセマンティクスの一部と見なされる場合があります。自然言語での性別と数の合意についても同じことが言えます。しかし、複数の一致が単語の実際の意味論的意味に依存する自然言語があり、そのため、構文にうまく適合しません。
表示セマンティクスでのプログラミング言語の多くの定義では、セマンティクスに宣言と型チェックが配置されています。Ira Baxterによって行われたように述べていますれたように、構文に必要な状況依存性を取得するためにCFパーサーがハッキングされているは、せいぜい状況の恣意的な見方です。一部のコンパイラではハックとして構成されている場合がありますが、そうである必要はありません。
また、CSパーサーが(ここで他の回答で使用されている意味で)構築が難しく、効率が悪いだけではありません。それらはまた、必要とされるかもしれない状況依存性の種類をはっきりと表現するには不十分です。また、プログラムのセマンティクスを導き出す、つまりコンパイルされたコードを生成するのに便利な構文構造(構文解析ツリーなど)を自然には生成しません。
コンパイラーの分析部分が通常、字句分析フェーズと構文解析(構文分析)フェーズに分けられる理由はいくつかあります。
resource___ コンパイラ(第2版)-Alfred V. Aboコロンビア大学モニカS.ラムスタンフォード大学Ravi Sethi Avaya Jeffrey D. Ullmanスタンフォード大学