Post processing vs Post initialization

Sometimes it is useful to be able to modify an EObject right after its creation, in runtime.
With “modify” we can mean several things: change the metamodel of the model (adding for example a feature or an operation) or change the values of the model (setting the value of a given feature) or both.
For clarification I shall call the second case “post initialization” to differentiate it with the post processing step.

Read more of this post

Advertisements

Change the outline, part 2

More children!

Now that we have retrieved our missing child, let’s change the structure of the outline a bit more, adding the TypeDeclaration to the Room node and the Room cross-references of Share, if it has one.
Again, Xtext provide us a convenience class for customization, the Transformer (MyDslTransformer) class. Just write down one method that returns the children of a Room element and one that returns the children of Share:

MyDslTransformer.java

...
public List<EObject> getChildren(Room room)
   {
   //Create a List with only one element: Room node will have only one child.
   List<EObject> res = new Vector<EObject>(1, 1);
   if(room.getType()!=null)
      res.add(room.getType());
   else
      return NO_CHILDREN;
   return res;
   }

public List<EObject> getChildren(Share share)
   {
   //Add the rooms where the furniture come from, eventually.
   if(share.getList()!=null)
      {
      List<EObject> res = new Vector<EObject>(1, 1);
      //Put the two rooms.
      if(share.getList().getSecond()!=null)
         res.add(share.getList().getSecond());
      if(share.getList().getFirst()!=null)
         res.add(share.getList().getFirst());
      if(res.size()>0)
         return res;
      else
         return NO_CHILDREN;
      }
   return NO_CHILDREN;
   }
...

Compile, run and you should see the new nodes appearing in the outline:


What if we want to see less nodes? How we can tell the framework to hide some of them? Imagine, for instance, that we don’t want to see "Kitchen" node in a Share element but we still want to see it for a Room element.
First of all, we have to see how Xtext actually skip a node:

  • The topmost parent class of MyDslTransformer is AbstractSemanticModelTransformer.
  • In there we can see that the method transformSemanticNode create a new node only if consumeSemanticNode returns true; otherwise it simply return the parentNode.

So we have two ways to hide a node in the outline:

  1. Override consumeSemanticNode, returning false accordingly.
  2. Write the right createNode method, in the Transform class, returning the parent node.

We’ll do the latter because it is a bit tricky to tell if the TypeDeclaration element comes from a cross reference in Share or from the declaration of Room, in the mehod consumeSemanticNode:

MyDslTransformer.java

public ContentOutlineNode createNode(TypeDeclaration semanticNode,
                                                             ContentOutlineNode parentNode)
   {
   //Used to see where we are.
   ContentOutlineNode grandParent = parentNode.getParent();
   if(grandParent!=null)
      {
      //If the EClass of the grand parent is named "Share", then we must hide the node.
      if(grandParent.getClazz()!=null &&
         grandParent.getClazz().getName().compareTo("Share")==0)
         return parentNode;
      }
   //Otherwise, standard behaviour.
   ContentOutlineNode outNode = super.createNode(semanticNode, parentNode);
   return outNode;
   }

Now you should see something like this:

Targeting problems

If you try to click one of the new nodes, you should see that they point to wrong sections of the code:

I think that the problem here is that in the MyDslTransformer‘s context, all the cross references are already resolved and we manipulate the pointed elements. To correct this we can either try to generate some custom TextLocation through a special implementation of ILocationInFileProvider (going nearer to the creation of virtual nodes), or we can change a little bit the model so that it has some nodes that specifically hold the cross references: at that point the default behaviour of Xtext should give us everything, as it should use that new model’s nodes to calculate the regions.
I will take the latter way because I think that when the outline is required to show some nodes that are not present in the model, then probably the model has to be changed to hold the new nodes. Personally, I think that the creation of outline’s virtual nodes must be done rarely. In other words, in my opinion the outline customization must be used only to hide or to redraw, not to inject.
(Still want to create virtual nodes? Jump to the appendix!)

So update MyDsl.xtext as follows:

//Use the default terminals.
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

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

//A house will be considered as a particular kind of structure.
Structure:
   //First, we must declare what type of structure it is.
   type=TypeDeclaration
   //A structure can be composed of various rooms.
   (rooms+=Room)*
   //A structure has various furnitures.
   (furns+=FurnitureDeclaration)*
   //A structure can have furnitures of other rooms.
   (shares+=Share)*
   ;

