Eine eigene Programmiersprache mit Xtext modellieren

Praxis

Anzeige

Auf Basis der Grammatik erzeugt Xtext beim Parsen der geschriebenen DSL einen Graphen in Form von Java-Objekten. Diese lassen sich nutzen, um Generatoren für beliebigen Output zu implementieren. Hierfür kommt Xtend zum Einsatz, eine ebenfalls in Xtext entwickelte Programmiersprache, die in Java kompiliert. Das folgende Listing zeigt einen Ausschnitt der Generierung der oben gezeigten POJOs in Xtend.

val attributMapping = #{ Datentyp.TEXT -> "String", Datentyp.ZAHL -> "int" }

def compile(Datendefinition datendefinition)
'''
public class «datendefinition.name»
{
«FOR attribut : datendefinition.attribute»
«generiereAttribut(attribut)»
«ENDFOR»
}
'''

def generiereAttribut(Attribut attribut)
{
val datentyp = attributMapping.get(attribut.datentyp)
val kleinerName = attribut.name.toLowerCase
'''
private «datentyp» «kleinerName»;

public «datentyp» get«attribut.name»()
{
return «kleinerName»;
}

public void set«attribut.name»(«datentyp» «kleinerName»)
{
this.«kleinerName» = «kleinerName»;
}
'''
}

Xtend unterstützt die Code-Generierung durch verschiedene Sprach-Features. Darunter fällt zum Beispiel die String-Interpolation, die in der Methode compile zu sehen ist. Innerhalb einer von ''' umschlossenen CharSequence lassen sich zwischen « und » (Guillements) beliebige Variablen und Ausdrücke verwenden. Der restliche Text wird inklusive Whitespace übernommen. Das führt dazu, dass auch der generierte Code korrekt wie dargestellt eingerückt wird.

Analog zu anderen Template-Sprachen wie ERB in Ruby on Rails oder Razor in .NET ist es möglich, Kontrollstrukturen in der String-Interpolation zu nutzen. Oben dargestellt ist eine Enhanced-For-Schleife innerhalb des Strings, um die Attribute der Datendefinition zu durchlaufen und für jedes von ihnen den Getter- und Setter-Code an dieser Stelle einzufügen.

Darüber hinaus bietet Xtend einige weitere Features, die den Code kompakt und lesbar halten:

  • Extension Methods: Fremde Typen lassen sich durch eigene Methoden um Funktionen erweitern.
  • Lambda-Ausdrücke: Anonyme Funktionen können in Parametern, Rückgabewerten und Variablen gespeichert werden.
  • Type Inference: Entwickler können die redundante Angabe von Typen zum Beispiel bei der Variablendeklaration vermeiden.
  • Operator-Überladung: Operatoren lassen sich für eigene Typen definieren.
  • Multiple Dispatch: echte Polymorphie bei Methodenaufrufen.

In der folgenden Abbildung ist die Entwicklung der Code-Generatoren mit Xtend in Eclipse zu betrachten. Im Editor-Fenster ist auch der sogenannte Greyspace zu sehen. Die String-Interpolation von Xtend stellt Whitespace im später generierten Code bereits während der Entwicklung dar. Im rechten Fenster erkennt man den Java-Code, in den Xtend kompiliert. Um eine lauffähige Eclipse-Umgebung mit dem geladenen Plug-in für die DSL zu starten, gibt es eine vordefinierte Run-Konfiguration.

Entwicklungsumgebung für Xtext und Xtend (Eclipse) (Abb. 1)

Die zweite Abbildung zeigt die gestartete Eclipse-Umgebung mit dem geladenen DSL-Plug-in. Zu sehen sind beispielsweise die Code-Vervollständigung, Fehler- und Syntax-Highlighting sowie die Outline. Aus der DSL in der Datei Person.dm generiert das Plug-in bei jedem Speichern automatisch die Java-Klasse in Person.java im Ordner src-gen.

Entwicklungsumgebung der fertigen DSL (Eclipse mit erzeugtem Plug-in) (Abb. 2)

Es gibt prominente Beispiele, die durch oder als eine DSL umgesetzt sind. Unter anderem sind Inform7, eine Sprache zur Gestaltung interaktiver Textadventures, Gradle, ein Build-Tool mit einer eigenen DSL, und e(fx)clipse zur Unterstützung der Entwicklung von JavaFX innerhalb von Eclipse mit Xtext entwickelt.

Bei der Alten Oldenburger Krankenversicherung AG, bei der die beiden Autoren beschäftigt sind, wurden innerhalb der letzten zwei Jahre mehrere DSLs erfolgreich mit Xtext umgesetzt. Einige Beispiele werden im Folgenden kurz beschrieben.

  • Routing: Das Routing des gesamten Posteingangs im Dokumentenmanagementsystem wird anhand von Regeln in einer DSL beschrieben, die C#-Code generiert.
  • Domänenmodell: Auf Basis einer DSL für das Domänenmodell ähnlich der oben beschriebenen werden Artefakte für verschiedene Plattformen generiert (z. B. Java-Klassen, XML-Schemas, Adapter-Code für ein Legacy-System).
  • Formeln: Komplette Programme für versicherungsmathematische Berechnungen im Legacy-System werden über eine an die mathematische Syntax angelehnte DSL generiert.
  • Datenbank: Die gesamte Datenbankzugriffsschicht im Legacy-System wird vollständig auf Basis einer DSL erzeugt.
Anzeige