Xtext provides lots of generic implementations for your language’s infrastructure but also uses code generation to generate some of the components. Those generated components are for instance the parser, the serializer, the inferred Ecore model (if any) and a couple of convenient base classes for content assist, etc.
The generator also contributes to shared project resources such as the plugin.xml, MANIFEST.MF and the Guice modules.
Xtext’s generator leverages MWE2 – the modeling workflow engine from EMFT to configure the generator.
MWE2 allows to compose object graphs declaratively in a very compact manner. The nice thing about it is that it just instantiates Java classes and the configuration is done through public setter and adder methods as one is used to from Java Beans encapsulation principles. An in-depth documentation can be found in the chapter MWE2.
Given the following simple Java class (POJO):
package com.mycompany;
public class Person {
private String name;
public void setName(String name) {
this.name = name;
}
private final List<Person> children = new ArrayList<Person>();
public void addChild(Person child) {
this.children.add(child);
}
}
One can create a family tree with MWE2 easily by describing it in a declarative manner without writing a single line of Java code and without the need to compile classes:
module com.mycompany.CreatePersons
Person {
name = "Grandpa"
child = {
name = "Father"
child = {
name = "Son"
}
}
}
These couple of lines will, when interpreted by MWE2, result in an object tree consisting of three instances of com.mycompany.Person. The interpreter will basically do the same as the following main method:
package com.mycompany;
public class CreatePersons {
public static void main(String[] args) {
Person grandpa = new Person();
grandpa.setName("Grandpa");
Person father = new Person();
father.setName("Father");
grandpa.addChild(father);
Person son = new Person();
son.setName("Son");
father.addChild(son);
}
}
The root element is a class-name following the Java classpath visibility rules. As the module is a sibling to the class com.mycompany.Person it is not necessary to use use fully qualified name. There are other packages implicitly imported into this workflow as well to make it convenient to instantiate actual workflows and components, but these ones are covered in depth in the appropriate chapter. The constructed objects are furthermore configured according to the declaration in the module, e.g. a second instance of Person will be created and added to the list of children of “Grandpa” while the third person – the class is inferred from the assigned feature – becomes a child of “Father”. All three instances will have their respective name assigned via a reflective invocation setName. If one wants to add another child to “Father”, she can simply repeat the child assignment:
child = com.mycompany.Person {
name = "Father"
child = {
name = "Son"
}
child = {
name = "Daughter"
}
}
As you can see in the example above MWE2 can be used to instantiate arbitrary Java object models without any dependency or limitation to MWE2 specific implementations. This is conceptually very close to the dependency injection mechanism and the XML language in the Spring Framework.
Tip Whenever you are in an *.mwe2 file and wonder what kind of configuration the underlying component may accept: Just use the Content Assist in the MWE2 Editor or navigate directly to the declaration of the underlying Java implementation by means of F3 (Go To Declaration).
This is the basic idea of the MWE2 language. There are of course a couple of additional concepts and features in the language and we also have not yet talked about the runtime workflow model. Please refer to the dedicated MWE2 reference documentation for additional information.
Of course a generator in Xtext is not composed of fathers and sons, but of so called language configurations. For each language configuration a URI pointing to its grammar file and the file extensions for the DSL must be provided. In addition, a language is configured with a list of IGeneratorFragments. The whole generator is composed of theses fragments. We have fragments for generating parsers, the serializer, the EMF code, the outline view, etc.
Each fragment gets the grammar of the language as an EMF model passed in. A fragment is able to generate code in one of the configured locations and contribute to several shared artifacts. The main interface IGeneratorFragment is supported by a convenient abstract base class AbstractGeneratorFragment, which by default delegates to an Xpand template with the same qualified name as the class and delegates some of the calls to Xpand template definitions.
We suggest to have a look at the fragment we have written for label providers ( LabelProviderFragment). It is pretty trivial and at the same time uses the most important call backs. In addition, the structure is not cluttered with too much extra noise so that the whole package can serve as a template to write your own fragment.
As already explained we use MWE2 from EMFT in order to instantiate, configure and execute this structure of components. In the following we see an exemplary Xtext generator configuration written in MWE2 configuration code:
module org.xtext.example.MyDsl
import org.eclipse.emf.mwe.utils.*
import org.eclipse.xtext.generator.*
import org.eclipse.xtext.ui.generator.*
var grammarURI = "classpath:/org/xtext/example/MyDsl.xtext"
var file.extensions = "mydsl"
var projectName = "org.xtext.example.mydsl"
var runtimeProject = "../${projectName}"
Workflow {
bean = StandaloneSetup {
platformUri = "${runtimeProject}/.."
}
component = DirectoryCleaner {
directory = "${runtimeProject}/src-gen"
}
component = DirectoryCleaner {
directory = "${runtimeProject}.ui/src-gen"
}
component = Generator {
pathRtProject = runtimeProject
pathUiProject = "${runtimeProject}.ui"
projectNameRt = projectName
projectNameUi = "${projectName}.ui"
language = {
uri = grammarURI
fileExtensions = file.extensions
// Java API to access grammar elements
fragment = grammarAccess.GrammarAccessFragment {}
/* more fragments to configure the language */
...
}
}
}
Here the root element is Workflow and is part of the very slim runtime model shipped with MWE2. It accepts bean s and component s. The var declaration is a first class concept of MWE2’s configuration language and defines the interface of the module. They allow to externalize some common configuration parameters. This comes especially handy in String variables where one can easily use ${variable} to concatenate values.
The method Workflow.addBean(Object) does nothing but provides a means to apply global side-effects, which unfortunately is required sometimes. In this example we do a so called EMF stand-alone setup. This class initializes a bunch of things for a non-OSGi environment that are otherwise configured by means of extension points, e.g. it allows to populate EMF’s singletons like the EPackage.Registry.
Following the bean assignment there are three component elements. The Workflow.addComponent() method accepts instances of IWorkflowComponent, which is the primary concept of MWE2’s workflow model. Xtext’s generator itself is an instance of IWorkflowComponent and can therefore be used within MWE2 workflows.
There are two fragments ImplicitRuntimeFragment and ImplicitUiFragment will be called implicitly if the required paths and project names are set. They take care of common defaults.
In the following table the most important standard generator fragments are listed. Please refer to the Javadocs for more detailed documentation.
Class | Generated Artifacts | Related Documentation |
---|---|---|
EcoreGeneratorFragment | EMF code for generated models | Model inference |
XtextAntlrGeneratorFragment | ANTLR grammar, parser, lexer and related services | |
GrammarAccessFragment | Access to the grammar | |
ResourceFactoryFragment | EMF resource factory | Xtext Resource |
ParseTreeConstructorFragment | Model-to-text serialization | Serialization |
ImportNamespacesScopingFragment | Index-based scoping | Index-based namespace scoping |
JavaValidatorFragment | Model validation | Model validation |
FormatterFragment | Code formatter | Declarative formatter |
LabelProviderFragment | Label provider | Label provider |
OutlineNodeAdapterFactoryFragment | Outline view configuration | Outline |
TransformerFragment | Outline view configuration | Outline |
JavaBasedContentAssistFragment | Java-based content assist | Content assist |
XtextAntlrUiGeneratorFragment | Content assist helper based on ANTLR | Content assist |
SimpleProjectWizardFragment | New project wizard | Project wizard |