Posts mit dem Label Ecore werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Ecore werden angezeigt. Alle Posts anzeigen

Mittwoch, 26. Mai 2010

Verwendung von XText

Mit diesem Post (und den nächsten) möchte ich ein Gefühl für die Verwendung von XText vermitteln. Dabei werde ich erst einmal auf die Erstellung der Grammatik eingehen, später auf den Workflow zur Erzeugung der Artefakte und des Plugins. Dies wird eine Basis bieten, um später die Konzepte für den Aufbau des Akzeptanztestsystems zu erläutern.

Dennoch wird es hier keine vollständige Anleitung für XText geben. Die Details kann man in der User Doku zu XText nachlesen. Hier findet man alle Möglichkeiten der Grammatik von XText und Informationen zu weiteren Konzepten, welche auch hier kurz angeschnitten werden sollen.

Außerdem empfehle ich für Beispiele, aber auch spezielle Thematiken rund um XText die Blogs von Sven Efftinge und Peter Friese. Da erfährt man, wie man zum Beispiel vorhandene Ecore-Modelle in XText importiert oder wie man mithilfe von XPand aus einer DSL Code generiert, was auch mir mittlerweile sehr geholfen hat.

Erstellung der Grammatik

Zunächst einmal habe ich eine einfache Grammatik für eine DSL zur Beschreibung von Hotels erstellt. Es ist zwar sehr einfach gehalten, sollte aber deshalb auch leicht nachzuvollziehen sein. Nachdem man ein neues XText Projekt angelegt hat (siehe XText User Guide), kann man die automatisch erstellte Grammatik folgendermaßen abändern:

grammar org.xtext.example.Testdsl with org.eclipse.xtext.common.Terminals

generate testdsl "http://www.xtext.org/example/Testdsl"

Model :
(hotels+=Hotel)*
(zimmer+=Zimmer)*
(gaeste+=Gast)*;

Hotel:
'Hotel' name=ID
'Zimmer' '{' (zimmer+=[Zimmer])+ '}'
'Gaeste' '{' (gaeste+=[Gast])+ '}';

Zimmer:
Einzelzimmer | Suite;

Einzelzimmer:
'Einzel' name=ID;

Suite:
'Suite' name=ID;

Gast:
'Gast' name=ID
'Vorname' vorname=STRING
'Nachname' nachname=STRING;

Die Grammatik beginnt mit ihrem Namen und dem Einbinden der von XText bereitgestellten Terminals. Diese Terminals, wie im Beispiel zu sehen ID, STRING und weiterhin INT sowie andere sind in einer eigenen Grammatik definiert, welche man meistens in seiner Grammatik mit verwenden möchte. Nach dem selben Prinzip lassen sich auch beliebige andere Grammatiken einbinden.

Das Schlüsselwort generate stellt den Ausgangspunkt eines wichtigen Prinzips von XText dar. Hier wird der Name des aus der Grammatik zu erstellenden Ecore-Modells angegeben. In Ecore wird das durch die DSL beschriebene Modell festgehalten, woraufhin man dieses mit den Möglichkeiten des Eclipse Modeling Frameworks weiter bearbeiten kann. Es ist auch möglich, auf die Generierung zu verzichten und stattdessen ein Modell zu importieren, bzw. eine Kombination davon. Darauf wollen wir an dieser Stelle erst einmal verzichten.

Mit dem Hintergedanken an das Modell, welches schließlich erzeugt wird, kann man nun mit Hilfe der Grammatik verschiedenen Regeln beschreiben, welche das Modell beschreiben, aber auch das Aussehen der DSL. Die EBNF-ähnliche Form der Grammatik (EBNF – Erweiterte Backus-Naur-Form) kann man relativ einfach nachvollziehen.

Die Regel Hotel zeigt grundlegende Funktionen der Grammatik:

Hotel:
'Hotel' name=ID
'Zimmer' '{' (zimmer+=[Zimmer])+ '}'
'Gaeste' '{' (gaeste+=[Gast])+ '}';

