web-dev-qa-db-ja.com

素人の言葉で言えば、左再帰とは何ですか?

Code.google.comの 1ページ によると、「左再帰」は次のように定義されています。

左再帰は、再帰的な非終端記号を指します。それ自体が含まれている形式のフォームを生成すると、それ自体の新しいコピーがプロダクションルールの左側に表示されます。

Wikipedia には2つの異なる定義があります。

  1. 文脈自由文法の観点から、非終端rは、rの生成(「代替」)の左端のシンボルが即時(直接/即時左再帰)またはその他の非終端記号を介しての場合、左再帰的です定義(間接的/非表示の左再帰)は再びrに書き換えられます。

  2. 「文法は、最終的にそれ自体を左の記号として持つ形容詞形を導出する非終端記号Aを見つけることができれば、左再帰的です。」

私はここで言語の作成をやっと始めたばかりで、余暇にそれを行っています。ただし、言語パーサーを選択することになると、このパーサーで左再帰がサポートされているかどうか、またはそのパーサーがすぐに前面と中央に現れるという問題です。 "sentential form"のような用語を検索しても、専門用語の詳細なリストが表示されるだけですが、 "左"再帰の区別は非常に単純なものでなければなりません。翻訳をお願いします?

12
Panzercrisis

ルールRが左再帰的である場合、Rが一致するかどうかを確認するために、最初にRが一致するかどうかを確認する必要があります。これは、Rが直接または間接的に、それ自体の一部のプロダクションの最初の項として現れるときに発生します。

算数式の文法のおもちゃバージョンを想像してください。注意散漫を回避するために、加算と乗算のみを行います。

Expression ::= Multiplication '+' Expression
            || Multiplication

Multiplication ::= Term '*' Term
                 || Term

Term ::= Number | Variable

書かれているように、ここには左再帰はありません—この文法を再帰下降パーサーに渡すことができます。

しかし、次のように書こうとしたとします。

Expression ::= Expression '*' Expression
            || Expression '+' Expression
            || Term

Term ::= Number | Variable

これは文法であり、一部のパーサーはそれを処理できますが、再帰降下パーサーとLLパーサーはできません。これは、ExpressionのルールがExpression自体で始まるためです。再帰下降パーサーでこれが実際に入力を消費することなく無制限の再帰につながる理由は明らかです。

ルールが直接または間接にそれ自体を参照するかどうかは関係ありません。 ABで始まる代替があり、BAで始まる代替がある場合、ABは両方とも間接的に左再帰的であり、再帰的下降パーサーではそれらの一致する関数は無限の相互再帰を引き起こします。

21
hobbs

私はそれを素人の言葉に入れることを突き刺します。

構文解析ツリー(ASTではなく、パーサーのアクセスと入力の拡張)の観点から考えると、左再帰は、左下方向に成長するツリーになります。正しい再帰は正反対です。

例として、コンパイラの一般的な文法は項目のリストです。文字列( "red"、 "green"、 "blue")のリストを取得して解析します。いくつかの方法で文法を書くことができました。次の例は、それぞれ直接左または右再帰です。

arg_list:                           arg_list:
      STRING                              STRING
    | arg_list ',' STRING               | STRING ',' arg_list 

これらの解析のための木:

         (arg_list)                       (arg_list)
          /      \                         /      \
      (arg_list)  BLUE                  RED     (arg_list)
       /       \                                 /      \
   (arg_list) GREEN                          GREEN    (arg_list)
    /                                                  /
 RED                                                BLUE

それが再帰の方向にどのように成長するかに注意してください。

これは実際には問題ではありません。パーサーツールで処理できる場合は、左再帰文法を記述してもかまいません。ボトムアップパーサーは問題なく処理します。したがって、より現代的なLLパーサーを使用できます。再帰的な文法の問題は再帰ではなく、パーサーを進めない再帰、またはトークンを消費せずに再帰することです。再帰時に常に少なくとも1つのトークンを消費する場合、最終的に解析の最後に到達します。左再帰は、消費せずに再帰する、つまり無限ループとして定義されます。

この制限は、純粋なナイーブトップダウンLLパーサー(再帰降下パーサー)で文法を実装することの純粋な実装の詳細です。左再帰の文法を使い続ける場合は、再帰する前に少なくとも1つのトークンを消費するようにプロダクションを書き直すことで対処できるため、非生産的なループに陥ることはありません。左再帰的な文法規則の場合は、文法を1レベルの先読みに平坦化する中間規則を追加して、再帰的な生成の間でトークンを消費することにより、それを書き直すことができます。 (注:一般化されたルールを指摘するだけで、これが文法を書き換える唯一の方法または推奨される方法であるとは言っていません。この単純な例では、最適なオプションは、右再帰形式を使用することです)。このアプローチは一般化されているため、パーサージェネレーターはプログラマーを関与させることなく(理論的には)実装できます。実際には、ANTLR 4はまさにそれを行うと私は信じています。

上記の文法の場合、左再帰を表示するLL実装は次のようになります。パーサーはリストの予測から始まります...

bool match_list()
{
    if(lookahead-predicts-something-besides-comma) {
       match_STRING();
    } else if(lookahead-is-comma) {
       match_list();   // left-recursion, infinite loop/stack overflow
       match(',');
       match_STRING();
    } else {
       throw new ParseException();
    }
}

実際には、私たちが実際に扱っているのは「単純な実装」、つまりです。最初に特定の文を述語し、次にその予測の関数を再帰的に呼び出し、その関数は同じ予測を単純に再度呼び出しました。

ボトムアップパーサーは、どちらの方向にも再帰ルールの問題はありません。なぜなら、文の先頭を再解析しないため、文を元に戻すことによって機能します。

文法の再帰は、トップダウンから作成する場合にのみ問題になります。パーサーは、トークンを消費するときに予測を「拡張」することで機能します。 LALR(Yacc/Bison)のボトムアップパーサーのように、拡張するのではなく折りたたむ(プロダクションは「削減される」)場合、どちら側の再帰も問題になりません。

4
codenheim