Showing posts with label DSL. Show all posts
Showing posts with label DSL. Show all posts

Monday, May 07, 2012

Martin Fowler's State Machine DSL with Xtext 2.3


In his book on domain-specific languages, Martin Fowler introduces a small example along which he shows different techniques to implement a domain-specific language. The example goes like this:

Imagine you work for a company specialized on developing and installing systems for secret compartments and you have many customers with very different mechanisms. Mrs. H for instance wants to have a secret panel in her bedroom, which can only be opened after the door has been closed, the second drawer in her chest has been opened and the bedside light was turned on. Also the panel should be closed and locked immediately after someone opens the door - no matter in what state the system is in that case.

Martin proposes the following script as an appropriate definition of Mrs. H's secret compartment system:

events
  doorClosed
  drawOpened
  lightOn   
  reset doorOpened
  panelClosed
end

commands
  unlockPanel
  lockPanel
  lockDoor
  unlockDoor
end

state idle
  actions {unlockDoor lockPanel}
  doorClosed => active
end

state active
  drawOpened => waitingForLight
  lightOn    => waitingForDraw
end

state waitingForLight
  lightOn => unlockedPanel
end

state waitingForDraw
  drawOpened => unlockedPanel
end

state unlockedPanel
  actions {unlockPanel lockDoor}
  panelClosed => idle
end

The script starts with two sections which declare the events and the commands. After that the different states are declared. Some of them execute declared commands as a side effect (the actions block). Also they contain declarations of transitions, i.e. to what state the system switches when a certain event is fired.

As I have already implemented this language with previous versions of Xtext, I'd like to make it a bit more interesting this time. Let's replace the more or less useless declaration of commands with the possibility to write and call real code!

events
  doorClosed
  drawOpened
  lightOn   
  reset doorOpened
  panelClosed
end

state idle
  do { 
    println("opened the door.")
    println("locked the panel.")
  }
  doorClosed => active
end

state active
  drawOpened => waitingForLight
  lightOn    => waitingForDraw
end

state waitingForLight
  lightOn => unlockedPanel
end

state waitingForDraw
  drawOpened => unlockedPanel
end

state unlockedPanel
  do { 
    println("opened the panel.")
    println("locked the door.")
  }
  panelClosed => idle
end