Mit Anführungszeichen eingeschlossenen Wörter gelten als Schlüsselwörter in der DSL, welche fest verankert sind und immer an ihrem vorbestimmten Platz vorkommen müssen. Sind sind lediglich dazu da, der DSL eine Struktur zu geben und sie leichter lesbar zu machen. In das Modell werden sie nicht übernommen.

Eine Regel in der DSL beschreibt meist eine Klasse im Modell. Jede Klasse bekommt bestimmte Eigenschaften oder auch Attribute, welche jeweils einen bestimmten Wert haben. In XText spricht man hier auch von Referenzen, welche die einzelnen Regeln im Syntaxbaum miteinander verbinden. Hotel besitzt ein Attribut (um in der Objektwelt zu bleiben) mit dem Namen name, welchem ein bestimmter Wert zugewiesen wird. Dieser wird hier durch die Terminal-Regel ID gebildet. Beim Parsen wird später der Inhalt dieser Regel hinter dem Schlüsselwort Hotel eingefügt, wobei es sich bei ID um eine Kombination aus Ziffern und Buchstaben handeln kann. Ein anderes Beispiel sind vorname und nachname in der Regel Gast.

Möchte man eine Liste erstellen, das heißt, kann das Attribut mehr als einen Wert annehmen, so wird dabei das = durch += ersetzt. Diesen “add”-Operator kennt man aus einigen Programmiersprachen. Ein Beispiel sieht man in der obersten Regel Model:

Model :
(hotels+=Hotel)*

Hier wird zum Beispiel beschrieben, dass die Regel Hotel mehrmals hintereinander existieren kann.

Kardinalität von Elementen

XText bietet mehrere Optionen an, um die Kardinalität, sprich die Anzahl der Elemente anzugeben:

  • keine Angabe – genau ein mal
  • ? – Maximal einmal, oder nicht vorhanden (kennzeichnet Optionalität)
  • + – bei mehreren Elementen, 1 mal oder mehr
  • * – bei mehreren Elementen, 0 oder mehr

Möchte man beispielsweise name aus dem oberen Beispiel optional werden lassen, so würde man es so beschreiben:

'Hotel' (name=ID)?

Beispiele für Listen mit mindestens einem oder mindestens 0 Elementen findet man in der Beispiel-DSL mehrfach. So muss ein Hotel mindestens ein Zimmer besitzen, jedoch kann das Modell auch gar keine Hotels oder Gäste besitzen.

Cross References

Bis jetzt haben wir uns nur mit dem Zuweisen von Regeln beschäftigt. An der entsprechenden Stelle wird die Regel vom Parser eingesetzt. Man kann jedoch auch Objekte durch Regeln an einem Ort beschreiben und diese dann wieder an einem anderen Ort wiederverwenden. Ein Beispiel:

'Hotel' name=ID
'Zimmer' '{' (zimmer+=Zimmer)+ '}'

Würde man die Regel in dieser Weise ausführen, so müsste man die Zimmer direkt in die geschweiften Klammern des Hotels schreiben, also:

Hotel hotel1
Zimmer { Einzel ... }

Geht man wieder zu der oben verwendeten Schreibweise zurück, so kann man an dieser Stelle Referenzen verwenden. Diese werden dadurch gekennzeichnet, dass man die entsprechende Regel in eckige Klammern setzt. Dies bedeutet dann so viel wie: hier wird ein Objekt/ eine Instanz vom Typ X eingesetzt.

'Hotel' name=ID
'Zimmer' '{' (zimmer+=[Zimmer])+ '}'

Nun würde man vorher ein Zimmer definieren, ihm einen Namen geben und diese Referenz dann verwenden.

Suite zimmmer1

Hotel hotel1
Zimmer { zimmer1 }

Wichtig hierbei ist: um eine Referenz wieder zu finden, muss die entsprechende Regel ein Attribut mit dem Namen name besitzen, so wie es schon mehrfach in der Beispielgrammatik vorkommt. Ich denke, dies hängt direkt mit dem Ecore-Modell zusammen. Die meisten Elemente des Ecore Meta-Modells erben von einer Klasse namens ENamedElement, welche für alle Elemente ein Attribut name definiert und sie so identifizierbar macht. Alle anderen Attribute, die nicht zur Referenzierung verwendet werden, kann man natürlich beliebig benennen.

