Laut einer Seite auf code.google.com ist "linke Rekursion" wie folgt definiert:
Die linke Rekursion bezieht sich nur auf jedes rekursive Nichtterminal, das, wenn es eine sententiale Form erzeugt, die sich selbst enthält, diese neue Kopie von sich links von der Produktionsregel erscheint.
Wikipedia bietet zwei verschiedene Definitionen:
In Bezug auf die kontextfreie Grammatik ist ein nicht-terminales r linksrekursiv, wenn das Symbol ganz links in einer der Produktionen von r ('Alternativen') entweder sofort (direkt / unmittelbar linksrekursiv) oder durch ein anderes nicht-terminales Symbol ist Definitionen (indirekt / versteckt links-rekursiv) werden erneut in r umgeschrieben.
"Eine Grammatik ist linksrekursiv, wenn wir ein nicht-terminales A finden, das schließlich eine sententiale Form mit sich selbst als linkem Symbol ableitet."
Ich fange gerade erst mit der Erstellung von Sprachen an und mache das in meiner Freizeit. Wenn es jedoch darum geht, einen Sprachparser auszuwählen, ist es ein Problem, das sofort im Vordergrund steht, ob die linke Rekursion von diesem Parser oder diesem Parser unterstützt wird . Das Nachschlagen von Begriffen wie "sententiale Form" führt nur zu weiteren Jargonlisten, aber die Unterscheidung der "linken" Rekursion muss fast etwas sehr Einfaches sein. Übersetzung bitte?
quelle
::=
vonExpression
auf geändertTerm
haben und dasselbe nach dem ersten getan haben||
, ist es nicht mehr rekursiv? Aber wenn Sie es erst danach tun würden::=
, aber nicht||
, wäre es immer noch rekursiv?Expression
waren mit herausgeschaltet werdenTerm
, die beide nach::=
und nach dem ersten||
, wäre alles in Ordnung sein; denn früher oder später würde es auf etwas stoßen, das weder einNumber
noch ein istVariable
, und dadurch feststellen können, dass etwas nichtExpression
ohne weitere Ausführung ist ...Expression
, würde er möglicherweise etwas finden, das nicht ein istTerm
, und es würde einfach weiter prüfen, ob allesExpression
immer und immer wieder ist. Ist das alles?Ich werde versuchen, es in Laienbegriffe zu fassen.
Wenn Sie in Bezug auf den Analysebaum denken (nicht den AST, sondern den Besuch und die Erweiterung der Eingabe durch den Parser), führt die linke Rekursion zu einem Baum, der nach links und unten wächst. Die richtige Rekursion ist genau das Gegenteil.
Eine gängige Grammatik in einem Compiler ist beispielsweise eine Liste von Elementen. Nehmen wir eine Liste von Zeichenfolgen ("rot", "grün", "blau") und analysieren sie. Ich könnte die Grammatik auf einige Arten schreiben. Die folgenden Beispiele sind direkt links bzw. rechts rekursiv:
Die Bäume für diese Analyse:
Beachten Sie, wie es in Richtung der Rekursion wächst.
Dies ist kein wirkliches Problem, es ist in Ordnung, eine linksrekursive Grammatik schreiben zu wollen ... wenn Ihr Parser-Tool damit umgehen kann. Bottom-up-Parser kommen damit zurecht. So können modernere LL-Parser. Das Problem mit rekursiven Grammatiken ist nicht die Rekursion, sondern die Rekursion, ohne den Parser voranzutreiben, oder die Rekursion, ohne ein Token zu verbrauchen. Wenn wir beim Rekursieren immer mindestens 1 Token verbrauchen, erreichen wir schließlich das Ende der Analyse. Linke Rekursion ist definiert als Rekursion ohne Verbrauch, was eine Endlosschleife ist.
Diese Einschränkung ist lediglich ein Implementierungsdetail für die Implementierung einer Grammatik mit einem naiven Top-Down-LL-Parser (rekursiver Abstiegsparser). Wenn Sie sich an linksrekursive Grammatiken halten möchten, können Sie sich damit befassen, indem Sie die Produktion neu schreiben, um vor dem Rekursieren mindestens 1 Token zu verbrauchen. Auf diese Weise wird sichergestellt, dass wir nie in einer unproduktiven Schleife stecken bleiben. Für jede Grammatikregel, die linksrekursiv ist, können wir sie umschreiben, indem wir eine Zwischenregel hinzufügen, die die Grammatik auf nur eine Lookahead-Ebene reduziert und ein Token zwischen den rekursiven Produktionen verbraucht. (HINWEIS: Ich sage nicht, dass dies der einzige oder bevorzugte Weg ist, die Grammatik neu zu schreiben, sondern nur auf die verallgemeinerte Regel hinzuweisen. In diesem einfachen Beispiel ist die beste Option die Verwendung der rechtsrekursiven Form.) Da dieser Ansatz verallgemeinert ist, Ein Parser-Generator kann es implementieren, ohne den Programmierer (theoretisch) einzubeziehen. In der Praxis glaube ich, dass ANTLR 4 jetzt genau das tut.
Für die obige Grammatik würde die LL-Implementierung, die die linke Rekursion anzeigt, so aussehen. Der Parser würde mit der Vorhersage einer Liste beginnen ...
In Wirklichkeit haben wir es wirklich mit "naiver Implementierung" zu tun, dh. Wir haben zunächst einen bestimmten Satz vorhergesagt und dann die Funktion für diese Vorhersage rekursiv aufgerufen, und diese Funktion ruft naiv dieselbe Vorhersage erneut auf.
Bottom-up-Parser haben nicht das Problem rekursiver Regeln in beide Richtungen, da sie den Satzanfang nicht wiederholen, sondern den Satz wieder zusammensetzen.
Rekursion in einer Grammatik ist nur dann ein Problem, wenn wir von oben nach unten produzieren, dh. Unser Parser "erweitert" unsere Vorhersagen, wenn wir Token verbrauchen. Wenn wir nicht expandieren, sondern kollabieren (Produktionen werden "reduziert"), wie in einem Bottom-Up-Parser von LALR (Yacc / Bison), ist die Rekursion beider Seiten kein Problem.
quelle