パーサーコンビネーターを使用して、構文解析と字句解析のパスを分けるのは良い習慣ですか?


18

パーサーコンビネーターを使用し始めたとき、私の最初の反応は、構文解析と字句解析の人為的な区別のように感じたものからの解放感でした。突然すべてが解析されただけです!

しかし、私は最近、codereview.stackexchangeでこの区別を回復する誰かを示すこの投稿に出会いました。最初はこれは非常にばかげていると思いましたが、Parsecにはこの動作をサポートする機能が存在するという事実に疑問を抱きます。

パーサーコンビネーターで既にレキシシングされたストリームを解析することの利点/欠点は何ですか?


誰かが[parser-combinator]タグを追加できますか?
イーライ・フレイ

回答:


15

解析では、ほとんどの場合、文脈自由言語の分析を理解します。コンテキストのない言語は通常の言語よりも強力であるため、パーサーは(ほとんどの場合)字句解析の仕事をすぐに行うことができます。

しかし、これはa)非常に不自然b)しばしば非効率的です。

a)について、たとえばif式がどのように見えるかを考えると、IF expr THEN expr ELSE exprではなく、「i」「f」ではなく、いくつかのスペース、式が始まることができる文字などが考えられます考え。

b)には、識別子、リテラル、あらゆる種類のブラケットなど、字句エンティティを認識する優れた仕事をする強力なツールがあります。それらは、ほとんど時間をかけずに作業を行い、素敵なインターフェイスを提供します:トークンのリスト。パーサーのスペースをスキップする心配はもうありません。パーサーが文字ではなくトークンを処理する場合、パーサーはより抽象的になります。

結局のところ、パーサーが低レベルのもので忙しいと思うなら、なぜ文字を処理するのですか?ビットレベルでも書くことができます!ご存知のように、ビットレベルで動作するこのようなパーサーはほとんど理解できません。文字とトークンについても同じです。

ちょうど2セントです。


3
正確さのために:パーサーは常に字句解析の仕事をすることができます。
ジョルジオ

また、効率に関して:パーサーの効率が低い(遅い)かどうかはわかりません。結果の文法には、通常の言語を記述するサブ文法が含まれ、そのサブ文法のコードは、対応する字句解析プログラムと同じくらい高速になると予想されます。IMOの本当のポイントは(a):より単純でより抽象的なパーサーで作業することがいかに自然で直感的かということです。
ジョルジオ

@Giorgio-最初のコメントに関して:あなたは正しい。ここで私が念頭に置いたのは、字句解析器が文法を簡単にするいくつかの作業を実用的に行い、LALR(2)の代わりにLALR(1)を使用できる場合です。
インゴ

2
さらなる実験と考察の後、あなたの答えを受け入れなくなりました。あなた2人がAntlr他のすべての世界から来たのは継ぎ目です。パーサーコンビネーターのファーストクラスの性質を考慮すると、多くの場合、パーサーの解析レイヤーで各トークンを単一の名前のままにして、トークンパーサーのラッパーパーサーを定義するだけです。たとえば、ifの例は次のようになりますif = string "if" >> expr >> string "then" >> expr >> string "else" >> expr
イーライ・フレイ

1
パフォーマンスは未解決の問題であり、ベンチマークを行います。
イーライ・フレイ

8

字句解析と構文解析を分離することは「良い習慣」であると提案する皆-私は同意しなければならない-多くの場合、字句解析と構文解析を単一のパスで実行すると、より多くのパワーが得られ、パフォーマンスへの影響は、その他の回答(Packratを参照)。

このアプローチは、1つの入力ストリームにさまざまな言語を混在させる必要がある場合に役立ちます。これは、のような奇妙なメタプログラミング指向言語で必要とされるだけでなく、Katahdin同様に HTMLにJavascriptを詰め、コメントでHTMLを使用して、文芸的プログラミング(と言う、ラテックスを混合して、C ++)のように、より多くの主流のアプリケーションのためだけでなく、しかし、およびなど。


私の答えでは、「特定のコンテキストでの良い習慣」であり、「すべてのコンテキストでのより良い習慣」ではないことを示唆しました。
ジョルジオ

5

字句解析器は通常の言語を認識し、パーサーは文脈自由言語を認識します。各通常言語もコンテキストに依存しないため(いわゆる右線形文法で定義できる)、パーサーは通常言語も認識でき、パーサーとレキシカルアナライザーの区別は不要な複雑さを追加するようです:単一のコンテキスト-free grammar(パーサー)は、パーサーと字句解析の仕事をすることができます。

一方、コンテキストフリー言語の一部の要素を通常の言語(したがって、字句解析器)を介してキャプチャすると便利な場合があります。

  1. 多くの場合、これらの要素は非常に頻繁に出現するため、数値および文字列リテラル、キーワード、識別子の認識、空白のスキップなどの標準的な方法で処理できます。
  2. トークンの通常の言語を定義すると、結果の文脈自由文法がより簡単になります。たとえば、個々の文字ではなく識別子の観点で推論したり、特定の言語に関係のない空白を完全に無視したりできます。