Verzweigungen / Alternativen

Oben in der Grammatik gibt es eine Verzweigung bei Zimmer, wobei die Regel Zimmer zwei Alternativen bietet: Einzelzimmer oder Suite.

Zimmer:
Einzelzimmer | Suite;

Ob diese Konstellation so sinnvoll ist oder nicht sei dahingestellt, jedoch lässt sich hier gut der Sinn solch einer Verzweigung darstellen. Sieht man das Ganze wieder im Hinblick auf das später erzeugte Modell, so erzeugt man hier eine Art der Vererbung. Zimmer ist die Superklasse und Einzelzimmer bzw. Suite sind die spezialisierten Klassen. In unserem Beispiel macht dies nicht so ganz Sinn, denn beide fügen keinerlei Spezialität hinzu sondern besitzen nur das gleiche Attribut name.

Hierbei sieht man aber eine Besonderheit im Aufbau solch einer Hierarchie. Alle gleichen Attribute, das heißt, eigentliche solche, die in die Superklasse kommen würden, müssen hier jeweils in den Regeln aller Kindklassen vorkommen. Die Regel Zimmer ist hier nur dazu da, die Verzweigung zu bestimmen. Im später erzeugten Modell werden dann alle gleichen Attribute weiter oben angesiedelt, wie man im folgenden Abschnitt sehen wird.

Eine weitere Einsatzmöglichkeit solcher Verzweigungen ist, den Ablauf der Sprache etwas beliebiger zu gestalten, sodass man beispielsweise Hotels, Zimmer und Gäste nicht in der genauen Reihenfolge beschreiben muss, sondern diese durch eine Verzweigung einer gemeinsamen Superklasse zuordnet, wodurch es möglich wird, die Reihenfolge völlig beliebig zu gestalten.

Das Modell hinter der Grammatik

Ohne momentan den Workflow zur Erstellung des Modells zu betrachten (das kommt beim nächsten Mal), möchte ich gern einmal so ein Modell vorstellen, und zwar genau das, welches durch die oben beschriebene Grammatik erstellt werden würde.

Ecore-Modell für das Beispiel

In dem Ecore-Modell wird für jede Liste eine EReference (gekennzeichnet durch den Pfeil am Symbol) angelegt, für jedes einzelne Attribut ein EAttribute. Wie man hier sieht, wird die Verzweigung bei Zimmer dadurch aufgelöst, dass hier Unterklassen gebildet werden. Alle gemeinsamen Attribute kommen dabei in die Superklasse, was in unserem Fall nur name ist.

Dies ist noch ein sehr einfaches Beispiel. Wir werden sehen, dass im Laufe der Entwicklung des AKzeptanztest-Systems ein sehr viel komplizierteres Modell herauskommen wird, vor allem da ein Modell nötig sein wird, welches nicht nur eine Domäne wie hier “Hotel”, sondern beliebig viele unterstützen muss.

Ein konkretes Beispiel zum Abschluss

Zum Schluss folgt ein konkreter Text in der oben vorgestellen DSL:

Hotel hotel1
Zimmer { zimmer1 zimmer2 }
Gaeste { gastMuster }

Einzel zimmer1
Suite zimmer2

Gast gastMuster
Vorname "Max" Nachname "Mustermann"

Hier erhält man nun einen Eindruck, was aus der oben beschriebenen DSL werden kann. Man beachte die Reihenfolge insbesondere der Schlüsselwörter und die Verwendung der Referenzen. Im Laufe der Zeit werde ich in diesem Blog immer mal wieder einzelne Details von XText vorstellen, welche zur Umsetzung des Akzeptanztest-Systems nötig sind.

Im nächsten Post habe ich vor, die Erstellung eine DSL aus der Sicht des Workflows zu betrachten, welchen man durchführen muss, um aus der Grammatik ein Modell zu machen und ein Plugin zu erzeugen, mit welchem man dann einen konkreten Text in der DSL schreiben kann.