はじめに
こんにちは
ロシア語では、このアルゴリズムの簡単でわかりやすい説明は見つかりませんでした。 このギャップを埋めることにしました。 まず、それは何ですか? LR(0)パーサーは、主にパーサーです。 パーサーの目的は、トークンの入力ストリーム(字句アナライザーが入力文字ストリームに基づいて生成する言語の基本要素、トークンの例は数字、コンマ、文字)を処理し、それを特定の形式で指定された言語の説明と比較することです。 比較は、特定のデータ構造(ほとんどの場合ツリー)の構築で構成されます。 さらに、この構造は次の段階であるセマンティック分析に進み、コンパイラはすでにツリーの意味を理解しようとしています。
パーサーには、アップストリームとダウンストリームの2つのクラスがあります。 前者は、入力トークンである葉から始まるツリーを構築しますが、後者は、逆に、ツリーのルートから始まります。 実際、LRは、アナライザーがストリームを左から右(L-「左」)に読み取り、下から上にツリーを構築することを意味します(混乱を起こさずに右を意味する文字Rを入力します)。 インデックス0は、次のトークンをプレビューせず、現在のトークンでのみ機能することを意味します。 このタイプのアナライザーを選択する利点は何ですか?
- 彼は速いです。
- 多くの言語をカバーしています。 つまり、言語を発明して記述した場合、LRアナライザーは高い確率でその言語を処理できます。
- 構文エラーはできるだけ早く検出されます。 前の入力ストリームと一致しない文字が見つかるとすぐに、これに関するエラーを出力できます。
欠点もあります:
- 構造の相対的な複雑さ。
- 言語の記述のあいまいさによって、アナライザーを混乱状態に追い込むことができます。
用語
端末記号 (
terminal )は、ユーザーがアナライザーに与えることができる記号です。この場合、これらはトークンです。
非終端 記号 (
nonterminal 、
nonterminal )-言語のオブジェクトを示す記号。 たとえば、記号Aは用語であるとしましょう。 もちろん、複数文字の名前(Aではなくterm)を選択できます。
文脈自由文法 (
CFG )-フォームのルールのセット

ここで、Aは非端末、wは端末と非端末の任意のコレクションです。 この記事では、この文脈のない意味で、単に文法を使用します。
アナライザーを作成する文法の小さな例:

