Channels ▼
RSS

Tools

Generating Code from DSLs


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.

Figure 1: An Xtend file (left) and the generated Java source code (right).

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,

  1. Follow the steps in the "Getting Started" section.
  2. Copy and paste the complete grammar from section "Start the Entity Editor."
  3. 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 method getAllContentsIterable() is imported as a static extension from the Java class ResourceExtensions.
  • member methods: All member methods of an Xtend class can implicitly be used as extension methods. In the example, type.contents is equivalent to this.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 from myObject are 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"
  }  
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video