TypeDeclaration:
   'TYPE' name=STRING
   ;

//A room has a name and is a structure of a particular type.
Room:
   'ROOM' name=STRING 'OF' 'TYPE' type=TypeReference
   ;

FurnitureDeclaration:
   'FURNITURE' name=STRING
   ;

//We let the furnitures to be shared among rooms/structures.
Share:
   'SHARE' name=[FurnitureDeclaration|STRING] (list=List)?
   ;

//This is used to define where the shared furniture come from.
//Share level is limited to two.
//{} <- [II] <- [I]
List:
   ('<-' second=RoomReference)? '<-' first=RoomReference
   ;
   
TypeReference:
   ref=[TypeDeclaration|STRING]
   ;
   
RoomReference:
   ref=[Room|STRING]
   ;

TypeReference and RoomReference are the novelty here. As a further clarification, here are the two .genmodel files, the old one at the right:

Finally delete getChildren(Room room) in MyDslTransformer and add two methods in MyDslLabelProvider:

MyDslLabelProvider.java

...
String text(TypeReference typeRef)
   {
   return typeRef.getRef().getName();
   }
   
String text(RoomReference roomRef)
   {
   return roomRef.getRef().getName();
   }
...

Now the outline should be correct, with the right nodes that point at the right sections of the code!

A very simple case study

This is a case study derived from my use of Xtext in my work as a software engineer, a consultant.

I will break it in several posts that might resamble a tutorial but it is not one because I still don’t feel myself so competent to write a full fledge tutorial: for each path that I will take there will be probably other cleaver ways, or, worse, errors that I won’t see.
So, please, take everything with a grain of salt.
(If you want a well-done tutorial, look here and here .)

Also, I cannot write about the language that I’m developing, because of non-disclosure agreements. So the language on which I’ll write about is specifically created for these posts, designed for the sole purpose to reflect problems that I have encountered. Someone might call it a head fake.

Shall we start?

The language I propose is used to describe houses. A house is a structure that can contain other structures, each one with some furnitures. A furniture can be shared from one sub-structure to another.
That’s all, simple isn’t it? 🙂

So after downloaded all the necessary things, and created the projects (with everything at their default values), write down the grammar:

MyDsl.xtext

//Use the default terminals.
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

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

//A house will be considered as a particular kind of structure.
Structure:
   //First, we must declare what type of structure it is.
   type=TypeDeclaration
   //A structure can be composed of various rooms.
   (rooms+=Room)*
   //A structure has various furnitures.
   (furns+=FurnitureDeclaration)*
   //A structure can have furnitures of other rooms.
   (shares+=Share)*
   ;

TypeDeclaration:
   'TYPE' name=STRING
   ;
 
//A room has a name and is a structure of a particular type.
Room:
   'ROOM' name=STRING 'OF' 'TYPE' type=[TypeDeclaration|STRING]
   ;

FurnitureDeclaration:
   'FURNITURE' name=STRING
   ;
 
//We let the furnitures to be shared among rooms/structures.
Share:
   'SHARE' name=[FurnitureDeclaration|STRING] (list=List)?
   ;
  
//This is used to define where the shared furniture come from.
//Share level is limited to two.
//{} <- [II] <- [I]
List:
   ('<-' second=[Room|STRING])? '<-' first=[Room|STRING]
   ;

Generate everything, then launch the plug-ins.
Here are some source files:

Home.mydsl

   TYPE "Home"

   ROOM "IfIAmHunger" OF TYPE "Kitchen"
   ROOM "TheSleepingRoom" OF TYPE "Bedroom"

   FURNITURE "Chair"
   FURNITURE "Table"

   SHARE "Fridge" <- "IfIAmHunger"

Bedroom.mydsl

   TYPE "Bedroom"

   FURNITURE "Bed"
   FURNITURE "Bookcase"

Kitchen.mydsl

   TYPE "Kitchen"
 
   FURNITURE "Fridge"
   FURNITURE "Hoven"

As you can see Xtext provide you with many things out of the box: syntax highlighting, code folding, an outline, ecc.. Each one is configurable according to your needs.


That’s it for this post!
Next we’ll start to customize something!