LR-Parser können nicht mit mehrdeutigen Grammatikregeln umgehen. (Erleichterte die Theorie in den 1970er Jahren, als die Ideen ausgearbeitet wurden).
C und C ++ erlauben beide die folgende Anweisung:
x * y ;
Es hat zwei verschiedene Parsen:
- Dies kann die Deklaration von y als Zeiger auf den Typ x sein
- Es kann eine Multiplikation von x und y sein, die die Antwort wegwirft.
Jetzt könnte man denken, dass Letzteres dumm ist und ignoriert werden sollte. Die meisten würden dir zustimmen; Es gibt jedoch Fälle, in denen es zu Nebenwirkungen kommen kann (z. B. wenn die Multiplikation überlastet ist). aber das ist nicht der Punkt. Der Punkt ist, dass es zwei verschiedene Parses gibt, und daher kann ein Programm verschiedene Dinge bedeuten, je nachdem, wie dies hätte analysiert werden sollen.
Der Compiler muss die entsprechende unter den entsprechenden Umständen akzeptieren und in Ermangelung anderer Informationen (z. B. Kenntnis des Typs von x) beide sammeln, um später zu entscheiden, was zu tun ist. Eine Grammatik muss dies also zulassen. Und das macht die Grammatik mehrdeutig.
Somit kann das reine LR-Parsen damit nicht umgehen. Auch viele andere weit verbreitete Parser-Generatoren wie Antlr, JavaCC, YACC oder traditionelle Bison- oder sogar PEG-Parser können nicht "rein" verwendet werden.
Es gibt viele kompliziertere Fälle (die Syntax von Parsing-Vorlagen erfordert einen beliebigen Lookahead, während LALR (k) höchstens k Token vorausschauen kann), aber nur ein Gegenbeispiel ist erforderlich, um das reine LR-Parsing (oder die anderen) abzuschießen .
Die meisten echten C / C ++ - Parser behandeln dieses Beispiel, indem sie eine Art deterministischen Parser mit einem zusätzlichen Hack verwenden: Sie verflechten die Analyse mit der Symboltabellensammlung ... sodass der Parser zum Zeitpunkt des Auftretens von "x" weiß, ob x ein Typ ist oder nicht, und kann somit zwischen den beiden möglichen Parsen wählen. Ein Parser, der dies tut, ist jedoch nicht kontextfrei, und LR-Parser (die reinen usw.) sind (bestenfalls) kontextfrei.
Man kann die LR-Parser betrügen und semantische Überprüfungen der Regelverkürzungszeit pro Regel hinzufügen, um diese Disambiguierung durchzuführen. (Dieser Code ist oft nicht einfach). Die meisten anderen Parsertypen verfügen über einige Mittel, um an verschiedenen Stellen der Analyse semantische Überprüfungen hinzuzufügen, die dazu verwendet werden können.
Und wenn Sie genug schummeln, können Sie LR-Parser für C und C ++ arbeiten lassen. Die GCC-Leute haben es eine Weile gemacht, aber es für das handcodierte Parsen aufgegeben, denke ich, weil sie eine bessere Fehlerdiagnose wollten.
Es gibt jedoch einen anderen Ansatz, der nett und sauber ist und C und C ++ ohne Hacking in Symboltabellen einwandfrei analysiert: GLR-Parser . Dies sind vollständig kontextfreie Parser (mit effektiv unendlichem Lookahead). GLR-Parser akzeptieren einfach beide Parsen und erzeugen einen "Baum" (eigentlich ein gerichteter azyklischer Graph, der meistens baumartig ist), der die mehrdeutige Analyse darstellt. Ein Durchlauf nach dem Parsen kann die Mehrdeutigkeiten beheben.
Wir verwenden diese Technik in den C- und C ++ - Frontends für unser DMS Software Reengineering Tookit (ab Juni 2017 verarbeiten diese C ++ 17 in MS- und GNU-Dialekten). Sie wurden verwendet, um Millionen von Zeilen großer C- und C ++ - Systeme mit vollständigen, präzisen Analysen zu verarbeiten, die ASTs mit vollständigen Details des Quellcodes erzeugen. (Siehe AST für die ärgerlichste Analyse von C ++. )