私はそれを素人の言葉に入れて突き刺します。
解析ツリー(ASTではなく、パーサーの訪問と入力の拡張)の観点から考えると、左再帰は左下に成長するツリーになります。右再帰はまったく逆です。
例として、コンパイラの一般的な文法は項目のリストです。文字列のリスト(「赤」、「緑」、「青」)を取得して解析します。いくつかの方法で文法を書くことができました。次の例は、それぞれ直接左または右再帰です。
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)ボトムアップパーサーのように、展開する代わりに折りたたむ(生産が「縮小」される)場合、どちらの側の再帰も問題ではありません。
::=
からExpression
のTerm
、そしてあなたが最初の後に同じことをやっている場合||
、それはもはや左再帰的なことでしょうか?しかし、で::=
なく||
、でした後で、それがまだ左再帰だとしたらどうでしょうか?