この文法は、0と1の2つの数値に対する不完全な算術演算のセットを記述しています。文法は、言語の記述です。 入力ストリームが私たちの言語に属しているか、どこかで構文エラー(1 + 1の代わりに1+を書いた)かどうかをチェックするために、開始の非終端から始まるこの規則に従って入力ストリームを取得する方法を探しています(Eがあります)。 1 + 1の場合、パスはそのようなEになり、ルール2、E + F、ルール1、F + F、ルール4、T + F、ルール8、1 + F、再び4、1 + T、最後に8番目を適用し、 1 +1。ご覧のとおり、入力文字列を取得できました。これは、構文的に正しいことを意味します。
これで、アナライザーの名前のRという文字を説明できます。 これは、ルールの極右部分から公理に移る、つまり、元の(1)を収集するより単純なルール(7および8)から進むことを意味します。 Lアナライザー(LL)は、ルールの左側の部分から次の分析方向を選択しようとします。
有限状態マシン (
FSM )についても言及する価値があります。 これは、一連の状態と入力ストリームを持つモデルです。 マシンは初期状態から起動し、現在のシンボルと入力シンボルに基づいて状態を変更します。 つまり、状態0から開始し、aが入力に入ると、オートマトンは状態1になり、bが状態2になります。遷移の仕組みは、表によって与えられます。列は現在の状態、列は入力記号です。
アルゴリズム
アナライザーを機能させるには、いくつかのことが必要です。
- 分析する入力ストリーム自体。
- パーサーステートのスタック(LIFO(Last In First Out)ルールに準拠するデータ構造)は、シートのスタックとして想像するのが最も簡単です-葉をスタックの最後に置き、スタックの要素が必要なときに最初に取得します)。
- アクションテーブル。 彼女は、現在の状態で、入力で現在のキャラクターをどうするかを教えてくれます。
- 変換表 アクションの1つで使用される補助テーブル。
ここでは、アナライザーがどのように機能するかを明確にする必要があります。 現在の状態は、スタックの一番上の状態です。 アクションのテーブルを見てください(インデックスは現在の入力シンボルと現在の状態です)。 このテーブルには4種類のレコードがあります。
- success(accepted)-入力行がこの文法に属していることを意味します。
- void(エラー)-アクションはありません。行き詰まっています。ユーザーは現在のキャラクターを間違えました。
- transfer(shift)-スタックの一番上に、入力文字に対応する状態を配置し、次を読み取ります
- リダクション(reduce)-文法規則を使用して状態を置き換えることができるスタック状態があります。ここでは、遷移テーブルの値を取得します。 最初のインデックスは現在の状態です。 2番目は、ルールの左側です。 つまり、状態のシーケンスを変換したものです。
コード形式では、次のようになります。
- スタック。 プッシュ (状態[ 0 ] ) ;
- while ( !が受け入れられます)
- {
- 状態* st =スタック。 トップ ( ) ;
- 末端項= s [ inp_pos ] ;
- if ( ! terms。IsTerm ( term ) )
- エラー( ) ;
- アクション*アクション= actionTable。 Get ( st、term ) ;
- if ( !アクション)
- エラー( ) ;
- スイッチ (アクション- >タイプ( ) )
- {
- ケース ActionAccept :
- 受け入れられた= true ;
- 休憩 ;
- case ActionShift :
- inp_pos ++ ;
- スタック。 プッシュ (アクション- >状態( ) ) ;
- 休憩 ;
- ケース ActionReduce :
- ルール*ルール=アクション- >ルール( ) ;
- スタック。 pop (ルール- >サイズ( ) ) ;
- 状態* transferState = transferTable。 Get (スタック。 トップ ( ) 、ルール- >左( ) ) ;
- if ( ! transferState )
- エラー( ) ;
- スタック。 push ( transferState ) ;
- 休憩 ;
- }
- }
ご覧のとおり、分析自体に複雑なことはありません。 ただし、全体のトリックは、これらのトリッキーなテーブルを作成することです。 まず、パーサーの状態を把握しましょう。 これは、アルゴリズムのかなり重要な部分です。 いいえ、これは単なる数字ではありません。 いくつかの新しい概念を導入する必要があります。
まず、これらはアイテムです。 これは、マーカーという新しいプロパティを持つルールです。 マーカーは、現在期待している要素を示します。 したがって、各ルールはn + 1個のトークンを生成します。nはルールの右側の文字数です。 たとえば、ルール3を使用します。円の中のプラス記号は、マーカーの場所を示します。

たとえば、2番目の段落のマーカーは、現在の文字にマイナス記号が表示されることを示しています。 複数のアイテムを組み合わせると、アイテムのセットになります。 実際には、状態は特定の方法で一緒に組み立てられたアイテムのセットです。
ただし、状態を操作するには、最初にセットを閉じる必要があります。 これは、完全なアナライザーブランチを取得することを意味します。 つまり、セット内にマーカーが非終端点を指すポイントがある場合(そして、この場合、すべての非終端点は左側の部分です)、対応する非終端点を「デプロイ」する必要があります。 これは、左部分がこの非終端であるアイテムを追加するだけで起こり、マーカーは最初の文字を指します。 単独で、再帰的に展開します。新しく追加された段落で最初の文字が非終端記号である場合は、閉じます。 フルセットを取得するまで。 ポイントが1つしかないセット(前の例では3番)を閉じます。

Fを展開すると、ポイント2、3、4が得られます。3と4では、再びFを展開するよう提案されますが、これらのルールは既にセットにあるので、スキップします。 しかし、Tがデプロイされていないため、5と6が得られます。すべて、クロージャーの準備ができました。
- for ( itemsetのclosed_item )
- {
- if ( closed_item。isClose )
- 続ける ;
- 要素マーカー= closed_item。 マーカー ( ) ;
- if (マーカー。 タイプ ( ) ! = ElementNonTerm )
- {
- closed_item。 isClose = true ;
- 続ける ;
- }
- NonTerminal nonTerm =マーカー。 NonTerm ( ) ;
- item = allitems- > First ( 0 、nonTerm ) ;
- while ( ! item。isend ( ) )
- {
- if ( ! itemset。exists ( item ) )
- アイテムセット。 追加 (アイテム) ;
- アイテム。 next ( 0 、nonTerm ) ;
- }
- closed_item。 isClose = true ;
- }
状態が何であるかを理解したら、それらの構築を開始できます。 最初に、出力の基礎であり、最後に到達する必要がある新しいルールを導入します。

