Zunächst gibt es den Unterschied zwischen Sprache und Grammatik. Eine Sprache ist eine Menge von Zeichenfolgen. Eine Grammatik ist eine Methode zur Beschreibung einer Menge von Zeichenfolgen (man sagt oft, dass eine Grammatik die Zeichenfolgen "generiert"). Eine bestimmte Sprache kann von mehreren Grammatiken beschrieben werden.
Die bekannteste Art von Grammatik sind die produktionsbasierten. Diese wurden von Chomsky in
-
unbeschränkten Grammatiken, bei denen beliebige Elemente auf beiden Seiten der Produktionen stehen können
-
monotonen Grammatiken, bei denen die linke Seite höchstens so lang ist wie die rechte Seite
-
kontextsensitiven Grammatiken, bei denen nur ein Nichtterminal erweitert wird
-
kontextfreien Grammatiken, bei denen die linke Seite der Produktionen nur aus einem Nichtterminal besteht
-
regulären Grammatiken, bei denen die linke Seite der Produktionen nur aus einem Nichtterminal besteht und die rechte Seite der Produktion nur ein Nichtterminal als letztes Element haben darf
Monotone und kontextsensitive Grammatiken werden auch als Typ-1-Grammatiken bezeichnet. Sie sind in der Lage, dieselben Sprachen zu generieren. Sie sind weniger leistungsfähig als Typ-0-Grammatiken. Soweit ich weiß, obwohl ich Beweise gesehen habe, dass es Sprachen gibt, die eine Typ-0-Grammatik haben, aber keine Typ-1-Grammatik, kenne ich kein Beispiel.
Kontextsensitive Grammatiken werden als Typ-2-Grammatiken bezeichnet. Sie sind weniger leistungsfähig als Typ-1-Grammatiken. Das Standardbeispiel für eine Sprache, für die es keine Typ-2-Grammatik, aber eine Typ-1-Grammatik gibt, sind Zeichenfolgen, die aus jeweils gleicher Anzahl von a, b und c bestehen, wobei das a vor dem b und das b vor dem c steht.
Reguläre Grammatiken werden auch als Typ-3-Grammatiken bezeichnet. Sie sind weniger leistungsfähig als Typ-2-Grammatiken. Das Standardbeispiel für eine Sprache, für die es keine Typ-3-Grammatik, aber eine Typ-2-Grammatik gibt, sind Zeichenfolgen mit korrekt übereinstimmenden Klammern.
Die Ambiguität in Grammatiken liegt außerhalb dieser Hierarchie. Eine Grammatik ist mehrdeutig, wenn eine bestimmte Zeichenfolge auf verschiedene Weisen generiert werden kann. Es gibt eindeutige Typ-1-Grammatiken und mehrdeutige Typ-3-Grammatiken.
Dann gibt es andere Arten von Grammatiken, die nicht Teil der Chomsky-Klassifikation sind (zweistufige Grammatiken, attributbasierte Grammatiken, baumbasierte Grammatiken, ...), auch wenn sie auf Produktionen basieren. Einige davon sind sogar in der Lage, die Semantik von Programmiersprachen zu beschreiben.
Dann gibt es Parsing-Algorithmen. Diese basieren oft auf CFG und legen weitere Einschränkungen fest, um eine bessere Parsing-Geschwindigkeit zu erreichen (das Parsen eines CSG erfordert exponentielle Zeit, ein CFG erfordert kubische Zeit, gängige Algorithmen benötigen nur lineare Zeit). Diese Einschränkungen führen zu anderen Grammatikklassen.
CSG- und monotone Grammatiken sind tatsächlich von geringer Bedeutung für die Beschreibung oder Kompilierung einer Programmiersprache: Ihr Gesamtverhalten ist nicht offensichtlich und wird aus lokalen Eigenschaften synthetisiert, daher sind sie schwer zu verstehen und die Zuordnung von Semantik zu Produktionen ist problematisch, ihr Parsen ist teuer - im Allgemeinen exponentiell - und das Fehlermanagement ist schwierig. Die nicht-Chomsky-Grammatiken wurden eingeführt, um diese Probleme zu lösen.
Zurück zu C++. Der Standard beschreibt die C++-Sprache mit einer kontextfreien Grammatik, aber
-
es gibt Mehrdeutigkeiten (zum Beispiel das berühmte "vexing parse"). Ein Compiler muss die Mehrdeutigkeiten erkennen und die richtige Interpretation verwenden (z.B. C x();
ist eine Funktionsdeklaration, nicht eine Objektdefinition).
-
die Grammatik ist nicht LR(1) (eines der bekanntesten Teilgebiete von CFG, für das ein linearer Parsing-Algorithmus existiert). Andere Algorithmen (potenziell kostspieliger in Bezug auf Zeit oder Speicher) werden verwendet, entweder basierend auf einer allgemeineren Theorie oder durch Anpassung linearer Algorithmen an die C++-Regeln. Die Vereinfachung der Grammatik und die Ablehnung der inkorrekt akzeptierten Programme in der semantischen Analyse sind ebenfalls eine Möglichkeit.
-
die Entsprechung zwischen Zeichenfolgen von Zeichen und Terminalen wird geändert (hauptsächlich durch Typ- und Vorlagen-Deklarationen, die Notwendigkeit, dies bei der Vorlagendefinition zu berücksichtigen, wurde mit der Verwendung von typename
und template
für abhängige Namen gelöst). Dies wird durch Abfrage der Symboltabelle in der Lexing-Phase gelöst, so dass eine bestimmte Zeichenfolge je nach Kontext ein Terminal oder ein anderes gibt.
-
es gibt zusätzliche Einschränkungen (Notwendigkeit, einige Bezeichner zu deklarieren, Typüberprüfung, ...) die in einer mehr oder weniger formalen Variante der englischen Sprache beschrieben sind. Dies wird in der Regel als semantisch angesehen, auch wenn einige leistungsstärkere Grammatikbeschreibungen damit umgehen könnten.