Xtext Tip: Adding importURI support

In a previous post I showed how to use the import namespace feature of Xtext. This feature helps to easily implement java like imports. However, Xtext also supports imports more in a C-like style, i.e. imports where a whole file imported, and its definitions made available to the current file.

For example, in our block language, it should be possible to import the name space from another Block and then use in our alias. We can for example define Block3 in file Block3.block

block Block3 {
		field myField1
		field myField2
}

And then use it in the file Block1.block as follows:

 import "Block3.block"

block Block1 {

	alias myAlias1 aliases Block3.myField1
}

The block language with import name space support

First, we modify the grammar to include an import statement:

grammar com.idiomaticsoft.dsl.block.Block with org.eclipse.xtext.common.Terminals

generate block "http://www.idiomaticsoft.com/dsl/block/Block"

Model:
	(imports+=Import)*
	blocks+=Block*;

Import:
	'import' importURI=STRING
;

Block:
	'block' name=ID'{' (members+=Member)* '}';

Member:
	Block | Field | Alias;

Field:
	'field' name=ID;

Alias:
	'alias' name=ID 'aliases' alias=[Member|MemberFQN];

MemberFQN:
	ID ("." ID)*;

This grammar includes a new Import rule that has importURI attribute. This attribute is one of those Xtext defaults, and to make the infrastructure work, you need to name your attribute like that.

The next step is to modify the runtime module to bind the right global scope provider. To do this, we modify the file as follows:

package com.idiomaticsoft.dsl.block;

import org.eclipse.xtext.scoping.IGlobalScopeProvider;
import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider;

/**
 * Use this class to register components to be used at runtime / without the
 * Equinox extension registry.
 */
public class BlockRuntimeModule extends AbstractBlockRuntimeModule {

	@Override
	public Class<? extends IGlobalScopeProvider> bindIGlobalScopeProvider() {
		return ImportUriGlobalScopeProvider.class;
	}

}

With theses two modifications, the scoping will work. Nevertheless, the import will not show an error when the file is not found. This can be corrected by modifying the MWE file to enable the use the ImportUriValidator, this is done as follows:

module com.idiomaticsoft.dsl.block.GenerateBlock

import org.eclipse.xtext.xtext.generator.*
import org.eclipse.xtext.xtext.generator.model.project.*

var rootPath = ".."

Workflow {
	
	component = XtextGenerator {
		configuration = {
			project = StandardProjectConfig {
				baseName = "com.idiomaticsoft.dsl.block"
				rootPath = rootPath
				runtimeTest = {
					enabled = true
				}
				eclipsePlugin = {
					enabled = true
				}
				eclipsePluginTest = {
					enabled = true
				}
				createEclipseMetaData = true
			}
			code = {
				encoding = "UTF-8"
				lineDelimiter = "\n"
				fileHeader = "/*\n * generated by Xtext \${version}\n */"
				preferXtendStubs = false
			}
		}
		
		
		
		language = StandardLanguage {
			name = "com.idiomaticsoft.dsl.block.Block"
			fileExtensions = "block"
			serializer = {
				generateStub = false
			}
			validator = {
				composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator"
				composedCheck = "org.eclipse.xtext.validation.ImportUriValidator"
				generateDeprecationValidation = true
			}
			generator = {
				generateXtendStub = true
			}
			junitSupport = {
				junitVersion = "5"
			}
		}
	}
}

Things to consider

The default for this feature are the following:

  1. If you want to have several importURI, then you need to use several Import rules. You cannot have a rule like importedNamespace+=ImportFQN. The default implementation expects this attribute to be a string, not a list.
  2. The imports of the rules are valid for all elements in the container and its children.

The full code for this example can be found here.

comments powered by Disqus