もちろん、最初の状態は、このルールに基づいて、マーカーをEに向けてアイテムを閉じることです。ここで、一時的な有限状態マシンのテーブルの構築を開始します。これは、遷移テーブルとアクションテーブルの基盤として機能します。 マーカーが指すシンボルの基準に従って、状態をグループに分けます。 この例のクロージャでは、Fグループ、Tグループ、0グループ、1グループの4つのグループがあります。 各グループは、新しい状態への移行です。 遷移からの最初のインデックスは、実際にグループ化するシンボル(F、T、0、1)です。 2番目のインデックスは現在の状態です。 そして、テーブル内の値は、渡す状態です。 したがって、4つの新しい状態を取得します。 それらを構築するのは非常に簡単です-各ポイントでグループ内でond位置のマーカーを右に移動し、結果セットを閉じます。 これは新しい状態になります。
- firstState。 追加 (項目。 最初 ( ) ) ;
- firstState。 MakeClosure ( ) ;
- 状態。 add ( firstState ) ;
- size_t state_idx = 0 ;
- while ( state_idx < states。size ( ) )
- {
- 状態* st =状態[ state_idx ] ;
- GroupedItems group = st- > Group ( ) ;
- for ( groupのgroup_class )
- {
- if ( group_class- > first。 タイプ ( ) == ElementEnd )
- 続ける ;
- State newState ( & items、states。Size ( ) ) ;
- for ( group_classのgroup_item )
- newState。 追加 ( group_item、group_item。MarkerInt ( ) + 1 ) ;
- newState。 MakeClosure ( ) ;
- 状態oldState =状態。 検索 ( newState )
- if ( ! oldState )
- {
- 状態。 add ( newState ) ;
- fsmTable。 Add ( st、group_class- > first、newState ) ;
- }
- 他に
- fsmTable。 Add ( st、group_class- > first、oldState ) ;
- }
- state_idx ++ ;
- }
遷移テーブルは非常に簡単に構築されます-インデックスが非終端であるFSMテーブルから列を転送します。
アクションテーブルはもう少し興味深いものです。 また、パーツはFSMからターミナルインデックス付きの列に転送されますが、元の宇宙船テーブルに記録された状態パラメーターを使用したシフトアクションはテーブルセルに書き込まれます。 次に、入力行の終わりを示す新しい列「$」を追加します。 この列には、受け入れられたイベントを入力します。これは、インデックス状態にアイテムが含まれている場合に書き込まれます

。 これは成功を意味し、主要なルールに変わり、同時に入力ストリームが終了します。 次に、畳み込みアクションがあります。 アイテムがある各州について

、ここでwは端末と非端末の任意の組み合わせで、行全体(もちろん、他のコマンドで占有されていないフリーセルのみを意味します)のreduceコマンドと、このアイテムが属する対応するルールのパラメーターを記述します。
- fsmTable。 FeedTransferTable ( transferTable ) ;
- fsmTable。 FeedActionTable ( actionTable ) ;
- アイテムendItem =アイテム。 GetItem ( 1 、 'S' 、Elements ( "E" 、nonTerms ) ) ;
- for ( st in states )
- if ( st。HaveItem ( endItem ) )
- actionTable。 Add ( st、 '$' 、 new Action ( ) ) ;
- for ( st in states )
- {
- ItemListリスト= st。 GetReducable ( ) ;
- for (リスト内のlistItem )
- actionTable。 Add ( st、 new Action ( listItem。GetRule ( ) ) ) ;
- }
文学
コンパイラ:原則、テクニック、およびツール、1986年、アルフレッドV.アホ、ラビセティ、ジェフリーD.ウルマン