Xtext Tip: How to use fully qualified names in your language
Xtext allows to easily add support for fully qualified names in your language. The full code for this example can be found here.
The basic block language
To illustrate this feature I will use a simple language to represent blocks. Each block has a name and can contain three members:
- Fields, which are basically a name,
- Blocks, which are blocks contained in another block
- Aliases, which allow to give another name to another member
This is an example of our block language:
block Block1 {
field field1
field field2
block SubBlock {
field field2
}
alias field3 aliases field2
}
block Block2 {
alias field1 aliases Block1.SubBlock.field2 // this does not compile because it aliases an FQN
}
This is the grammar that we used to generate it:
grammar com.idiomaticsoft.dsl.block.Block with org.eclipse.xtext.common.Terminals
generate block "http://www.idiomaticsoft.com/dsl/block/Block"
Model:
blocks+=Block*;
Block:
'block' name=ID '{' (members+=Member)* '}';
Member:
Block | Field | Alias;
Field:
'field' name=ID;
Alias:
'alias' name=ID 'aliases' alias=[Member];
The block language with FQN support
Xtext is based around certain conventions. By convention (this convention can be changed), Xtext assumes that FQN of a member is the concatenation of the names of the containers using a dot (.
) as separator. For example, the FQN of the field2
field in the Subblock
container would be Block1.SubBlock.field
.
Hence, if we want to have the possibility to have a reference named with an FQN, then we need to do two things:
- Create a rule that matches an FQN.
- Use the rule to tell Xtext that a given reference can be referenced by the FQN.
This can be seen in the following grammar:
grammar com.idiomaticsoft.dsl.block.Block with org.eclipse.xtext.common.Terminals
generate block "http://www.idiomaticsoft.com/dsl/block/Block"
Model:
blocks+=Block*;
Block:
'block' name=ID '{' (members+=Member)* '}';
Member:
Block | Field | Alias;
Field:
'field' name=ID;
Alias:
'alias' name=ID 'aliases' alias=[Member|MemberFQN];
MemberFQN: // this rules matches either an FQN or a simple identifier
ID ("." ID)*;
Because of the convention, changing the grammar is the only step necessary to support FQNs like this.
Limitations
This only works if the FQN is a set of segments separated by dots, and where each segment correspond to the containing object. If the FQN has another structure, further customization is needed.