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:
- If you want to have several
importURI
, then you need to use severalImport
rules. You cannot have a rule likeimportedNamespace+=ImportFQN
. The default implementation expects this attribute to be a string, not a list. - 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.