Below you can find a detailed description. For those of you who prefer to just sit and watch, here are three screencasts I've recorded around this example.
Creating the project and defining the language
Defining the code generator and see its integration
Features, Features, Features
1) Download Eclipse and Install Xtext
For many people this is the hardest part. But believe me things have improved and it is now quite easy to get a working Eclipse distribution including the latest milestone of Xtext 2.0. :-)
Download an Eclipse Classic distribution for your platform from here :
http://www.eclipse.org/downloads/index-developer.php
Unzip and start Eclipse, choose Help -> Install New Software... and use the update site
http://download.itemis.com/updates/milestones
in order to install the Xtext SDK feature
2) Use the Xtext project wizard
Select New -> Project..., and within the dialog choose Xtext Project. You can stick with the defaults on the next page, but make sure you choose the Experimental Features configuration in order to get the latest and greatest features, such as rename refactoring.
You should use the experimental features |
3) Define the grammar
The Xtext grammar file is automatically opened. Just copy and paste the following grammar definition for Martin's statemachine DSL in.
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
generate statemachine "http://www.xtext.org/example/secretcompartments/Statemachine"
Statemachine :
{Statemachine}
('events'
events+=Event+
'end')?
('resetEvents'
resetEvents+=[Event]+
'end')?
('commands'
commands+=Command+
'end')?
states+=State*
;
Event:
name=ID code=ID
;
Command:
name=ID code=ID
;
State:
'state' name=ID
('actions' '{' actions+=[Command]+ '}')?
transitions+=Transition*
'end'
;
Transition:
event=[Event] '=>' state=[State]
;
4) Run the generator
Now you need to run the generator which creates the parser and a couple of other parts of the language infrastructure. To do so right click into the editor and choose
Run As -> Generate Xtext Artifacts
5) Define the code generator
A DSL isn't worth much if you are not able to execute it somehow. Xtext has already provided you with a stub for a code generator written in our cool new template language Xtend (Xtend as well as the grammar language are also implemented with Xtext of course).
To get a working implementation which produces a Java implementation of a defined statemachine, just copy the following into the file MyDslGenerator.xtend.
package org.xtext.example.mydsl.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import org.xtext.example.mydsl.statemachine.Statemachine
import org.xtext.example.mydsl.statemachine.Event
import org.xtext.example.mydsl.statemachine.Command
import org.xtext.example.mydsl.statemachine.State
class MyDslGenerator implements IGenerator {
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
fsa.generateFile(resource.className+".java", toJavaCode(resource.contents.head as Statemachine))
}
def className(Resource res) {
var name = res.URI.lastSegment
name.substring(0, name.indexOf('.'))
}
def toJavaCode(Statemachine sm) '''
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class «sm.eResource.className» {
public static void main(String[] args) {
new «sm.eResource.className»().run();
}
«FOR c : sm.commands»
«c.declareCommand»
«ENDFOR»
protected void run() {
boolean executeActions = true;
String currentState = "«sm.states.head.name»";
String lastEvent = null;
while (true) {
«FOR state : sm.states»
«state.generateCode»
«ENDFOR»
«FOR resetEvent : sm.resetEvents»
if ("«resetEvent.name»".equals(lastEvent)) {
System.out.println("Resetting state machine.");
currentState = "«sm.states.head.name»";
executeActions = true;
}
«ENDFOR»
}
}
private String receiveEvent() {
System.out.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
return br.readLine();
} catch (IOException ioe) {
System.out.println("Problem reading input");
return "";
}
}
}
'''
def declareCommand(Command command) '''
protected void do«command.name.toFirstUpper»() {
System.out.println("Executing command «command.name» («command.code»)");
}
'''
def generateCode(State state) '''
if (currentState.equals("«state.name»")) {
if (executeActions) {
«FOR c : state.actions»
do«c.name.toFirstUpper»();
«ENDFOR»
executeActions = false;
}
System.out.println("Your are now in state '«state.name»'. Possible events are [«
state.transitions.map(t | t.event.name).join(', ')»].");
lastEvent = receiveEvent();
«FOR t : state.transitions»
if ("«t.event.name»".equals(lastEvent)) {
currentState = "«t.state.name»";
executeActions = true;
}
«ENDFOR»
}
'''
}