Es scheint, dass GCC und LLVM-Clang handgeschriebene Parser für rekursiven Abstieg verwenden und keine maschinengenerierte, Bison-Flex-basierte Bottom-up-Analyse.
Könnte hier jemand bitte bestätigen, dass dies der Fall ist? Und wenn ja, warum verwenden Mainstream-Compiler-Frameworks handgeschriebene Parser?
Antworten:
Ja:
GCC verwendete einst einen Yacc (Bison) -Parser, der jedoch irgendwann in der 3.x-Reihe durch einen handgeschriebenen Parser für rekursiven Abstieg ersetzt wurde: siehe http://gcc.gnu.org/wiki/New_C_Parser für Links zu relevanten Patch-Einsendungen.
Clang verwendet auch einen handgeschriebenen Parser für rekursiven Abstieg: siehe Abschnitt "Ein einzelner einheitlicher Parser für C, Objective C, C ++ und Objective C ++" am Ende von http://clang.llvm.org/features.html .
quelle
foo * bar;
könnte parsen entweder als ein Multiplikationsausdruck (mit dem Ergebnis , nicht verwendet), oder eine Erklärung einer Variablenbar
mit Typ pointer-tofoo
. Welches richtig ist, hängt davon ab, ob eintypedef
forfoo
zu diesem Zeitpunkt im Geltungsbereich ist, was nicht mit einer beliebigen Menge an Lookahead bestimmt werden kann. Dies bedeutet jedoch nur, dass der Parser für rekursiven Abstieg einige hässliche zusätzliche Maschinen benötigt, um dies zu handhaben.Es gibt einen Folk-Satz, der besagt, dass C schwer zu analysieren ist und C ++ im Wesentlichen unmöglich ist.
Es ist nicht wahr.
Was wahr ist, ist, dass C und C ++ mit LALR (1) -Parsern ziemlich schwer zu analysieren sind, ohne die Parsing-Maschinerie zu hacken und sich in Symboltabellendaten zu verwickeln. GCC hat sie tatsächlich mit YACC und zusätzlichem Hackery wie diesem analysiert, und ja, es war hässlich. Jetzt verwendet GCC handgeschriebene Parser, aber immer noch mit der Symboltabelle Hackery. Die Clang-Leute haben nie versucht, automatisierte Parser-Generatoren zu verwenden. AFAIK the Clang Parser war schon immer eine handcodierte rekursive Abstammung.
Was stimmt, ist, dass C und C ++ mit einfacheren automatisch generierten Parsern, z. B. GLR-Parsern , relativ einfach zu analysieren sind und Sie keine Hacks benötigen. Der Elsa C ++ - Parser ist ein Beispiel dafür. Unser C ++ - Frontend ist ein anderes (wie alle unsere "Compiler" -Frontends ist GLR eine wunderbare Parsing-Technologie).
Unser C ++ - Frontend ist nicht so schnell wie das von GCC und sicherlich langsamer als Elsa. Wir haben wenig Energie in die sorgfältige Optimierung gesteckt, da wir andere dringlichere Probleme haben (dennoch wurde es in Millionen von Zeilen C ++ - Code verwendet). Elsa ist wahrscheinlich langsamer als GCC, nur weil es allgemeiner ist. Angesichts der heutigen Prozessorgeschwindigkeit sind diese Unterschiede in der Praxis möglicherweise nicht sehr wichtig.
Aber die heute weit verbreiteten "echten Compiler" haben ihre Wurzeln in Compilern von vor 10 oder 20 Jahren oder mehr. Ineffizienzen waren dann viel wichtiger, und niemand hatte von GLR-Parsern gehört, also taten die Leute, was sie zu tun wussten. Clang ist sicherlich jünger, aber dann behalten Volkstheoreme ihre "Überzeugungskraft" für lange Zeit.
Sie müssen es nicht mehr so machen. Sie können GLR und andere solche Parser sehr vernünftig als Frontends verwenden, um die Wartbarkeit des Compilers zu verbessern.
Was ist wahr, das ist eine Grammatik erhalten , die Ihre freundliche Nachbarschaft Compiler Verhalten entspricht hart ist. Während praktisch alle C ++ - Compiler (die meisten) des ursprünglichen Standards implementieren, haben sie auch viele Erweiterungen für dunkle Ecken, z. B. DLL-Spezifikationen in MS-Compilern usw. Wenn Sie eine starke Parsing-Engine haben, können Sie Ihre Zeit damit verbringen, diese zu erhalten Die endgültige Grammatik, die der Realität entspricht, anstatt zu versuchen, Ihre Grammatik so zu biegen, dass sie den Einschränkungen Ihres Parser-Generators entspricht.
BEARBEITEN November 2012: Seit wir diese Antwort geschrieben haben, haben wir unser C ++ - Frontend verbessert, um volles C ++ 11 zu verarbeiten, einschließlich ANSI-, GNU- und MS-Dialektvarianten. Obwohl es viele zusätzliche Dinge gab, müssen wir unsere Parsing-Engine nicht ändern. Wir haben gerade die Grammatikregeln überarbeitet. Wir mussten die semantische Analyse ändern; C ++ 11 ist semantisch sehr kompliziert, und diese Arbeit überflutet den Aufwand, den Parser zum Laufen zu bringen.
BEARBEITEN Februar 2015: ... behandelt jetzt volles C ++ 14. (Siehe GLS-Analyse eines einfachen Code-Bits und C ++ 's berüchtigte "ärgerlichste Analyse" unter " Vom Menschen lesbaren AST aus C ++ - Code abrufen").
BEARBEITEN April 2017: Behandelt jetzt (Entwurf) C ++ 17.
quelle
foo * bar;
Mehrdeutigkeit um?Clangs Parser ist ein handgeschriebener Parser mit rekursivem Abstieg, ebenso wie mehrere andere Open-Source- und kommerzielle C- und C ++ - Frontends.
Clang verwendet aus mehreren Gründen einen Parser für rekursiven Abstieg:
Insgesamt spielt es für einen C ++ - Compiler keine Rolle: Der Parsing-Teil von C ++ ist nicht trivial, aber immer noch einer der einfacheren Teile. Es lohnt sich also, ihn einfach zu halten. Die semantische Analyse - insbesondere die Suche nach Namen, die Initialisierung, die Überlastungsauflösung und die Instanziierung von Vorlagen - ist um Größenordnungen komplizierter als das Parsen. Wenn Sie einen Beweis wünschen, überprüfen Sie die Verteilung von Code und Commits in Clangs "Sema" -Komponente (für die semantische Analyse) im Vergleich zu ihrer "Parse" -Komponente (für die Analyse).
quelle
Der Parser von gcc ist handgeschrieben. . Ich vermute dasselbe für Klirren. Dies hat wahrscheinlich einige Gründe:
Dies ist wahrscheinlich kein Fall von "hier nicht erfunden" -Syndrom, sondern eher im Sinne von "Es wurde nichts speziell für das optimiert, was wir brauchten, also haben wir unser eigenes geschrieben".
quelle
Seltsame Antworten da!
C / C ++ - Grammatiken sind nicht kontextfrei. Sie sind aufgrund der Foo * -Leiste kontextsensitiv. Mehrdeutigkeit. Wir müssen eine Liste von Typedefs erstellen, um zu wissen, ob Foo ein Typ ist oder nicht.
Ira Baxter: Ich verstehe den Punkt mit Ihrer GLR-Sache nicht. Warum einen Analysebaum erstellen, der Mehrdeutigkeiten enthält? Parsen bedeutet, Mehrdeutigkeiten zu lösen und den Syntaxbaum zu erstellen. Sie lösen diese Unklarheiten in einem zweiten Durchgang, sodass dies nicht weniger hässlich ist. Für mich ist es viel hässlicher ...
Yacc ist ein LR (1) -Parsergenerator (oder LALR (1)), kann jedoch leicht so geändert werden, dass er kontextsensitiv ist. Und da ist nichts Hässliches drin. Yacc / Bison wurde entwickelt, um das Parsen der C-Sprache zu erleichtern. Daher ist es wahrscheinlich nicht das hässlichste Tool, um einen C-Parser zu generieren ...
Bis GCC 3.x wird der C-Parser von yacc / bison generiert, wobei die typedefs-Tabelle während des Parsens erstellt wird. Mit der Tabellenerstellung "in parse" typedefs wird die C-Grammatik lokal kontextfrei und darüber hinaus "lokal LR (1)".
In Gcc 4.x handelt es sich nun um einen Parser für rekursiven Abstieg. Es ist genau der gleiche Parser wie in Gcc 3.x, es ist immer noch LR (1) und hat die gleichen Grammatikregeln. Der Unterschied besteht darin, dass der yacc-Parser von Hand neu geschrieben wurde, die Verschiebung / Reduzierung jetzt im Aufrufstapel versteckt ist und es kein "state454: if (nextsym == '(') goto state398" wie in gcc 3.x yacc's gibt Parser, so ist es einfacher zu patchen, Fehler zu behandeln und schönere Nachrichten zu drucken und einige der nächsten Kompilierungsschritte während des Parsens auszuführen. Zum Preis von viel weniger "leicht lesbarem" Code für einen gcc noob.
Warum wechselten sie von Yacc zu rekursivem Abstieg? Weil es durchaus notwendig ist, yacc zu vermeiden, um C ++ zu analysieren, und weil GCC davon träumt, ein mehrsprachiger Compiler zu sein, dh maximal Code zwischen den verschiedenen Sprachen zu teilen, die es kompilieren kann. Aus diesem Grund werden C ++ und C-Parser auf dieselbe Weise geschrieben.
C ++ ist schwieriger zu analysieren als C, da es nicht "lokal" LR (1) als C ist, sondern nicht einmal LR (k). Schauen Sie sich an,
func<4 > 2>
welche Vorlagenfunktion mit 4> 2 instanziiert ist, d. H.func<4 > 2>
wie gelesen werden mussfunc<1>
. Dies ist definitiv nicht LR (1). Nun überlegen Sie ,func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>
. Hier kann ein rekursiver Abstieg Mehrdeutigkeiten leicht lösen, und zwar zum Preis einiger weiterer Funktionsaufrufe (parse_template_parameter ist die mehrdeutige Parserfunktion. Wenn parse_template_parameter (17tokens) fehlgeschlagen ist, versuchen Sie es erneut mit parse_template_parameter (15tokens), parse_template_parameter (13tokens) ... bis Es klappt).Ich weiß nicht, warum es nicht möglich wäre, rekursive Subgrammatiken zu yacc / bison hinzuzufügen. Vielleicht ist dies der nächste Schritt in der Entwicklung von gcc / GNU-Parsern?
quelle
Insbesondere Bison kann meiner Meinung nach nicht mit der Grammatik umgehen, ohne einige Dinge mehrdeutig zu analysieren und später einen zweiten Durchgang durchzuführen.
Ich weiß, dass Haskell's Happy monadische (dh zustandsabhängige) Parser zulässt, die das spezielle Problem mit der C-Syntax lösen können, aber ich kenne keine C-Parser-Generatoren, die eine vom Benutzer bereitgestellte Statusmonade zulassen.
Theoretisch wäre die Fehlerbehebung ein Punkt für einen handgeschriebenen Parser, aber meine Erfahrung mit GCC / Clang hat gezeigt, dass die Fehlermeldungen nicht besonders gut sind.
In Bezug auf die Leistung scheinen einige der Behauptungen unbegründet zu sein. Das Generieren einer großen Zustandsmaschine mit einem Parser-Generator sollte zu etwas führen,
O(n)
und ich bezweifle , dass das Parsen der Engpass bei vielen Werkzeugen ist.quelle