Home > DSLs, MDSD, The How, Xtext > Scoping and content assist using Xtend on steroids

Scoping and content assist using Xtend on steroids

Gosh, it has been almost a month since my last blog…about time to get my act together and publish some more. Luckily, there’s enough material in the pipe line 🙂

By the way: I changed the theme as the previous one was a bit dark and not wide enough to comfortably fit bits of “real” code. I also have started using WordPress’ built-in code tags to show the code as it easier to copy code from that than from screen shots and making the screen shots is often quite a lot of work. The down side to all that is the lack of syntax highlighting for non-standard languages such as Xtend.

I often find that a lot of my scoping and content assist logic can be declaratively expressed in Xtend quite comfortably while the equivalent Java implementation takes up rather more space. One possible solution would be to use JVM-based languages like Groovy or Scala to express such logic since these language allow you to use more or less the same syntax as Xtend.

But the solution I’m going to explore here is to actually use Xtend to implement some scoping. One thing to take into account here is that Xtend is interpreted so it will always be at least somewhat slower and potentially a lot slower. However, I find that in most circumstances Xtend expressions can quite easily be tuned to have the same runtime complexity (in the algorithmic sense) as an equivalent Java implementation. To get that last extra bit of ‘oompf!’ I’m also going to caching for the scoping.

The example

We’re going to use the Domain-Model example shipped with Xtext since version 1.0.0. The scoping implementation for the ‘opposite’ feature of the Reference type is as follows:

Now, even using Google Collections this looks rather ungainly. So, we’ll trim it down to a more succinct and completely functional expression using a bit of pseudo-code which happens to look remarkably like a piece of Xtend code 😉

$features = ((Entity) ref.type.referenced).features   // line 29
$references1 = $features.typeSelect(Reference)        // line 30
$references2 = $references2.select( input | ref.eContainer == input.type.referenced )   // lines 31-34

(I use $-prefixes to indicate temporary variables.) Seeing that none of the right-hand side expressions have any side effects, we can roll this expression into the following (Xtend) “one liner” spread over three lines for the sake of exposé:

((Entity) ref.type.referenced).features
       .typeSelect(Reference)
       .select( input | ref.eContainer == input.type.referenced )

This also makes the semantics of this scoping logic a lot clearer: “for this Reference, which is a feature of entity A (which equals ‘ref.eContainer’), pointing to entity B, select all Reference features of entity B which point to entity A”.

 

Now that we have our Xtend expression we can put it in an Xtend file called Scoping.ext residing next to the DomainModelScopeProvider Java class file:

import domainmodel;

List[Reference] scope_Reference_opposite(Reference ref) :
   ((Entity) ref.type.referenced).features
      .typeSelect(Reference)
      .select( input | ref.eContainer == input.type.referenced );

Note that we don’t want to have this Xtend function cached by prepending its definition with the cached keyword as that would mean that the editor would always return the scoping reflecting the initial state, even after the contents changes.

 

XtendFacade

To use this Xtend file, we’re going to make use of the org.eclipse.XtendFacade class which is the work horse behind the Xtend workflow component one usually encounters. (This class resides in a plugin which is by default an (indirect) dependency of any Xtext project.) First we have to obtain an instance of the XtendFacade in the DomainModelScopeProvider which we’ll do by adding a no-args constructor and instance field to the class’ innards:

private XtendFacade xtendFacade;

public DomainmodelScopeProvider() {
  xtendFacade = XtendFacade.create("org::eclipse::xtext::example::scoping::Scoping");
  xtendFacade.registerMetaModel(new EmfRegistryMetaModel());
}

We obtain an XtendFacade object by pointing to a particular Xtend file, after which we add the EmfRegistryMetaModel meta model implementation which simply ensures that all meta models previously registered with the EMF Registry are included in Xtend’s internal type system. Note that the location of the Xtend file follows the Xtend convention instead of the file system convention, and that you have to leave out the ‘.ext‘ extension.

 

Now we can replace the existing implementation of the scope_Reference_opposite method with the following one:

public IScope scope_Reference_opposite(final Reference ref, EReference eRef) {
  @SuppressWarnings( "unchecked" )
    List result = (List) xtendFacade.call("scope_Reference_opposite", ref);
  return scopeFor(result);
}

We need the cast to List<Reference> because XtendFacade#call has Object as return type. Furthermore, I follow Joshua Bloch’s Item 24 from Effective Java (Second Edition) to suppress the unchecked cast warning.

 

Caching

Finally, we’re going to add some sensible caching to this implementation. Note that in this case, we don’t expect any serious performance hit because usually/hopefully entities typically do not have a huge number of features and the scoping logic is linear in the number of features of the entity pointed to by the reference for which the scoping is invoked. But since the execution of Xtend is typically somewhat slower than the Java counterpart, we might as well show how to.

For the caching, we’re going to use the org.eclipse.xtext.util.IResourceScopeCache interface which has a default implementation which is automagically wired up by the Guice DI container and which has the behavior that it evicts the cache as soon as the containing resource is changed. It’s also the best result one can hope to achieve in general: we could do better by listening to changes on the referenced entity, but that makes the implementation quite a bit hairier.

First, we add an appropriate field to the class:

@Inject
private IResourceScopeCache scope_Reference_opposite_cache;

Then, we beef up the implementation of scope_Reference_oppositemethod to use the cache:

public IScope scope_Reference_opposite(final Reference ref, EReference eRef) {
	return scope_Reference_opposite_cache.get(ref, ref.eResource(), new Provider<IScope>() {
			public IScope get() {
				@SuppressWarnings( "unchecked" )
					List<Reference> result = (List<Reference>) xtendFacade.call("scope_Reference_opposite", ref);
				return scopeFor(result);
			}
		}
	);
}

Note that the previous implementation of this method has simply moved into the get method of anonymous inner class implementing com.google.inject.Provider (lines 4-6).

 

Under the rug

We have swept quite some details under the rug, most notably the exception handling: the code obviously works with this particular grammar definition, but as soon as the grammar def starts to change, then this code is not very robust, because of the following two implicit (in the sense that you most likely don’t get any immediate warnings from the IDE as soon as they break) assumptions in the code:

  1. ref.type.referenced (with ref being a Reference) is assumed to be an Entity, because of the cast in the scoping Xtend expression;
  2. the scope_Reference_opposite function returns a List of References.

As soon as these assumptions don’t hold any more, the scoping simply stops working and the only signs of that you’re likely to see outside of that are stack traces in the console of a launching Eclipse instance.

Furthermore, both the Java as well the Xtend implementation could be optimized slightly:

  1. The typeSelect and select calls could be rolled into one select invocation which also checks type. This saves creation and populating of 1 list and running over it.
  2. The value ‘ref.eContainer‘ could be stored in a local variable to avoid unnecessary dereferencing. This is more important for the Xtend implementation, as the JVM probably inlines that anyway for the Java implementation.

In this case, both optimizations are not worth the effort but for more intricate scoping logic they can make your editor experience just that bit better.

Advertisements
Categories: DSLs, MDSD, The How, Xtext
  1. October 13, 2010 at 11:40 am

    Ziet er weer netjes uit!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: