Improvements to the DSL
Now that we have a working DSL, we can do some improvements and modifications to the grammar.
After every modification to to the grammar, as we said in the section The Xtext generator, we must run the MWE2 workflow so that Xtext will generate the new ANTLR parser and the updated EMF classes.
First of all, while experimenting with the editor, you might have noted that
MyEntity[] myattribute;
is a valid statement of our DSL, while the one below (note the spaces between the square brackets):
MyEntity[ ] myattribute;
produces a syntax error.
This is not good, since we do not want spaces to be relevant (although there are languages such as Python and Haskell where spaces are indeed relevant).
The problem is due to the fact that, in the Attribute
rule, we specified []
, thus, no space is allowed between the square brackets; we can modify the rule as follows:
Attribute: type=[Entity] (array?='[' ']')? name=ID ';';
Since we split the two square brackets into two separate tokens, spaces between the brackets are allowed in the editor. Indeed, spaces are automatically discarded, unless they are explicit in the token definition. In general, spaces in keywords should be avoided.
Note
Xtext 2.8 introduced support for whitespace-aware languages, where whitespaces, for example indentations, are used to specify the structure of programs (such as in Python). We will not describe this feature in this book. We refer the interested reader to the Xtext documentation and to the example language Home Automation that ships with Xtext.
We can further refine the array specification in our DSL by allowing an optional length:
Attribute:
type=[Entity] (array ?='[' (length=INT)? ']')? name=ID ';';
There is no rule defining INT
in our grammar—we inherit this rule from the grammar Terminals
. As you can imagine, INT
requires a positive integer literal, thus the length
feature in our model will have an integer type as well. Since the length
feature is optional (note the question mark), both the following attribute
definitions will be valid statements of our DSL:
MyEntity[ ] a; MyEntity[10] b;
When the length is not specified, the length
feature will hold the default integer value (0
).
Dealing with types
The way we defined the concept of an AttributeType
is not conceptually correct, since the array
feature is part of Attribute
when it should be something that concerns only the type of Attribute
.
We can then specify the concept of AttributeType
in a separate rule, which will also result in a new EMF class in our model
:
Attribute:
type=AttributeType name=ID ';';
AttributeType:
entity=[Entity] (array ?='[' (length=INT)? ']')?;
If you run the MWE2 workflow, you will note no difference in your DSL editor, but the metamodel for your AST has changed. For example, consider this part of EntitiesEMFExample
that we showed previously:
Attribute attribute = factory.createAttribute();
attribute.setName("myattribute");
attribute.setArray(false);
attribute.setType(superEntity);
This is no longer valid Java code and has to be changed as follows:
Attribute attribute = factory.createAttribute();
attribute.setName("myattribute");
AttributeType attributeType = factory.createAttributeType();
attributeType.setArray(false);
attributeType.setLength(10);
attributeType.setEntity(superEntity);
attribute.setType(attributeType);
As a further enhancement to our DSL, we would like to have some basic types—at the moment, only entities can be used as types. For instance, let's assume that our DSL provides three basic types—string
, int
, and boolean
. Therefore, a basic type is represented by its literal representation. On the contrary, an EntityType
(the only type concept we have used up to now) is actually a reference to an existing Entity
. Furthermore, we want to be able to declare arrays both of BasicType
and of EntityType
.
For these reasons, the array
feature still belongs to AttributeType
, but we need to abstract over elementType
. Thus, we modify the grammar as follows:
AttributeType:
elementType=ElementType (array ?='[' (length=INT)? ']')?;
ElementType:
BasicType | EntityType;
BasicType:
typeName=('string'|'int'|'boolean');
EntityType:
entity=[Entity];
As you can see, we introduce a rule, ElementType
, which in turn relies on two alternative mutually exclusive rules: BasicType
and EntityType
. Alternative rules are separated using the pipe operator |
. Note that rules such as ElementType
, which delegates to other alternative rules, implicitly introduce an inheritance relation in the generated EMF classes. Thus, both BasicType
and EntityType
inherit from ElementType
. In the BasicType
rule, the string feature typeName
will contain the corresponding keyword entered in the program.
After running the MWE2 workflow, you can try your editor and see that now you can also use the three basic types. Furthermore, in a context where a type specification is expected, the content assist will present all possible elementType
alternatives, both entity
types and BasicType
. Refer to the following screenshot:
Note
In general, it is better not to rely on hardcoded alternatives in the grammar such as the BasicType
we used previously. Instead, it would be better to provide a standard library for the DSL with some predefined types and functions. An application of this technique will be presented in Chapter 10, Scoping.
Now that our EMF model has changed again, we need to change our EntitiesEMFExample
class accordingly as follows:
Attribute attribute = factory.createAttribute();
attribute.setName("myattribute");
AttributeType attributeType = factory.createAttributeType();
attributeType.setArray(false);
attributeType.setLength(10);
EntityType entityType = factory.createEntityType();
entityType.setEntity(superEntity);
attributeType.setElementType(entityType);
attribute.setType(attributeType);
If you reopen the generated Entities.ecore, you can see the current metamodel for the AST of our DSL (note the inheritance relations—BasicType
and EntityType
inherit from ElementType
). Refer to the following screenshot:shot:
The preceding EMF metamodel can also be represented with a graphical notation following UML-like conventions. Refer to the following diagram:
We now have enough features in the Entities DSL to start dealing with additional tasks typical of language implementation. We will be using this example DSL in the upcoming chapters.