Home > DSLs, The How, Xtext > Path expressions in entity models

Path expressions in entity models

In this blog, I’ll explain how to enhance the standard Domain Model example (which is a fairly standard entity model DSL) shipped with Xtext 1.0 with the possibility to define so-called ‘path expressions’. A path expression points to a particular attribute or reference of an entity in an entity model through a specific ‘path’ of entity or attribute/data type references. Within the context of the entity model itself this is not so useful, but outside of it, it may be used for things like defining interface mappings, bindings of fields on screens to expressions languages for constraint and business logic. As an example, consider the following (simple and not so real-life) DSL text for which we’ll cook up the implementation in the course of this blog:

Grammar-wise, the enhancement is fairly simple, but the scoping aspect is a little more difficult to get right. Generally speaking, implementation of the scoping is a bit of a hotch-potch of concerns: functionally it is closely tied to the actual abstract syntax of the DSL, combined with concerns on performance/scalability and maintainability/language evolution. While implementing the feature, I’ll show a number of tricks to help with the scoping aspect.

Prerequisites From now on, I’ll assume that you’re working with Eclipse Helios, with Xtext 1.0 installed -preferably the Itemis bundle found here as it comes with the Antlr parser generation backend.

Enhancing the grammar

As a first step, create a New Project, select the ‘Xtext Domain-Model Example’ under Xtext > Examples and click ‘Next >’ and ‘Finish’. We’re going to enhance the grammar first, so open up the Domainmodel.xtext file (Ctrl/Cmd+Shift+R ‘Open Resource’ is quite helpful for finding non-Java files), navigate to the end of the file and add the following snippet:

This tells the grammar that I can define paths which consist of a name and a path expression starting with a reference to an entity and continues with references to structural features of an entity (i.e., an attribute or a reference to another entity, not an operation) prefixed with a slash, in a tail recursive manner. We’ll call each of the references to a structural feature a path element. Note that the PathHead rule could very well be rolled into the PathDefinition rule but that prevents re-use of path expressions outside of a path definition, so we’ll not do that now.

Now, the PathExpression type rule in the grammar is colored grey which means it’s only defined but not actually used. To remedy that, we add the rule as an extra alternative in the AbstractElement type rule, like so:

Note that if we were to try and do this using a list assignment (+=) like this,

PathExpression: entity=[Entity] ( '/' pathElements+=[StructuralFeature] )+;

we’d have to come up with a way of knowing at what item in the list the scoping was triggered. Using tail recursion like this, scoping is going to be called for the  PathTail element which is contained through a containing reference called tail and we have to provide a scope for the (multi-valued) feature reference based on the path which ‘comes before’.

To assist in thinking about this, here’s a figure detailing the abstract syntax tree of part of the DSL text given above:

Here, tree nodes are labelled with the grammar rule name and edges are (object) references labelled with the grammar rule feature.

By the way, I chose the slash notation because I like it better than the dot and also because the dot notation is already used in the qualified naming scheme for this DSL (which is not used in the example text above). I also choose to exclude references to operation features and downcasting: it’s perfectly possible and not very difficult to do both, but it burdens the implementation with irrelevant details…which I could save for another post 😉

Implementing the scoping

Now, run the GenerateDomainmodelLanguage.mwe2 workflow to generate parser, editor, etc. Now open the org.eclipse.xtext.example.scoping.DomainmodelScopeProvider class (e.g., using Ctrl/Cmd+Shift+T ‘Open Type’) and note that it inherits from AbstractDeclarativeScopeProvider. This means multiple/polymorphic dispatch is used to get to the correct scoping method based on the (runtime) type of the model element (EClass) and cross-reference (EReference) for which the scoping is triggered.

To avoid that our scoping code breaks silently immediately after we change the grammar in a way we don’t anticipate at the moment (and as it’s an example I’m not anticipating anything), it is prudent to throw an unchecked exception with a meaningful message to alert the language engineer (that’s you!) as early as possible you need to fix something. Note that the editor does not break this way, but the exception will show up on the console of the launching Eclipse instance and the local scope will be empty -everything that shows up in the content assist is a consequence of the parser recovering.

