残された再帰がなぜ悪いのですか?


20

コンパイラの設計で、なぜ文法で再帰を残すべきなのですか?私はそれが無限再帰を引き起こす可能性があるからだと読んでいますが、正しい再帰文法にも当てはまりませんか?


2
通常、コンパイラはトップダウン解析を使用します。左再帰がある場合、パーサーは無限再帰に入ります。ただし、右再帰では、パーサーはこれまでに持っていた文字列のプレフィックスを見ることができます。したがって、派生が「遠すぎる」かどうかを確認できます。もちろん、ロールを交換し、右から式を解釈して、右再帰を悪くし、左再帰をうまく行うことができます。
シャール

6
コンピューターに16 KBのRAMがあった昔は、最も一般的に使用されていたパーサージェネレーターがそれに対処できなかったため、左再帰は不適切です。
アンドレイバウアー

回答:


15

左再帰文法は必ずしも悪いことではありません。これらの文法は、LRパーサーの場合のように、スタックを使用して簡単に解析され、既に解析されたフレーズを追跡します。

CF文法の左再帰規則 は次の形式であることを思い出してください。G=(V,Σ,R,S

ααβ

の要素Vβの要素V Σ。(タプルのための完全な正式な定義を参照してくださいV Σ R S があります)。αVβVΣ(VΣRS

通常、端末と非端末のシーケンスは、実際にある、とのために他のルールがありα αが右側に表示されませんが。βαα

文法パーサーが(レクサーから)新しい端末を受信するたびに、この端末はスタックの上にプッシュされます。この操作はshiftと呼ばれます。

ルールの右側がスタックの最上部にある連続した要素のグループと一致するたびに、このグループは新しく一致したフレーズを表す単一の要素に置き換えられます。この置換はリダクションと呼ばれます。

正しい再帰文法を使用すると、縮約が発生するまでスタックが無限に大きくなり、解析の可能性が大幅に制限される可能性があります。ただし、左の再帰的なものは、コンパイラーがより早く(実際には、できるだけ早く)削減を生成できるようにします。詳細については、ウィキペディアのエントリを参照してください。


変数を定義すると役立ちます。
アンドリューS 14年

12

このルールを考慮してください:

example : 'a' | example 'b' ;

ここで、LLパーサー'b'がこのルールのように一致しない文字列を一致させようとしていると考えてください。'a'一致しないため、一致しようとしますexample 'b'。しかし、そうするためには、一致する必要がありexampleます...それはそもそもしようとしていたことです。トークンの同じストリームを同じルールに常に一致させようとするため、一致するかどうかを確認しようとすると、永久にスタックする可能性があります。

それを防ぐためには、右から解析する必要があります(私が見た限りでは非常にまれであり、代わりに正しい再帰を行うでしょう)、入れ子の許容量を人為的に制限するか、一致する必要があります再帰が開始する前のトークン。常にベースケースがあります(つまり、すべてのトークンが消費され、完全に一致するものはまだありません)。右再帰ルールはすでに3番目のルールを実行しているため、同じ問題はありません。


3
解析は必然的に単純なトップダウン解析であると盲目的に仮定しています。
reinierpost

かなり一般的な解析方法の落とし穴を強調しています-簡単に回避できる問題です。確かに、左再帰を処理することは可能ですが、それを保持すると、それを使用できるパーサーのタイプにほぼ常に不必要な制限が生じます。
cHao

はい、それはより建設的で便利な方法です。
reinierpost

4

(私はこの質問が今ではかなり古いことを知っていますが、他の人が同じ質問を持っている場合...)

再帰降下パーサーのコンテキストで質問していますか?たとえば、文法のexpr:: = expr + term | term場合、なぜ次のようになりますか(左再帰):

// expr:: = expr + term
expr() {
   expr();
   if (token == '+') {
      getNextToken();
   }
   term();
}

問題はありますが、これはそうではありません(右再帰)。

// expr:: = term + expr
expr() {
   term();
   if (token == '+') {
      getNextToken();
      expr();
   }
}

両方のバージョンがexpr()自分自身を呼び出すようです。ただし、重要な違いはコンテキストです。つまり、再帰呼び出しが行われたときの現在のトークンです。

左の再帰的なケースでexpr()は、同じトークンを使用して自身を継続的に呼び出しますが、進行はありません。正しい再帰の場合、への呼び出しterm()に到達する前に、への呼び出しとPLUSトークンの入力の一部を消費しexpr()ます。そのため、この時点で、再帰呼び出しはtermを呼び出してから、ifテストに再度到達する前に終了します。

たとえば、2 + 3 + 4の解析を検討します。左の再帰パーサーはexpr()最初のトークンでスタック中に無限に呼び出しますが、右の再帰パーサーはexpr()再度呼び出す前に「2 +」を消費します。expr()「3 +」に一致する2番目の呼び出しと呼び出しexpr()、残り4つだけ。4は用語に一致し、解析はを呼び出さずに終了しexpr()ます。


2

Bisonマニュアルから:

「左再帰または右再帰のいずれかを使用して、あらゆる種類のシーケンスを定義できますが、スタック領域が制限されている任意の数の要素のシーケンスを解析できるため、常に左再帰を使用する必要があります。ルールを一度でも適用する前に、すべての要素をスタックにシフトする必要があるため、シーケンス内の要素数に比例します。これについての詳細は、Bison Parserアルゴリズムを参照してください。

http://www.gnu.org/software/bison/manual/html_node/Recursion.html

そのため、パーサーのアルゴリズムに依存しますが、他の回答で述べられているように、一部のパーサーは単に左再帰で動作しない場合があります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.