As you can see the main change is that you are now able to call Java libraries right from within your state machine. I want these expressions to be statically typed (incl. full support for Java generics) and to keep the code as readable and dense as the rest of the DSL we need to have type inference like in Scala. Feature-wise everything should be supported, from for-loops to conditional logic, from the typical literals to more advanced concepts like lambda expressions (it's 2012 after all). 

But how would we talk to a door resp. panel service in order to open and close them? We could use static methods, but that's bad design since then the application is hardly testable and you cannot easily switch the concrete implementations behind these services. So let's use dependency injection. 
To support that I added a services block to the language where you list all required services. Now we can use these services within the action code :

events
  doorClosed
  drawOpened
  lightOn   
  reset doorOpened
  panelClosed
end

services
  DoorService door
  PanelService panel
end

state idle
  do { 
    door.open
    panel.close
  }
  doorClosed => active
end

state active
  drawOpened => waitingForLight
  lightOn    => waitingForDraw
end

state waitingForLight
  lightOn => unlockedPanel
end

state waitingForDraw
  drawOpened => unlockedPanel
end

state unlockedPanel
  do { 
    door.close
    panel.open
  }
  panelClosed => idle
end

Our language would now be very testable and should integrate nicely with any Java project. 

How Do I Implement Such A Language In Xtext?

To get a full implementation of this DSL, including not only a parser, linker, unparser, etc. but also a compiler generating readable and executable Java code as well as having nice integration in Eclipse, you need to create a fresh Xtext project, using the wizard and define the following grammar:

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.xbase.Xbase

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Statemachine :
  {Statemachine}
  ('events' 
    events+=Event+ 
  'end')?
  ('services'
    services+=Service*
  'end')?
  states+=State*;

Service :
  type=JvmTypeReference name=ID;

Event:
  resetEvent?='reset'name=ID;

State:
  'state' name=ID
    ('do' action=XBlockExpression)?
    transitions+=Transition*
  'end';

Transition:
  event=[Event] '=>' state=[State];

I don't want to go into a detailed explanation of the grammar language, since that is explained in the documentation. But note, that in order to be able to write full Java types in the service declaration we refer to a library grammar rule (JvmTypeReference). The same applies for the expressions: The library grammar 'org.eclipse.xtext.xbase.Xbase' predefines the full expressions and all we have to do here is to import the grammar in the first line and call the rule XBlockExpression within the rule State.

Now that we have defined the syntax of our language, we need to tell how it is translated to Java. For that matter we write some code, which creates a Java DOM out of the state machine DOM produced by the parser. To make this very readable and concise Xtext comes with an internal DSL implemented in the programming language Xtend. The actual code looks like this:

 def dispatch void infer(Statemachine stm, 
                         IJvmDeclaredTypeAcceptor acceptor, 
                         boolean isPreIndexingPhase) {
   
   // create exactly one Java class per state machine
   acceptor.accept(stm.toClass(stm.className)).initializeLater [
     
     // add a field for each service annotated with @Inject
     members += stm.services.map[service|
       service.toField(service.name, service.type) [
         annotations += service.toAnnotation(typeof(Inject))
       ]
     ]
     
     // generate a method for each state having an action block
     members += stm.states.filter[action!=null].map[state|
       state.toMethod('do'+state.name.toFirstUpper, state.newTypeRef(Void::TYPE)) [
         visibility = PROTECTED
         
         // Associate the expression with the body of this method.
         body = state.action
       ]
     ]
     
     // generate a method containing the actual state machine code
     members += stm.toMethod("run"newTypeRef(Void::TYPE)) [
       
       // the run method has one parameter : an event source of type Provider 
       val eventProvider = stm.newTypeRef(typeof(Provider), stm.newTypeRef(typeof(String)))
       parameters += stm.toParameter("eventSource", eventProvider)
       
       // generate the body
       body = [append('''
         boolean executeActions = true;
         String currentState = "«stm.states.head.name»";
         String lastEvent = null;
         while (true) {
           «FOR state : stm.states»
             if (currentState.equals("«state.name»")) {
               «IF state.action != null»
                 if (executeActions) {
                   do«state.name.toFirstUpper»();
                   executeActions = false;
                 }
               «ENDIF»
               System.out.println("Your are now in state '«state.name»'. Waiting for [«
                 state.transitions.map[event.name].join(', '].");
               lastEvent = eventSource.get();
               «FOR t : state.transitions»
                 if ("«t.event.name»".equals(lastEvent)) {
                   currentState = "«t.state.name»";
                   executeActions = true;
                 }
               «ENDFOR»
             }
           «ENDFOR»
           «FOR resetEvent : stm.events.filter[resetEvent]»
             if ("«resetEvent.name»".equals(lastEvent)) {
               System.out.println("Resetting state machine.");
               currentState = "«stm.states.head.name»";
               executeActions = true;
             }
           «ENDFOR»
           
         }
       ''')]
     ]
   ]
 }


That's all you need. With just the grammar and the Xtend code above you have implemented the full language! I've pushed the full project to github.
This is what the Java code generated for Mrs. H's controller looks like.

What Do You Get?

As already mentioned the expressions are feature complete and statically typed. The compiler can be run from any Java process (e.g. ant, maven, gradle, command line). Also the tooling is just like you would expect: content assist, coloring, hovers, refactorings, dead code analysis and even debugging work out of the box:


Content Assist For Expressions

Call-Hierarchy Integrated in JDT
Content Assist For Types

Dead Code Analysis






Thursday, February 23, 2012

EclipseCon 2012 and Xtext

In case you didn't recognize this year's EclipseCon has a whole track on DSLs, covering a lot of interesting sessions around Xtext. There are many technical sessions, success stories, and even a full tutorial.

Here are the technical talks given by the core committers of Xtext and EMF:

And just in case you need some inspiration, there are a lot of sessions talking about stuff which was built with Xtext:


Finally there are some more general sessions like:

Xtext core committers, that is Jan, Moritz, Sebastian, Ed and me, will be at the conference all days and be happy to discuss all kinds of topics with you. We'll have a booth where you can always find someone to talks to and most likely we'll also have a BOF session at one of the evenings.

So if you are in any case interested in Xtext and/or domain specific languages, EclipseCon 2012 is the conference to go to. It's only in five weeks, so you should register now!

And by the way there are many, many other interesting sessions going on there.

I'm looking forward to a fun community meet-up! :-)

Wednesday, November 25, 2009

RE: The Hidden Costs of Domain-Specific Languages

Today, Johan Den Haan pointed me to a post about "Hidden Costs" of DSLs via twitter.
The post starts with the following statement:
While I am a big fan of Domain-Specific Modeling, I am openly not in favor of Domain-Specific Languages.
... which leaves me puzzled. I'm pretty sure that the DSM guys call their abstractions DSLs as well.
Anyway, the post itself is not particularly interesting, but at the end of it there are a couple of questions about DSLs. This sort of questions which I've heard often and which contain a lot of miss- and preconceptions. Thought it was a good opportunity to answer them.

So here are the questions and my answers:
If we take so much time to master a general purpose language, should we invest a comparable amount of time in limited-use languages (i.e. DSL)?
No, you shouldn't and you won't. DSLs are much easier to learn, since they are limited and focused. The important thing is that you have to understand the domain. But that is necessary in any case, no matter if you translate everything to a general purpose language or write it down like it is meant to be (i.e. using a DSL). If you know the domain very well, but are not able to grasp the DSL, than the DSL might not be that good.
How can we get support for a DSL, apart from its own creators?
How can you get support for an API, apart from its creator? Well, by looking at the code or any existing documentation, or by asking any other users. In many aspects DSLs are comparable to libraries or frameworks.
Where’s community support?
It depends on whether it is a widely used DSL (i.e. there is a community) or one you use and have defined in your one-man project. For the DSLs used in Ruby on Rails there's much community support, for instance. Same for the DSLs we have in Xtext.
Where's the community support for your homegrown libraries?
What happens after the departure of the language’s creator?
It is like with any other artifact in your software system: A DSL definition needs to be clean, understandable and maintainable. If you know that an important team member is leaving the project, make sure she's not taking any exclusive knowledge with her. That is she should coach other people before she lefts.
What’s BNF? Do I need it?
see http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form :-)
DSL critics say really useful DSLs are hard and expensive to create. DSL supporters answer that DSLs are not designed, they evolve. Well, won’t any of those “evolutionary steps” risk breaking the entire development based on that DSL, much like a broken app build?
Useful DSLs can be very simple and at the same time a very complex DSL can be absolutely useless. It's not the complexity of a DSL, which makes it useful, but whether how good it is to solve the problem at hand. KISS and YAGNI are important principles here as well.
When defining concepts and abstractions it is key to find a good compromise between simplicity (abstraction) and re-usability. The simpler a DSL and the higher its potential for reuse the better. You're hitting a sweet spot, then. You start leaving that sweet spot as soon as you add concepts to the DSL which are only used by a small subset of the users. Often it is better to have two simple DSLs in two projects, than putting everything into one big DSL.

