「ダングリングエルス問題」とスキャナーレス解析は何の関係がありますか?


13

Dangling Else問題に関するWikipediaの記事のこの文は理解できません。

[Dangling Elseの問題]は、コンパイラの構築、特にスキャナーレス解析でしばしば発生する問題です。

誰かがスキャナーレス解析技術がこの問題を悪化させる可能性があることを私に説明できますか?問題は文法にあるようです-曖昧だからです-解析手法の選択ではありません。私は何が欠けていますか?


2
私が考えることができる唯一のことは、スキャナーのないパーサーはより複雑な文法を必要とし、あいまいさを解決するための発見的手法を提供することをより難しくすることです。
ジョルジオ

3
@Robert Harvey:ポイントは、この仮定が構文木に反映されなければならないということです。文法により、文字列に対して2つの異なる構文ツリーを導出できる場合、if a then if b then s1 else s2その文法はあいまいです。
ジョルジオ

1
@RobertHarveyが言語を定義する一般的な方法は、文脈自由文法に加えて、必要に応じて文法を明確にする一連の規則を使用することです。

2
すべてのスキャナーレスパーサーが同等に作成されたわけではありません。たとえば、PEGまたはGLRの場合、他の動作がぶら下がるのは常に予測可能です。
SKロジック

1
[Dangling Elseの問題]は、スキャナーなしの解析とは関係ありません。[ダングリングエルス問題]は、LR(ボトムアップ)パーサーのshift-reduce操作に関連しています。知る
ddur

回答:


6

私の最良の推測は、ウィキペディアの記事の文章は、E。Visserの仕事に対する誤解に起因しているということです。

スキャナーレスパーサーの文法(つまり、言語を文字のシーケンスのセットとして代わりにトークンのシーケンスのセットとして記述する言語)は、トークンを文字のストリングとして個別に記述しますが、多くのあいまいさがあります。E. Visser論文スキャナーレス一般化LRパーサーの曖昧性除去フィルター(*)は、あいまいさを解決するためのメカニズムをいくつか提案しています。しかし、この論文では、 "dangling else problem"という名前の正確なあいまいさがスキャナーレスパーサーに関連しているとは述べていません(また、このメカニズムはスキャナーレスパーサーに特に役立つことさえありません)。

それを解決するメカニズムを提案するという事実は、別のあいまいさ解決メカニズム(オペレーターの優先順位と優先順位)としての暗黙のステートメントではありません。ネストの結果として通常の文法に存在しますが、最長一致ルールで処理されるものは可能です)。


(*)これはおそらく、スキャナーレスパーサーに関する別の記事を参照している場合でも、スキャナーレスパーサーに関するウィキペディアの記事の基礎となる論文です。


13

問題を述べるために、Dangling Else Problemはコード構文仕様のあいまいさであり、次のifやelseの場合、他のifに属している場合、それは不明確かもしれません。

最も単純で古典的な例:

if(conditionA)
if(conditionB)
   doFoo();
else
   doBar();

それは、心によって言語仕様の詳細を知らない人には、不明であるif取得else(をし、この特定のコードスニペットは、半ダースの言語で有効ですが、それぞれに異なっ行う場合があります)。

Dangling Elseコンストラクトは、スキャナーレスパーサー実装の潜在的な問題を引き起こします。これは、パーサーがトークン化(アセンブリまたはコンパイル中の中間言語にダイジェスト)するのに十分であるとパーサーが認識するまで、一度に1文字ずつファイルストリームを丸toみする戦略であるためです。これにより、パーサーは最小限の状態を維持できます。解析されたトークンをファイルに書き込むのに十分な情報があると判断するとすぐに、そうします。それがスキャナーレスパーサーの最終目標です。高速、シンプル、軽量のコンパイル。

句読点の前後の改行と空白は無意味であると仮定すると(ほとんどのCスタイル言語のように)、このステートメントはコンパイラーに次のように表示されます。

if(conditionA)if(conditionB)doFoo();else doBar;

コンピューターに完全に解析できるので、見てみましょう。私が持っているまで、私は一度に1つのキャラクターを取得します:

if(conditionA)

ああ、私はそれが何を意味するか知っています(C#で)、それは「pushevalスタックにconditionAがあり、それがbrfalse真でなければ次のセミコロンの後のステートメントにジャンプするために呼び出す」ことを意味します。現時点ではセミコロンが表示されていないため、ここでは、この命令の後にジャンプオフセットを次のスペースに設定し、セミコロンが表示されるまでさらに命令を挿入するにつれてそのオフセットを増やします。解析を続行しています...

if(conditionB)

OK、これは同様のIL操作のペアに解析され、解析した命令の直後に実行されます。セミコロンが表示されないので、前のステートメントのジャンプオフセットを2つのコマンド(プッシュ用とブレーク用)の長さだけ増やし、探し続けます。

doFoo();

わかりました、簡単です。それが「calldoFoo」です。そして、それは私が見るセミコロンですか?まあ、それは素晴らしい、それは行の終わりです。これらの2つのコマンドの長さだけ、両方のブロックのジャンプオフセットをインクリメントし、気にしなかったことを忘れます。OK

else

... ええとああ。これは見た目ほど単純ではありません。OK、私はちょうどやっていたが、何を忘れたelse手段は、私はすでに見てきたことを、条件付きbreak文のどこかにありますので、私はそれは、そこにされて、うん...振り返ってみましょうbrfalse、私はいくつかの「conditionBは」上押した直後にそれが何であれ、スタック。OK、break次のステートメントとして無条件が必要になりました。その後に続くステートメントは間違いなく私の条件付きブレークのターゲットなので、私はそれが正しいことを確認し、入れた無条件のブレークを増やします。

doBar();

それは簡単です。「calldoBar」。そしてセミコロンがあり、ブレースは見たことがありません。だから、無条件の人breakは、それが何であれ、次のステートメントにジャンプする必要があります。


だから、私たちは何を持っています...(注:午後10時であり、ビットオフセットを16進数に変換したり、これらのコマンドで関数の完全なILシェルを埋めたりしたくないので、これは単なる擬似ILです通常はバイトオフセットがある行番号を使用します):

ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>

まあ、それは実際に正しく実行されます。ルール(ほとんどのCスタイル言語のように)がelse最も近いであるということifです。実行のネスティングに従うようにインデントされ、次のように実行されます。conditionAがfalseの場合、スニペットの残り全体がスキップされます。

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();

...しかし、外側のifステートメントに関連付けられたブレークbreak内側 の最後のステートメントにジャンプし、ステートメントif全体を超えて実行ポインターを取得するため、セレンディピティによってそうなります。これは余分な不要なジャンプであり、この例がより複雑な場合、この方法で解析およびトークン化すると機能しなくなる可能性があります。

また、言語仕様がダングリングelseが最初のものifに属していると言っており、conditionAがfalseの場合doBarが実行され、conditionAがtrueでconditionBではない場合は何も起こらない場合はどうなりますか?

if(conditionA)
    if(conditionB)
       doFoo();
else
   doBar();

パーサーは最初ifに存在したことを忘れていたため、この単純なパーサーアルゴリズムは正しいコードを生成せず、効率的なコードは言うまでもありません。

現在、パーサーはより長い時間にわたってifsとelses を記憶するのに十分賢いかもしれませんが、言語仕様がelse2 if番目のsが最初のsに一致するifことを示している場合if、一致するelsesを持つ2つのsで問題を引き起こします:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();
else
    doBaz();

パーサーは最初を見て、最初elseに一致してから、if2番目を見て、パニック「地獄はまた何をしていた」モードに入ります。この時点で、パーサーは変更可能な状態でかなり多くのコードを取得しているため、出力ファイルストリームに既にプッシュされているはずです。

これらすべての問題とwhat-ifに対する解決策があります。しかし、そのスマートである必要があるコードはパーサーアルゴリズムの複雑さを増すか、パーサーをこの馬鹿にする言語仕様により、言語ソースコードの冗長性が増しend ifます。ifステートメントにがある場合はブロックしますelse(両方とも他の言語スタイルでよく見られます)。

これは、2つのifステートメントの単なる1つの単純な例であり、コンパイラーが下す必要があるすべての決定と、とにかく非常に簡単に台無しになる可能性がある場所を調べます。これは、あなたの質問におけるウィキペディアからの無害な声明の背後にある詳細です。


1
おもしろいですが、それがウィキペディアの記事で意図されたものであるとは確信できません。Eelco Visserのレポートを(スキャナーレスエントリを介して)参照しますが、その内容は一見すると説明と互換性がありません。
AProgrammer

3
回答いただきありがとうございますが、実際にはOPに対応していません。私は、スキャナーレスパーサーの目標が何であり、どのように実装されているかについての投稿の仮定に同意しません。スキャナーレスパーサーを実装するには多くの方法がありますが、この投稿では限られたサブセットのみを扱っているようです。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.