Xtext cross references and scoping – an overview (Part 3)

Overview

Default Imports

Motivation

A question regularly asked is how to implement built in types for your DSL. Enums or particular keywords for those types, i.e. hard coding them in the grammar, is possible, but the library approach is considered good (if not best) practice. The basic idea is shipping a model file containing the built in types along with the rest of the language infrastructure, as usually it is possible to model them just like a user defined type.

Of course, it would be nice if those types were visible without the user having to explicitly import that model file or the corresponding namespace.

The Approach

The solution proposed here assumes that the importing project has a “dependency” to the jar containing the default model (for want of a better term). That is, the default model is on the classpath of the importing model. One possibility is that the default model is contained in a “library” plugin project and the importing plugin has a dependency to the former.

URI Imports

If you are using the import uri mechanism, i.e. you are explicitly importing a particular model file, you can hook into the ImportUriGlobalScopeProvider. You simply add the default imports to the set of imports picked up from the model itself

public class MyImportUriGlobalScopeProvider extends
    ImportUriGlobalScopeProvider {

  @Override
  protected LinkedHashSet<URI> getImportedUris(EObject context) {
    LinkedHashSet<URI> temp = super.getImportedUris(context);
    temp.add(URI.createURI("classpath:/the/library/package/defaults.mydsl"));
    return temp;
  }
}

You could make this change known analogous to the way the ImportUriGlobalScopeProvider is bound in the AbstractXRuntimeModule

@Override
public void configureIScopeProviderDelegate(com.google.inject.Binder binder) {
  binder.bind(org.eclipse.xtext.scoping.IScopeProvider.class).
  annotatedWith(com.google.inject.name.Names.named(
  "org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider.delegate
  )).to(MyImportUriGlobalScopeProvider.class);}

Namespace Imports

When using namespace imports (so that an element can be referenced via its simple name rather than the fully qualified one) the hook is to be found in the ImportedNamespaceAwareLocalScopeProvider. In Xtext 1.x something like the following snippet does the trick.

public class MyImportedNamespaceAwareLocalScopeProvider extends
    ImportedNamespaceAwareLocalScopeProvider {

  @Override
  protected Set<ImportNormalizer> getImportNormalizer(EObject context) {
    Set<ImportNormalizer> temp = super.getImportNormalizer(context);
    temp.add(new ImportNormalizer(
      new QualifiedName("builtin.types.namespace.*")
    ));
    return temp;
  }
}

In Xtext 2 there is a dedicated method for implicit imports (also the QualifiedName-API has changed).

public class MyImportedNamespaceAwareLocalScopeProvider extends
    ImportedNamespaceAwareLocalScopeProvider {

  @Override
  protected List<ImportNormalizer> getImplicitImports(boolean ignoreCase) {
    List<ImportNormalizer> temp=new ArrayList();
    temp.add(new ImportNormalizer(
      QualifiedName.create("builtin","types","namespace"),
      true, ignoreCase));
    return temp;
  }
}

In both cases the change is made know in the runtime module as above, now binding MyImportedNamespaceAwareLocalScopeProvider.

It should be clear that you can also use this approach for dealing with “split packages”, i.e. spreading the same namespace over several files. Usually, you would have to import “your own” namespace in order to refer to elements from another file using their simple names. Note that the getImplicitImports method in the Xtext 2 example is not the right hook now, as there you don’t have access to the context element (for obtaining its namespace).

Leave a Reply