For more information on the Xtext framework, consult Project of the Month: Xtext DSL Framework.
In a previous article, my colleague Sebastian Zarnekow discussed how to use Xtext to create a DSL without too much effort. Once you've honed the DSL syntax to your satisfaction, no doubt you'll want to do something useful with it for example, execute it with an interpreter or compile it with a code generator. This article focuses on the code generation approach. Giving DSL instances semantics by mapping them onto an existing language, framework, or library is an ideal way to reuse an established technology stack. Coding that becomes tedious, boiler-plate repetition, and begs for the definition of a concise notation that expresses intent at a higher-level of abstraction. Code generation makes this stratighforward.
Xtend, part of the Eclipse Xtext project released with Eclipse 2.7 (Indigo) in June 2011, is a statically typed language focused on code generation. It is tightly integrated with and bound to Java (not only does it support reuse of any existing Java classes, it also compiles down to Java source code). It's a good example of a DSL that exploits code generation. In case you have already been working with Xtext, you might find it interesting that Xtend has been implemented with Xtext and on top of Xbase. The final article in this series will explain in detail how Xtext languages can benefit from Xbase.
A First Glance at Xtend
Figure 1 shows a small Xtend file on the left, along with the Java code that is generated from it on the right. In Xtend, a class is compiled to a public Java class, a def is compiled to Java method, and a multiline string is compiled to multiple Java statements that append contents to a StringConcatenation object. To run an Xtend template, simply invoke the Java methods in the generated code. There is no need to generate the Java code from the Xtend files manually, It is done in the background by an incremental builder in Eclipse; so each time you modify and save an Xtend file, the corresponding Java code is automatically produced.
For your convenience the code in the left and right panes, respectively, is shown below.
Left pane:
import org.example.domainmodel.domainmodel.DataType
import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.*
class DomainmodelGenerator implements IGenerator {
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
for(type : resource.allContentsIterable().filter(typeof(DataType)))
fsa.generateFile(type.name + ".java", type.contents)
}
def getContents(DataType type) '''
public class <<type.name>> {
}
'''
Right pane:
/*
* generated by Xtext
*/
package org.example.domainmodel.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
class DomainmodelGenerator implements IGenerator {
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
//TODO implement me
}
}
Generators Within Xtext
We will now implement a simple code generator for the DSL introduced in the previous article. In case you don't already have an Xtext project for this in your workspace,
- Follow the steps in the "Getting Started" section.
- Copy and paste the complete grammar from section "Start the Entity Editor."
- Select file "GenerateDomainmodel.mwe2" and trigger
"Run As" -> "MWE2" from the context menu.
After this, open the file DomainmodelGenerator.xtend in your project.
So far, Xtend looks very similar to Java. Some differences to note in this code snippet are that terminating semicolons are optional and Java's @Override annotation is a keyword. Note that IGenerator is a plain Java interface provided by Xtext as a simple way to plug in code generators.
Step 1: A Basic Template
To start, we'll implement the method doGenerate(). This example collects all DataType instances from the parsed DSL model and generates a Java class for each. If you look at the code, you will again notice several differences when compared with Java.
Type Inference
Xtend can infer the types of variables, methods, closures, and so on. This significantly reduces notational noise. In the example we see type inference for the variable type in the for-loop and for the return type of getContents().
Access Getters and Setters as Properties
If an object has a getter named object.getFoo(), you can access that value using object.foo. If it has a setter named object.setBar(String value), you can assign a value to object.bar. In essence, you can use foo like a Java field. This is appropriate given that it represents an abstraction over a field. In the example, type.name is translated to type.getName() and type.contents is translated to type.getContents(). Because they support assignment, setters need to have exactly one parameter. One would assume, too, that getters must have no parameters, which is true for the most cases, but not extensions methods, which are explained next.
Extension Methods
Xtend extension methods are methods declared outside that object that can be invoked on objects as if they were member methods. An extension method must accept the object as its first parameter. This design allows for the use of the Method Chaining pattern for types that don't provide all the desired APIs and are beyond your control. Method chaining can make your code more concise and more readable. Extension methods don't have a special declaration syntax. Whether a static method can be used as an extension method is determined by whether its first parameter type is compatible with the receiver (the object to which the method is applied). Extension methods can come from three sources:
import static extension: The imported methods must be static. This implies that it is implemented in Java, as Xtend doesn't support the declaration of static methods. In the example, the methodgetAllContentsIterable()is imported as a static extension from the Java classResourceExtensions.- member methods: All member methods of an Xtend class can implicitly be used as extension methods. In the example,
type.contentsis equivalent tothis.getContents(type). - injected extensions: Xtend supports member variables annotated with
@Inject. At runtime, an instance is injected by Google Guice. With the syntax@Inject extension MyType myObject, the nonstatic methods frommyObjectare available as extension methods.
Multiline Strings
The example template generates its output using multiline-strings. They are easy to recognize: the strings are enclosed by three single-quotes ('''). Within multiline strings, expressions and statements for flow control can be embedded within guillemots (<< >>). In the example, this is how the value of type.name is inserted into the string. Statements for flow control are IF-conditions and FOR-loops. For further examples, see Step 3 in this article.
Step 2: Adding Helper Functions
In the example from Step 1, package names have not been handled correctly. In fact, files were always written to the root folder and didn't contain package declarations. Because in our model packages can be nested, we need an algorithm to determine the full package name. In the following listing, I've chosen a recursive algorithm that lets me introduce Xtend's switch statement.
class DomainmodelGenerator implements IGenerator {
// (...)
def String getPackageName(EObject obj) {
switch(obj) {
PackageDeclaration case(obj.eContainer instanceof PackageDeclaration):
obj.eContainer.packageName + "." + obj.name.toLowerCase
PackageDeclaration case(obj.eContainer instanceof Domainmodel):
obj.name.toLowerCase
AbstractElement:
obj.eContainer.packageName
default:
""
}
}
def getJavaName(Type dataType) {
dataType.packageName + "." + dataType.name
}
def getFileName(Type entity) {
entity.javaName.replace(".", "/") + ".java"
}
}