Now, we have to distinguish between two cases. The first case is that the parent of the PathTail instance (i.e., parser node) for which to compute the scope of the feature…uhm…feature is a PathHead. In this case, the scope is all the Reference or Attribute (i.e., StructuralFeature) elements in the features list attribute of the Entity referenced by the PathHead, which excludes all Operations. The second case is where the parent is another PathTail instance which (presumably) has a non-null Reference which points to an instance of Type. If that instance is an Entity, the scope is simply all structural features of that Entity. If not, the user is trying to continue a path expression from a singular Attribute which must be considered an error which we choose to express as the scope being empty so nothing valid can be filled out at that spot.

Including the prudent exception thrown mentioned above, the code looks as follows:

    IScope scope_PathTail_feature(final EObject context, EReference ref /* not used */) {
        EObject parent = context.eContainer();
        // parent can be PathHead or PathTail

        if( parent instanceof PathHead ) {
            return scopeFor( structuralFeatures(((PathHead) parent).getEntity()) );
        }
        if( parent instanceof PathTail ) {
            StructuralFeature prevFeature = ((PathTail) parent).getFeature();
            if( prevFeature instanceof Reference ) {
                Type referenced = ((Reference) prevFeature).getType().getReferenced();
                if( referenced instanceof Entity ) {
                    return scopeFor( structuralFeatures((Entity) referenced) );
                } else {
                    return scopeFor( Iterables.<Feature>emptyIterable() );
                        // Continuing a path expression from an Attribute is actually
                        // an ERROR, triggered here through the scope being empty.
                }
            }
        }

        // Fall-through? Probably a programmer error...
        throw new RuntimeException("don't know how to compute scope for 'feature' of PathTail");
    }

    private List<StructuralFeature> structuralFeatures(Entity entity) {
        return org.eclipse.xtext.EcoreUtil2.typeSelect(entity.getFeatures(), StructuralFeature.class);
    }

Note that we used a method from org.eclipse.xtext.EcoreUtil2 which happens to be a nice and useful util class inside the Xtext runtime.

Possible improvements

Now, the implementation looks rather ugly with al those explicit ifs and downcasts. Using polymorphic dispatch we’d be able to write the same thing down much more cleanly, but in our rather limited case it adds as many lines of boilerplate code as it saves ‘functional’ code, so we’ll leave that to a situation where the need for clarity and maintainability outweighs those extra lines.

To make this feature a little more mature, we could add a validation warning that a path expression ending on an attribute cannot be continued further. In any case, when continuing such a path expression, the scope for the feature reference would be empty, so the continued path would always be flagged with an error. The warning would just serve as an explanation of why the error would be there, so it’s not totally clear we’d need that validation.

A validation which is already quite useful to add, is to warn for path expressions which encounter a path element which points to a multi-valued attribute or reference anywhere before the final element. Again, I’ll leave that as an exercise to the reader for now.

P.S.: I changed some details of this blog with respect to the implementation of the scoping provider, necessitated by changes between Xtext 1.0.0 and 1.0.1 in the shipped examples.

Advertisement
Categories: DSLs, The How, Xtext
  1. October 13, 2011 at 11:19 am

    Hi,

    Is this still compatible for Xtext 2.0 ?

    (I’ve tried, but no method scopeFor found, for instance)

    Thanks

    • October 13, 2011 at 4:02 pm

      This is still largely compatible with Xtext 2.x (x=1 coming up quite soon). The scopeFor method resides in org.eclipse.xtext.scoping.Scopes and I used a static import to be able to write the Java code above.

      However, for the specific situation that you’re extending the ‘Xtext Domain-Model Example’, you need to take into account that its implementation has changed quite a bit and that goes quite a bit further than the facts that its scope provider is written in Xtend and doesn’t inherit from AbstractDeclarativeScopeProvider.

      I’ll be doing a blog post on a Xtext 2.x version soon, by porting the original example and not one based on the current ‘Xtext Domain-Model Example’. Stay tuned…

  1. October 15, 2010 at 9:45 am
  2. December 14, 2010 at 1:49 pm
  3. April 8, 2011 at 10:38 pm
  4. November 23, 2011 at 2:46 pm
  5. August 26, 2014 at 7:46 am

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 )

Connecting to %s

%d bloggers like this: