Warum ist die Linksrekursion schlecht?

20

Warum sollte im Compiler-Design die Linksrekursion in Grammatiken beseitigt werden? Ich lese, dass es daran liegt, dass es eine unendliche Rekursion verursachen kann, aber gilt das nicht auch für eine richtige rekursive Grammatik?

Raphael
quelle
2
Compiler verwenden in der Regel eine Syntaxanalyse von oben nach unten. Wenn Sie eine linke Rekursion haben, geht der Parser in eine unendliche Rekursion über. In der Rechtsrekursion kann der Parser jedoch das Präfix der Zeichenfolge sehen, die er bisher hat. Somit kann geprüft werden, ob die Ableitung "zu weit" gegangen ist. Natürlich können Sie die Rollen vertauschen und Ausdrücke von rechts interpretieren, so dass Rechtsrekursion schlecht und Linksrekursion in Ordnung ist.
Shaull
6
Linke Rekursion ist schlecht, weil in früheren Zeiten, als Computer über 16 KB RAM verfügten, der am häufigsten verwendete Parser-Generator damit nicht fertig wurde.
Andrej Bauer

Antworten:

15

Linksrekursive Grammatiken sind nicht unbedingt eine schlechte Sache. Diese Grammatiken können einfach mit einem Stapel analysiert werden, um die bereits analysierten Phrasen zu verfolgen, wie dies beim LR-Parser der Fall ist .

Denken Sie daran, dass eine linksrekursive Regel einer CF-Grammatik die folgende Form hat:G=(V,Σ,R,S)

ααβ

mit ein Element von und ein Element von . (Siehe die vollständige formale Definition für das Tupel dort ).αVβVΣ(V,Σ,R,S)

Normalerweise ist eine Folge von Terminals und Nicht-Terminals, und es gibt eine andere Regel für bei der nicht auf der rechten Seite angezeigt wird.βαα

Immer wenn der Grammatik-Parser (aus dem Lexer) ein neues Terminal empfängt, wird dieses Terminal auf den Stapel geschoben. Diese Operation wird als Verschiebung bezeichnet .

Jedes Mal, wenn die rechte Seite einer Regel mit einer Gruppe aufeinanderfolgender Elemente am oberen Rand des Stapels abgeglichen wird, wird diese Gruppe durch ein einzelnes Element ersetzt, das den neu abgeglichenen Ausdruck darstellt. Dieser Ersatz wird als Reduktion bezeichnet .

Bei richtigen rekursiven Grammatiken kann der Stapel unbegrenzt wachsen, bis eine Reduzierung eintritt, wodurch die Analysemöglichkeiten drastisch eingeschränkt werden. Bei rekursiven Links kann der Compiler jedoch früher (in der Tat so bald wie möglich) Reduzierungen generieren. Weitere Informationen finden Sie im Wikipedia-Eintrag .

didierc
quelle
Es wäre hilfreich, wenn Sie Ihre Variablen definieren würden.
Andrew S
12

Betrachten Sie diese Regel:

example : 'a' | example 'b' ;

Stellen Sie sich nun einen LL-Parser vor, der versucht, eine nicht übereinstimmende Zeichenfolge wie 'b'diese Regel zu finden. Da 'a'nicht übereinstimmt, wird versucht, eine Übereinstimmung zu erzielen example 'b'. Aber um das zu tun, muss es passen example... und genau das hat es auch versucht. Es könnte für immer stecken bleiben, um zu sehen, ob es übereinstimmen kann, weil es immer versucht, den gleichen Strom von Token mit der gleichen Regel abzugleichen.

Um dies zu verhindern, müssten Sie entweder die Verschachtelung von rechts aus analysieren (was meines Erachtens recht ungewöhnlich ist und das Problem stattdessen durch eine richtige Rekursion lösen würde), den zulässigen Verschachtelungsgrad künstlich begrenzen oder eine Übereinstimmung herstellen ein Token, bevor die Rekursion beginnt, damit es immer einen Basisfall gibt (nämlich, in dem alle Token verbraucht wurden und es immer noch keine vollständige Übereinstimmung gibt). Da eine rechtsrekursive Regel bereits die dritte Regel ausführt, besteht nicht dasselbe Problem.

cHao
quelle
3
Sie gehen blindlings davon aus, dass Parsing notwendigerweise naives Top-Down-Parsing ist.
Reinierpost
Ich zeige die Fallstricke einer ziemlich verbreiteten Parsing-Methode - ein Problem, das leicht vermieden werden kann. Es ist sicherlich möglich , die Linksrekursion zu handhaben, aber die Beibehaltung der Linksrekursion führt zu einer fast immer unnötigen Einschränkung des Parsertyps, der sie verwenden kann.
CHAO
Ja, das ist konstruktiver und nützlicher.
Reinierpost
4

(Ich weiß, dass diese Frage mittlerweile ziemlich alt ist, aber für den Fall, dass andere die gleiche Frage haben ...)

Fragen Sie im Zusammenhang mit Parsern rekursiver Abstammung? Zum Beispiel für die Grammatik expr:: = expr + term | term, warum so etwas (links rekursiv):

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

ist problematisch, aber nicht das (richtig rekursiv)?

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

Es sieht so aus, als ob sich beide Versionen von expr()call selbst nennen. Der wichtige Unterschied ist jedoch der Kontext - dh das aktuelle Token, wenn dieser rekursive Aufruf erfolgt.

Im linken rekursiven Fall expr()ruft er sich ständig mit demselben Token auf und es werden keine Fortschritte erzielt. In dem richtigen rekursiven Fall verbraucht es einen Teil der Eingabe in dem Aufruf von term()und dem PLUS-Token, bevor es den Aufruf von erreicht expr(). Zu diesem Zeitpunkt kann der rekursive Aufruf den Ausdruck aufrufen und dann beendet werden, bevor der if-Test erneut erreicht wird.

Betrachten Sie zum Beispiel das Parsen von 2 + 3 + 4. Der linke rekursive Parser ruft expr()unendlich lange auf, während er sich auf dem ersten Token befindet, während der rechte rekursive Parser "2 +" verbraucht, bevor er expr()erneut aufruft . Der zweite Aufruf expr()entspricht "3 +" und ruft expr()nur mit den 4 übrig. Die 4 stimmen mit einem Begriff überein und das Parsen wird ohne weitere Aufrufe von beendet expr().

user65808
quelle
2

Aus dem Bison-Handbuch:

"Jede Art von Sequenz kann entweder mit der linken oder der rechten Rekursion definiert werden, aber Sie sollten immer die linke Rekursion verwenden , da sie eine Sequenz aus einer beliebigen Anzahl von Elementen mit begrenztem Stapelspeicherplatz analysieren kann. Die rechte Rekursion belegt Speicherplatz auf dem Bison-Stapel in proportional zur Anzahl der Elemente in der Sequenz, da alle Elemente auf den Stapel verschoben werden müssen, bevor die Regel auch nur einmal angewendet werden kann.

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

Es hängt also vom Algorithmus des Parsers ab, aber wie in anderen Antworten angegeben, funktionieren einige Parser möglicherweise einfach nicht mit Linksrekursion

Eduardo Wada
quelle