Development/KDevelop-PG-Qt Introduction: Difference between revisions

From KDE TechBase
(Created page with '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-Plugi...')
 
No edit summary
Line 3: Line 3:
KDevelop-PG-Qt ist der Parser-Generator aus KDevplatform. Er wird für einige KDevelop-Sprachunterstützungs-Plugins verwendet (Ruby, PHP, Java...).
KDevelop-PG-Qt ist der Parser-Generator aus KDevplatform. Er wird für einige KDevelop-Sprachunterstützungs-Plugins verwendet (Ruby, PHP, Java...).


= Anwendung =
= Das Programm =
Das Programm KDevelop-PG-Qt findet sich im SVN-tree in /home/kde/trunk/playgroun/devtools/kdevelop-pg-qt. Dort finden sich auch drei kleine Beispiele.
== 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.
<pre>
<pre>
svn co svn://anonsvn.kde.org/home/kde/trunk/playground/devtools/
</pre>
Das Programm verlangt eine .g-Datei als Eingabe:
<pre>
./kdev-pg-qt --output=''prefix'' syntax.g
</pre>
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 ==
<pre>
--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
</pre>
--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:
<pre>
"special-command"    return Parser::Token_SPECIAL_COMMAND;
</pre>
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:
<code>
  class-declaration
| struct-declaration
| function-declaration
| union-declaration
| namespace-declaration
| typedef-declaration
| extern-declaration
-> declaration ;;
</code>
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.
<code>
  CLASS IDENTIFIER SEMICOLON
| CLASS IDENTIFIER LBRACE class-declaration* RBRACE SEMICOLON
-> class-declaration ;;
</code>
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:
<code>
  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 ;;
</code>
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:
<code>
  CLASS IDENTIFIER SEMICOLON
| CLASS IDENTIFIER LBRACE (#class-declaration=class-declaration)* RBRACE SEMICOLON
-> class-declaration ;;
</code>
Variablen können mehrfach benutzt werden:
<code>
  #one=one (#one=one)*
-> one-or-more
</code>
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:
<code>
  (#one=one)+
-> one-or-more
</code>
...entspricht genau obigem Beispiel.<br>
Ebenfalls häufig verwendet ist das '@':
<code>
  #item=item @ COMMA
-> comma-separated-list
</code>
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...

Revision as of 17:24, 19 October 2009

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...