Development/KDevelop-PG-Qt Introduction
This is a German article. There's not yet any English version!
KDevelop-PG-Qt ist der Parser-Generator aus KDevplatform. Er wird für einige KDevelop-Sprachunterstützungs-Plugins verwendet (Ruby, PHP, Java...).
Das Programm
Anwendung
Das Programm KDevelop-PG-Qt findet sich im SVN-tree http://websvn.kde.org/trunk/playgroun/devtools/kdevelop-pg-qt. Dort finden sich auch drei kleine Beispiele.
svn co svn://anonsvn.kde.org/home/kde/trunk/playground/devtools/
Das Programm verlangt eine .g-Datei als Eingabe:
./kdev-pg-qt --output=''prefix'' syntax.g
Alle Dateien werden dann mit dem angegebenen Präfix erstellt und es gibt auch den namespace für den erzeugten Code an.
Ausgabe
Das Programm gibt Informationen über sogenannte Konflikte aus und erzeugt mehrere Dateien (jeweils mit dem gewählten Präfix).
ast.h
Hier wird eine Datenstruktur definiert, in der der Parsetree gespeichert wird. Die Knoten des Baums sind alle structs mit dem Postfix AstNode, die Zeiger auf die möglichen Unterelemente enthalten.
parser.h und parser.cpp
Hier werden ein enum für die Tokens definiert und das eigentliche Parsen erledigt. Für jeden Nicht-Terminal existiert eine Funktion parseNicht_terminal_name. Die Parser-Klasse verlangt einen Tokenizer. Möchte man nun eine Token-Folge parsen, erzeugt man einen Pointer auf die Wurzel (z.B. DocumentAstNode*) und ruft parseDocument(&root) auf. Bei Erfolg wird in root der Parsetree abgelegt.
visitor.h und visitor.cpp
Diese beiden Dateien definieren eine abstrakte Basis-Klasse zur Traversierung über den Parsetree.
defaultvisitor.h und defaultvisitor.cpp
Die hier definierte Klasse ist eine Implementierung des visitors, die automatisch die Unterknoten eines Knotens besucht. Möchte man über den gesamten Parsetree traversieren, ist diese Klasse als Basisklasse gut geeignet.
CLI-Optionen
--namespace=''namespace'' - Einen Namespace unabhängig vom Datei-Präfix festlegen (dann darf das Präfix auch / enthalten) --no-ast - Die ast.h-Datei wird nicht erstellt, dazu später mehr --debug-visitor - Code zur Ausgabe des Parse-Trees wird erzeugt --help - Eine (nicht besonders aufschlussreiche) Hilfe wird angezeigt --symbols - Alle Nicht-Terminale werden in eine Datei ''kdev-pg-symbols'' geschrieben --rules - Alle Regeln werden mit Informationen zum syntaktischen Zusammenhang (hilfreich zur Konflikt-Behebun) in eine Datei namens ''kdev-pg-rules'' geschrieben
--serialize-visitor - Code zur Serialisierung über ein QIODevice wird erstellt
Tokenizer
Wie bereits kurz erwähnt erfordert KDevelop-PG-Qt einen fertigen Tokenizer. Entweder man schreibt sich diesen selbst oder man verwendet ein Tool wie Flex. Mit den Beispielen sollte es kein Problem sein, sich solch einen Tokenizer zu konstruieren. Zwischen den meisten Sprachen unterscheidet sich die Syntax von Kommentaren, Literalen etc. nicht großartig, so dass hier wenig zu tun ist. Das Hinzufügen von einfachen Tokens ist eine Triviale Angelegenheit:
"special-command" return Parser::Token_SPECIAL_COMMAND;
Das war es im wesentlichen, wenn man vorhandene Dateien als Vorlage verwendet.
Schreiben von .g-Dateien
Typ2-Grammatiken
Die verwendeten Typ2-Grammatiken werdenso aufgebaut, das sich jeweils ein Nicht-Terminal (Symbol) aus weiteren Nicht-Terminalen und Terminalen (Tokens) zusammensetzt. Bei der Grundstruktur kann man sich an die Semantik der Sprache halten, allerdings wird man immer wieder kleine Helfer benötigen. Ein C++-Dokument besteht beispielsweise aus vielen Deklarationen. Klassen-Definitionen sind ein Beispiel. Diese bestehen wiederum aus dem Token CLASS, einem Namen, dem Token {, Klassen-Element-Deklarationen, dem Token } und dem Token ;. Klassen-Element-Deklaration ist ein solcher Helfer. Kein normaler C++-Entwickler benutzt solch ein Wort, sie helfen jedoch die Struktur des Codes aufzuschlüsseln.
Grundlegende Syntax
Nun kann man einmal versuchen solche Beschreibungen in der KDevelop-PG-Qt-Syntax zu fassen:
class-declaration
| struct-declaration
| function-declaration
| union-declaration
| namespace-declaration
| typedef-declaration
| extern-declaration
-> declaration ;;
Das |-Zeichen steht für ein oder. Jede Regel endet mit zwei Semikola. Diese Unter-Deklarationen müssen nun wiederum definiert werden. Dabei werden Nicht-Terminale klein und Tokens groß geschrieben.
CLASS IDENTIFIER SEMICOLON
| CLASS IDENTIFIER LBRACE class-declaration* RBRACE SEMICOLON
-> class-declaration ;;
Ein neues Zeichen ist aufgetaucht: Der Stern bedeutet wie in regulären Ausdrücken, dass etwas 0-unendlich mal auftreten darf. Das Zeichen 0 steht für eine leere Token-Folge. Klammerung ist beliebig möglich.
Speicherung im Parsetree
Mit dieser einfachen Regel-Deklaration wird zwar die Token-Folge geparst, jedoch wird kein Parsetree gespeichert. Dies lässt sich leicht ändern:
class-declaration=class-declaration
| struct-declaration=struct-declaration
| function-declaration=function-declaration
| union-declaration=union-declaration
| namespace-declaration=namespace-declaration
| typedef-declaration=typedef-declaration
| extern-declaration=extern-declaration
-> declaration ;;
In der Klasse DeclarationAstNode werden nun Zeiger auf Class_declarationAstNode, Struct_declarationAstNode etc. angelegt. Beim Parsen werden diese Zeiger belegt. (in diesem Fall natürlich immer nur einer, die anderen werden auf NULL gesetzt)
Möchte man Wiederholungen speichern, so wird auch noch eine Listenstruktur zur Verfügung gestellt, hierfür muss dem Variablennamen eine Raute (#) vorangestellt werden:
CLASS IDENTIFIER SEMICOLON
| CLASS IDENTIFIER LBRACE (#class-declaration=class-declaration)* RBRACE SEMICOLON
-> class-declaration ;;
Variablen können mehrfach benutzt werden:
#one=one (#one=one)*
-> one-or-more
Unabhängig davon, wo die Variable #one benutzt wird: Ein Element wird an die selbe Liste angehängt.
Spezielle Zeichen...
...für Regeln
Einige weitere Zeichen erleichtern die Arbeit:
(#one=one)+
-> one-or-more
...entspricht genau obigem Beispiel.
Ebenfalls häufig verwendet ist das '@':
#item=item @ COMMA
-> comma-separated-list
Sorgt genau dafür, dass zwischen den Items ein Komma stehen muss.
Verwendet man an Stelle des Gleichheitszeichens den Doppelpunkt (:), so wird lediglich während des Parsens eine lokale Variable für den Unterbaum eingerichtet wird.
...für manuellen Code
...coming soon...
Konflikte
...coming soon...