Ich bin mit der Tatsache vertraut, dass die Grammatiken von C und C ++ kontextsensitiv sind , und insbesondere benötigen Sie einen "Lexer-Hack" in C. Andererseits habe ich den Eindruck, dass Sie nur Java analysieren können 2 Zeichen Vorausschau, trotz erheblicher Ähnlichkeit zwischen den beiden Sprachen.
Was müssten Sie an C ändern, um das Parsen einfacher zu machen?
Ich frage, weil alle Beispiele, die ich für Cs Kontextsensitivität gesehen habe, technisch zulässig, aber schrecklich seltsam sind. Beispielsweise,
foo (a);
könnte die void-Funktion foo
mit Argument aufrufen a
. Oder es könnte deklariert a
werden, ein Objekt vom Typ zu sein foo
, aber Sie könnten genauso gut die Klammern loswerden. Zum Teil tritt diese Verrücktheit auf, weil die Produktionsregel "direkter Deklarator" für die C-Grammatik den doppelten Zweck erfüllt, sowohl Funktionen als auch Variablen zu deklarieren.
Andererseits hat die Java-Grammatik separate Produktionsregeln für die Variablendeklaration und die Funktionsdeklaration. Wenn du schreibst
foo a;
Dann wissen Sie, dass es sich um eine Variablendeklaration handelt, foo
die eindeutig als Typname analysiert werden kann. Dies ist möglicherweise kein gültiger Code, wenn die Klasse foo
nicht irgendwo im aktuellen Bereich definiert wurde. Dies ist jedoch ein Job für die semantische Analyse, der in einem späteren Compiler-Durchlauf ausgeführt werden kann.
Ich habe gesehen, dass C aufgrund von typedef schwer zu analysieren ist, aber Sie können Ihre eigenen Typen auch in Java deklarieren. Welche C-Grammatikregeln direct_declarator
sind dabei schuld?
Antworten:
Das Parsen von C ++ wird schwierig. Das Parsen von Java wird genauso schwierig.
In dieser SO-Antwort wird erläutert, warum C (und C ++) "schwer" zu analysieren sind . Die kurze Zusammenfassung ist, dass C- und C ++ - Grammatiken von Natur aus mehrdeutig sind. Sie geben Ihnen mehrere Analysen und Sie müssen den Kontext verwenden, um die Mehrdeutigkeiten aufzulösen. Die Leute machen dann den Fehler anzunehmen, dass Sie beim Analysieren Unklarheiten auflösen müssen. nicht so, siehe unten. Wenn Sie beim Parsen darauf bestehen, Mehrdeutigkeiten zu lösen, wird Ihr Parser komplizierter und umso schwieriger zu erstellen. Aber diese Komplexität ist eine selbstverschuldete Wunde.
IIRC, Java 1.4s "offensichtliche" LALR (1) -Grammatik war nicht mehrdeutig, daher war es "einfach" zu analysieren. Ich bin mir nicht so sicher, ob das moderne Java nicht zumindest lokale Unklarheiten über große Entfernungen aufweist. Es besteht immer das Problem zu entscheiden, ob "... >>" zwei Vorlagen schließt oder ein "Rechtsschichtoperator" ist. Ich vermute, dass modernes Java nicht mehr mit LALR (1) analysiert wird .
Man kann das Parsing-Problem jedoch überwinden, indem man für beide Sprachen starke Parser (oder schwache Parser und Hacks für die Kontextsammlung, wie dies derzeit in C- und C ++ - Frontends meistens der Fall ist) verwendet. C und C ++ haben die zusätzliche Komplikation, einen Präprozessor zu haben; Diese sind in der Praxis komplizierter als sie aussehen. Eine Behauptung ist, dass die C- und C ++ - Parser so hart sind, dass sie von Hand geschrieben werden müssen. Es ist nicht wahr; Mit GLR-Parser-Generatoren können Sie problemlos Java- und C ++ - Parser erstellen.
Aber das Parsen ist nicht wirklich das Problem.
Sobald Sie analysiert haben, möchten Sie etwas mit dem AST / Analysebaum tun. In der Praxis müssen Sie für jeden Bezeichner wissen, wie er definiert ist und wo er verwendet wird ("Namens- und Typauflösung", schlampig, Symboltabellen erstellen). Dies stellt sich als viel mehr Arbeit heraus, als den Parser richtig zu machen, zusammengesetzt aus Vererbung, Schnittstellen, Überladung und Vorlagen, und die Tatsache, dass die Semantik für all dies in informeller natürlicher Sprache geschrieben ist, die sich über zehn bis Hunderte von Seiten erstreckt des Sprachstandards. C ++ ist hier wirklich schlecht. Java 7 und 8 werden aus dieser Sicht ziemlich schrecklich. (Und Symboltabellen sind nicht alles, was Sie brauchen; siehe meine Biografie für einen längeren Aufsatz über "Leben nach dem Parsen").
Die meisten Leute haben Probleme mit dem reinen Parsing-Teil (oft nie fertig; überprüfen Sie SO selbst auf die vielen, vielen Fragen, wie man funktionierende Parser für echte Sprachen erstellt), so dass sie das Leben nach dem Parsen nie sehen. Und dann bekommen wir Volkstheoreme darüber, was schwer zu analysieren ist, und kein Signal darüber, was nach dieser Phase passiert.
Das Korrigieren der C ++ - Syntax bringt Sie nicht weiter.
In Bezug auf das Ändern der C ++ - Syntax: Sie werden feststellen, dass Sie viele Stellen patchen müssen, um die Vielfalt lokaler und realer Mehrdeutigkeiten in jeder C ++ - Grammatik zu berücksichtigen. Wenn Sie darauf bestehen, könnte die folgende Liste ein guter Ausgangspunkt sein . Ich behaupte, es macht keinen Sinn, dies zu tun, wenn Sie nicht das C ++ - Standardkomitee sind. Wenn Sie dies tun und einen Compiler damit erstellen würden, würde es niemand vernünftig verwenden. Es wird zu viel in vorhandene C ++ - Anwendungen investiert, um für die Benutzer, die Parser erstellen, zu wechseln. Außerdem sind ihre Schmerzen vorbei und vorhandene Parser funktionieren einwandfrei.
Möglicherweise möchten Sie Ihren eigenen Parser schreiben. OK das passt; Erwarten Sie nur nicht, dass der Rest der Community Sie die Sprache ändern lässt, die sie verwenden müssen, um es Ihnen einfacher zu machen. Sie alle möchten, dass es ihnen leichter fällt, und das heißt, die Sprache so zu verwenden, wie sie dokumentiert und implementiert ist.
quelle