Back to the question: I don't think that evolution and design is mutual exclusive. Don't everybody design software systems incrementally these days.
Changing a textual DSL is not a big problem in practice. At least not as long as you have a decent test suite (which you should have no matter if you use DSLs or not). However, managing compatibility and versioning can become quite complicated, but again it's the same as with APIs.
Will the evolution in the language be severe enough to trash what has been done so far?
You can shoot your foot, if you want. But you don't need a DSL to do so.
Can you imagine yourself developing a complex C++ software system while C++ itself was still being designed and developed?

Can you imagine writing a complex C++ software system while the standard libraries are still being designed and developed?
I can imagine, but I am not keen on doing that. That is why we have things like versioning and that is also why people think about compatibility when enhancing public programming interfaces. Such interfaces can be a method signature, but it can also be a syntax of a language. It doesn't matter. If it breaks it breaks. And the designer has to be explicit about the contract of that interface as well as its life cycle.

Tuesday, June 10, 2008

Fowler's DSL example with Xtext

In his recent DSL related blog entries (ParseFear and Syntactic Noise), Martin Fowler mentioned a DSL example he's using in his upcoming DSL book. Here you can see how it is implemented using Antlr and a lot of Java.

This is how it can be done using Xtext and openArchitectureWare.
1) Download the oAW distro from itemis
2) Create a new Xtext Project
3) Paste the following Xtext grammar into the opened editor:

Statemachine :
'events'
(events+=Event)*
'end'
'commands'
(commands+=Command)*
'end'
(states+=State)*;

Event :
(resetting?='resetting')? 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) start the generator (right click on generate.oaw Run->oaw workflow)
your're done.

You not only get an Antlr based parser but also get an EMF based AST (SemanticModel), and a fully fledged eclipse editor.

Of course it's up to you whether you want to interpret models or generate some code out of them. I choosed to write a small Xpand template file, generating a "controller".
Here it is.

Codegeneration 2008

Codegeneration 2008 is coming! And there'll be a lot of friends giving talks.
Peter and Frank are talking about model-driven Lego.
Markus will give two talks (Implementation Techniques for Domain-Specific Languages and Building Interpreters with EMF, Xtext and Scala).
I'll do two tutorials together with Arno:
One is called Concrete Syntaxes of DSLs and the other will give an Overview of Eclipse Modeling.
Finally on Friday, Karsten, Jan and me are holding a workshop about Using openArchitectureWare for M2M and M2T.
As this conference is specialized on code generation I'm looking forward to some interesting discussions. See you there!