したがって、構文解析を字句解析から分離することには、より単純なコンテキストフリーの文法で作業し、字句アナライザーでいくつかの基本的な(多くの場合ルーチン)タスクをカプセル化できるという利点があります(divide et impera)。

編集

私はパーサーコンビネータに精通していないので、上記の考慮事項がそのコンテキストでどのように適用されるかわかりません。私の印象では、パーサーコンビネーターを使用した場合、文脈自由文法は1つしかなくても、2つのレベル(字句解析/構文解析)を区別することで、この文法をよりモジュール化できます。前述のように、下の字句解析レイヤーには、識別子、リテラルなどの基本的な再利用可能なパーサーを含めることができます。


2
すべてのレクサーは正規表現エンジンに基づいているため、語彙素は自然ではなく通常の文法に分類されます。設計できる言語の表現力を制限しています。
SKロジック

1
通常の言語として記述できない語彙素を定義することが適切な言語の例を挙げていただけますか?
ジョルジオ

1
たとえば、私が作成したいくつかのドメイン固有の言語では、識別子はTeX式である可能性があります\alpha'_1 (K_0, \vec{T})。識別子です。
SKロジック

1
文脈自由文法が与えられれば、常に非終端記号Nを取り、それ自体が有用な意味を持つ単位として表現できる単語を扱うことができます(例えば、表現、用語、数、文)。これは、そのユニットの解析方法(パーサー、パーサー+レクサーなど)に関係なく実行できます。IMOパーサー+字句解析器の選択は、セマンティック(解析するソースコードのブロックの意味)よりも技術的なもの(解析の実装方法)です。たぶん私は何かを見落としているかもしれませんが、2つの側面は私に直交しているように見えます。
ジョルジオ

3
したがって、私はあなたに同意します:任意の基本的なビルディングブロック(語彙素)を定義し、それらを認識するために字句解析器を使用したい場合、これは常に可能とは限りません。これがレクサーの目標なのかと思います。私が理解している限り、字句解析器の目標はより技術的なものです。つまり、パーサーから低レベルで退屈な実装の詳細を取り除くことです。
ジョルジオ

3

簡単に言えば、字句解析と構文解析は複雑さが異なるため、分離する必要があります。LexingはDFA(決定論的有限オートマトン)であり、パーサーはPDA(プッシュダウンオートマトン)です。これは、構文解析は本質的に字句解析よりも多くのリソースを消費することを意味し、DFAのみが利用できる特定の最適化手法があります。また、有限状態マシンの記述はそれほど複雑ではなく、自動化が容易です。

lexに解析アルゴリズムを使用するのは無駄です。


パーサーを使用して字句解析を行う場合、PDAはスタックを使用せず、基本的にDFAとして機能します。入力を消費し、状態間をジャンプするだけです。100%確信はありませんが、DFAに適用できる最適化手法(状態の数を減らす)はPDAにも適用できると思います。しかし、はい:より強力なツールを使用せずに字句アナライザーを作成し、その上に単純なパーサーを作成する方が簡単です。
ジョルジオ

また、全体がより柔軟で保守可能になります。たとえば、レイアウトルールのないHaskell言語のパーサー(つまり、セミコロンとブレース)があるとします。別のレクサーがある場合、トークンに別のパスを実行し、必要に応じてブレースとセミコロンを追加するだけで、レイアウトルールを追加できます。または、より簡単な例として、識別子でのみASCII文字をサポートする言語から始めて、識別子でUnicode文字をサポートしたいとします。
インゴ

1
@Ingo、そしてなぜ別のレクサーでそれを行う必要があるのですか?それらの端末を除外するだけです。
SKロジック

1
@ SK-logic:あなたの質問を理解したかどうかわかりません。なぜ別のレクサーが良い選択かもしれないのか、私の投稿で実証しようとしました。
インゴ

ジョルジオ、いや スタックは、通常のLALRスタイルパーサーの重要なコンポーネントです。パーサーを使用して字句解析を行うと、メモリがひどく無駄になり(静的ストレージと動的に割り当てられた両方)、はるかに遅くなります。Lexer / Parserモデルは効率的です。使用してください:)
riwalk

1

個別の解析/ lexの主な利点の1つは、中間表現-トークンストリームです。これは、lex / parseの組み合わせでは不可能なさまざまな方法で処理できます。

そうは言っても、優れた 'ol再帰的まともな方法は、パーサージェネレーターを学習するよりも複雑でなく、操作しやすく、パーサージェネレーターのルール内でグラマーの弱点を表現する方法を理解する必要があることがわかりました。


構文解析時に実行されるよりも、プレハブストリームでより簡単に表現される文法について詳しく説明していただけますか?おもちゃの言語と少数のデータ形式を実装した経験しかないので、何かを見落としているかもしれません。手持ちのRDパーサー/ lexコンボとBNFフィード(想定)ジェネレーターの間にパフォーマンス特性があることに気づきましたか?
イーライ・フレイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.