Does this sound familiar to you?
Oh, I need to rename this misspelled property within our domain model. Ok, so let's startup this big UML tool... and by the way let's get a new cup of coffee. Cool, it has been started up already. Grabbing the mouse, clicking through the different diagrams and graph visualizations... Ahhh, there is the name of the property right down there in the properties view. Let's change it, export it to XMI (...drinking coffee), starting the oAW generator (in a jiffy ;-)). Oh, it's not allowed for the property to be named that way, a constraint says that properties names should start with a lower case letter. Ok, let's change that and again reexport...
Some moments later, everything seems to works (tests are green). Ok let's check it in!
Oh someone else has also modified the model! Aaarrrgggh....
Think of this:
Want to change a properties name? Ok, open the respective text file, rename the properties name and save it. The editor complains about a violated constraint. Ok fix the issue, save again and generate. Check the changes into SVN (CVS). Oh, there is a conflict, ok, let's simply merge it using Diff.
And now? Let's have a cup of coffee :-)
Xtext is a textual DSL development framework. Providing the ability to describe your DSL using a simple EBNF notation. Xtext will create a parser, a metamodel and a specific Eclipse text editor for you!
Xtext is part of the openArchitectureWare SDK. An easy way to install is to download and unzip the "Eclipse 3.3 for RCP/Plug-in Developers" from the download site of Eclipse (http://www.eclipse.org/downloads/).
Afterwards, download the
org.openarchitectureware.all_in_one_feature-4.3.1.*.zip
release from our website and extract it to the directory where you have unzipped the
Eclipse release (i.e. the Eclipse installation directory).
Make sure that you start Eclipse with a Java VM Version greater than 5.0.
If you have been using Xtext prior to version 4.3, you need to re-generate your DSL editors in order to use the updated version 4.3. To do this, please perform the followng steps:
In your DSL project, open
generate.properties
and enable overwriting of
plug-in resource by setting
overwrite.pluginresources
to
true
Open your grammar definition. From the context menu, select Generate Xtext Artifacts in order to re-generate the DSL and the DSL editor.
Finally, open the context menu of your DSL project and select PDE Tools > Update classpath.... Tick all your DSL projects and click Finish.
Since 4.3.1 we allow URIs pointing to other models to be classpath
relative, which is a well-established method to access resources
uniformly from within a workspace, an Eclipse plug-in or a standalone
Java application. The URI resolution happens inside a special
XtextResourceSet
. As the classpath can be
different for each project, each editor instance now manages its own
XtextResourceSet.
To provide the classpath context, we had to change the signature
of
org::openarchitectureware::xtext::registry::Modelloader::load
to load(String uri, EObject contextElement)
where the resource set for the new resource is the resource set the
contextElement
is stored in. If you are using
cross-references to other models, you have to regenerate existing Xtext
artifacts.
A further extension load(String uri, EObject
contextElement, boolean forceLinking)
allows you to switch
off linking of the loaded resource. This is used by the editor to avoid
recursive loading of cross-referenced resources. Note that a resource
loaded without linking will not have any cross-references set.
If you didn't already do so, you should have a look at the ???. This will give you a good overview of how Xtext basically works. Come back to this document to find out about additional details.
At the heart of Xtext lies its grammar language. It is a lot like an extended Backus-Naur-Form, but it not only describes the concrete syntax, but can also be used to describe the abstract syntax (metamodel).
A grammar file consists of a list of so called Rules.
This is an example of a rule, describing something called an entity:
Entity : "entity" name=ID "{" (features+=Feature)+ "}";
Entity is both the name of the rule and the name of
the metatype corresponding to this rule. After the colon, the
description of the rule follows. A description is made up of
tokens. The first token is a
keyword token which says that a description of an
entity starts with the keyword entity
. A so-called
Assignment follows (name=ID
).
The left-hand side refers to a property of the metatype (in this
case it is the property name
of type
Entity). The right-hand side is a call to the built-in
token ID
. Which means Identifier and allows character
sequences of the form ('a-zA-Z_' ('a-zA-Z_0-9)*)
. The
parser will assign ('=
') the Identifier to the
specified property (name).
Then (enclosed in curly brackets, both are essentially keyword
tokens) one or more features can be declared
(features+=Feature)+
. This one, again, is an assignment.
This time, the token points to another rule (called
Feature) and each feature is added (note
+=
operator) to the reference of the entity called
features
.
The Feature rule itself could be described like this:
Feature : type=ID name=ID ";";
so, that the following description of an entity would be valid according to the grammar:
entity Customer { String name; String street; Integer age; Boolean isPremiumCustomer; }
Note that the types (String, Integer, Boolean) used in this description of a customer, are simple identifiers, they do not have been mapped to e.g. Java types or something else. So, according to the grammar, this would also be valid, so far:
entity X { X X; X X; X X; cjbdlfjerifuerfijerf dkjdhferifheirhf; }
As stated before, the grammar is not only used as input for the parser generator, but it is also used to compute a metamodel for your DSL. We will first talk about how an Xtext parser works in general, before we look at how a metamodel is being constructed.
The analysis of text is divided in two separate tasks: the lexing and the parsing.
The lexer is responsible of creating a sequence of tokens from a
character stream. Such tokens are identifiers, keywords, whitespace,
comments, operators, etc. Xtext comes with a set of
built-in lexer rules which can be extended or overwritten if necessary.
You have already seen some of them (e.g. ID
).
The parser gets the stream of tokens and creates a parse tree out of them. The type rules from the example are essentially parser rules.
Now, let us have a look at how the metamodel is constructed.
We have already seen, how type rules work in general. The name of the rule is used as name for the metatype generated by Xtext.
Each assignment token within an Xtext grammar is not only used to create a corresponding assignment action in the parser but also to compute the properties of the current metatype.
Properties can refer to the simple types such as String, Boolean or Integer as well as to other complex metatypes (i.e. other rules). It depends on the assignment operator and the type of the token on the right, what the type actually is.
There are three different assignment operators:
Standard assignment '=
' : The type will
be computed from the token on the right.
Boolean assignment '?=
' : The type will
be Boolean.
Add assignment '+=
' : The type will be
List. The inner type of the list depends on the type
returned by the token on the right.
Example:
Entity : (isAbstract?="abstract")? "entity" name=ID "{" (features+=Feature)* "}";
The metatype Entity will have three properties:
Boolean
isAbstract
String
name
List[Feature]
features
Parsers construct parse trees not graphs. This means that the outcome of a parser has no crossreferences only so called containment references (composition).
In order to implement crosslinks in the model, one usually has to add third task: the linking. However, Xtext supports specifying the linking information in the grammar, so that the metamodel contains cross references and the generated linker links the model elements automatically (for most cases). Linking semantic can be arbitrary complex. Xtext generates a default semantic (find by id) which can be selectively overwritten. We will see, how this can be done, later in this document.
Let us now see in detail, what the grammar language supports:
Entity : "entity" name=ID ("extends" superType=[Entity])? "{" (features+=Feature)* "}";
Have a look at the optional extends
clause. The rule name Entity on the right is surrounded
by squared parenthesis. That's it.
By default, the parser expects an ID
to point
to the referred element. If you want another kind of token to act as a
reference, you can optionally specify the type of token you want to
use, separated by a vertical bar:
... ("extends" superType=[Entity|MyComplexTokenRule])? ...
Where MyComplexTokenRule must be either a NativeLexerRule or a StringRule (explanation follows).
We have seen how to define simple concrete metatypes and its features. One can also define type hierarchies using the grammar language of Xtext. Sometimes you want to abstract rules, in order to let a feature contain elements of different types.
We have seen the Feature rule in the example. If you would like to have two different kinds of Feature (e.g. Attribute and Reference), you could create an abstract type rule like this:
Feature : Attribute | Reference; Attribute : type=ID name=ID ";"; Reference : "ref" (containment?="+")? type=ID name=ID ("<->" oppositeName=ID)? ";";
The transformation creating the metamodel automatically
normalizes the type hierarchy. This means that properties defined in
all subtypes will automatically be moved to the common supertype. In
this case, the abstract type Feature would be created
containing the two features (name
and
type
). Attribute and
Reference would be subtypes of Feature,
inheriting those properties.
It is also possible to define concrete supertypes like this:
Feature : type=ID name=ID ";" | Reference; Reference : "ref" (containment?="+")? type=ID name=ID ("<->" oppositeName=ID)? ";";
In this case Feature would not be abstract, but it would be the supertype of Reference.
If you need multiple inheritance you can simply add an abstract rule. Such a rule must not be called from another rule.
Example:
Model : TypeA TypeA TypeC; TypeA : "A" | TypeB; TypeB : "B"; TypeC : "C"; CommonSuper : TypeB | TypeC; // just for the type hierarchy
The resulting type hierarchy will look like this:
- Model
- TypeA
- TypeB extends TypeA, CommonSuper
- TypeC extends CommonSuper
- CommonSuper
The enum rule is used to define enumerations. For example, if you would like to hardwire the possible data types for attributes into the language, you could just write:
Attribute : type=DataType name=ID ";"; Enum DataType : String="string" | Integer="int" | Boolean="bool";
In this case, this would be valid:
entity Customer { string name; string street; int age; bool isPremiumCustomer; }
However, this would not be valid:
entity Customer { X name; // type X is not known String street; // type String is not known (case sensitivity!) }
Xtext provides built-in tokens. We have already seen the IdentifierToken and the KeywordToken.
Sometimes, this is not sufficient, so we might want to create our
own tokens. Therefore, we have the so-called String
rule , which is implemented as a parser rule (it is not a lexer
rule).
Example
String JavaIdentifier : ID ("." ID)*;
The contents of the String
rule is simply
concatenated and returned as a string. One can refer to a
String
rule in the same manner we refer to any other
rule.
So, just in case you want to declare data types using your DSL and therein specify how it is mapped to Java (not platform independent, of course, but expressive and pragmatic), you could do so using the following rules:
Attribute : type=DataType name=ID ";"; DataType : "datatype" name=ID "mappedto" javaType=JavaIdentifier; String JavaIdentifier : ID ("." ID)*;
A respective model could look like this:
entity Customer { string name; string street; int age; bool isPremiumCustomer; } datatype string mappedto java.util.String datatype int mappedto int datatype bool mappedto boolean
You could of course point to a custom type mapping implementation, if you need to support multiple platforms (like e.g. SQL, WSDL, Java,...). Additionally, you should consider to define the data types in a separate file, so the users of your DSL can import and use them.
As mentioned before we Xtext provides some common built-in lexer rules. Let us start with the two simplest ones.
All static characters or words, known as
keywords, can be specified directly in the grammar
using the usual string literal syntax. We never need the value of
keyword because we already know it since it is static. But sometimes,
there are optional keywords like e.g. the modifiers in Java. The
existence of a keyword can be assigned using the boolean assignment
operator "?=
". However, if you want to assign the
value of the keyword to a property just use the assignment operator
'=
'.
Example:
Entity : (abstract?="abstract")? "entity" name=ID ("<" extends=ID)? "{" (features+=Feature)* "}";
With this the type Entity will have the boolean
property abstract
, which is set to
true
if the respective keyword has been specified for
an entity. (The extends
part is added, because an
abstract entity would not make sense without inheritance).
Note that operators such as '<
' in the example
are keywords, too.
We also have seen the identifier token (ID
).
This is the token rule expressed in AntLR grammar syntax:
('^')?('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*
So, an identifier is a word starting with a character or an
underscore, optionally followed by further characters, underscores, or
digits. The return value of the ID
token is a
String
. So, if you use the usual assignment
operator "=
", the feature the value is assigned to
will be of type String
. You could also use the
boolean operator (?=
) and the type will be
Boolean
.
If an identifier conflicts with a keyword or another lexer rule,
it can be escaped with the " ^
" character.
There is also a built-in String Token. Here is an example:
Attribute : type=DataType name=ID (description=STRING)? ";";
With this token, one can optionally specify a description for an entity like this:
entity Customer { string name ; string street "should include the street number, too."; int age; bool isPremiumCustomer; }
By default, the two string literal syntaxes "my text" and 'my text' are supported. Note that, unlike in Java, also multi-line strings are supported:
entity Customer { string name ; string street "should include the street number, too. And if you do not want to specify it, you should consider specifying it somewhere else."; int age; bool isPremiumCustomer; }
Sometimes, you want to assign Integers. Xtext supports it with the built-in lexer rule INT.
Index: "#" index=INT;
The default pattern is:
('-')?('0'..'9')+
It can be overwritten (see next section), but you have to take care, that the coercion (Integer.valueOf(String) is used) works.
If you want to allow inclusion of and references to other models,
use the URI
token, e.g.
Import: 'import' model=URI ';';
From the parser's point of view, an URI
token is just a String literal enclosed in quotes. The Runtime interprets
this string as the Uniform Resource Identifier (URI)
of another model file whose elements can be referenced from the importing model.
With the above grammar rule example, you can allow references to elements of the
model file refModel.model
by adding a line
import "platform:/resource/myproject/model/refModel.model";
in your model. See the section called “Cross-References to Models of the Same DSL/Metamodel” and the section called “Cross-references to Models of a Different DSL/Metamodel” for examples on model import.
There are two different kinds of comments automatically available in any Xtext language.
// single-line comments and /* mutli-line comments */
Note that those comments are ignored by the language parser by default (i.e. they are not contained in the AST returned from the parser).
If you do not want ignored comments, or you want to have a
different syntax, you need to overwrite the default implementation (The
name of the corresponding comment rule is SL_COMMENT
for single-line comments, i.e. ML_COMMENT
for
multi-line comments).
Every textual model contains whitespace. As most languages simply
ignore whitespace, Xtext does so by default, too.
If you want to have semantic whitespace in your language (like e.g.
Python), you have to overwrite the built-in whitespace rule (its name is
WS
).
If you want to overwrite one or more of the built-in lexer rules or add an additional one, the so-called native rule is your friend.
Example:
// overwriting SL_COMMENTS we do not want Java syntax (//) but bash syntax (#) Native SL_COMMENT : "'#' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;}"; // fully qualified names as a lexer rule Native FQN : "ID ('.' ID)*";
The syntax is :
"Native" ID ":" STRING // The string contains a valid ANTLR 3 lexer rule expression ";" // (see http://www.antlr.org/wiki/display/ANTLR3/ANTLR+v3+documentation)
It is assumed that you have used the Xtext
project wizard, and that you have successfully written an
Xtext grammar file describing your little language.
Next up you need to start the generator of Xtext in order to get a parser,
a metamodel and an editor. To do so. just right-click the workflow file
(*.oaw)
located next to the grammar file and choose
Run As+ (in Eclipse, of course). The generator will read the
grammar file in and create a bunch of files. Some of them located in the
src-gen
directories others located in the
src
directory.
IMPORTANT : You should now (after first generation) open the *.properties file and set the "overwritePluginRes=true" option to false!
The generator can be configured with the following properties
defined in
:generate.properties
property name (default) | description |
---|---|
grammar | The grammar file to generate the code from. |
debug.grammar (false) | Specifies whether a debug grammar should be generated. A debug grammar is an AntLR grammar without any action code, so it can be interpreted within AntLRWorks. |
language.name | The name of the DSL. Is used throughout the generated code |
language.nsURI ("http://www.oaw.org/xtext/dsl/${language.name}") | A unique URI used within the derived ecore package. |
language.fileextension ("${language.name}") | The file extension the generated editor is configured for. |
overwrite.pluginresources ("false") | If this is set to true the plugin resources (META-INF/MANIFEST.MF, plugin.xml) will be overwritten by the generator!!! |
wipeout.src-gen ("true") | Specifies whether the src-gen folders should be deleted before generation. |
generator.project.name ("") | If this property is set to something, a project wizard will be generated referencing the generator project. |
workspace.dir | The root of the workspace. |
core.project.name | name of the main project |
core.project.src ("src/") | src folder of the main project |
core.project.src.gen ("src-gen/") | src-gen folder of the main project |
editor.generate ("true") | should an editor be generated at all |
editor.project.name ("${core.project.name}.editor") | name of the editor project |
editor.project.src ("${core.project.src}") | src folder of the editor project |
editor.project.src.gen ("${core.project.src.gen}") | src-gen folder of the editor project |
Any textual artifacts located in the src
directory (of any project) will always stay untouched. The generator
just creates them the first time when they do not exist.
Files generated to the src-gen
directory
should never be touched! The whole directory will be wiped out the next
time one starts the generator.
Xtext generates artifact into two different projects.
The name of the main project can be specified in the wizard. This project contains the main language artifacts and is 100% eclipse independent. The default locations of the most important resources are:
Table 23. Resources of the main project
Location | Description |
---|---|
src/[dslname].xtxt | The grammar file, containing the grammar rules describing your DSL |
src/generate.oaw | The workflow file for the Xtext generator. |
src/generator.properties | Properties passed to the Xtext generator |
src/[base.package.name]/Checks.chk | The Check-file used by the parser and within the editor. Used to add semantic constraints to your language. |
src-gen/[base.package.name]/GenChecks.chk | The generated Check-file contains checks automatically derived from the grammar. |
src/[base.package.name]/Extensions.ext | The Extension-file is used (imported) by all
another extensions and check files. It reexports the
extensions from |
src-gen/[base.package.name]/GenExtensions.ext | generated extensions (reexported by
|
src/[base.package.name]/Linking.ext | Used by |
src-gen/[base.package.name]/GenLinking.ext | Contains the default linking semantic for each cross reference. Example: Void link_featureName(my::MetaType this) : (let ents = this.allVisibleElements().typeSelect(my::ReferredType) : this.setFeatureName(ents.select(e|e.id() == this.parsed_featureName).first()) ); This code means the following: Get all instances of the referred type using the allVisibleElements() extension. Select the first one where the id() equals the parsed value (by default an identifier). Both extensions,
id() and
allVisibleElements()) can be overwritten or
specialized in the |
src-gen/[base.package.name]/[dslname].ecore | Metamodel derived from the grammar |
src-gen/[base.package.name]/parser/* | Generated AntLR parser artifacts |
The name of the editor project is derived from the name of the
main project by appending the suffix .editor
to
it. The editor project contains the informations specific to the
Eclipse text editor. Note that it uses a generic xtext.editor plugin,
which does most of the job. These are the most important resources:
Table 24. Resources of the editor
Location | Description |
---|---|
src/[base.package.name]/[dslname]EditorExtensions.ext | The Xtend-file is used by the outline view. If you want to customize the labels of the outline view, you can do that here. |
src-gen/[base.package.name]/[dslname]Utilities.java | Contains all the important DSL-specific information. You should subclass it in order to overwrite the default behaviours. |
src/[base.package.name]/[dslname]EditorPlugin.java | If you have subclassed the *Utilities class, make sure to change the respective instantiation here. |
The name of the generator project is derived from the name of the main project
by appending the suffix .generator
to it. The generator
project is intended to contain all needed generator resources such as
Xpand templates, platform-specific
Xtend files etc..
These are the most important resources:
Table 25. Resources of the generator
Location | Description |
---|---|
src/[base.package.name]/generator.oaw |
The generators workflow preconfigured with the generated DSL parser and the Xpand component. As this is just a proposal, feel free to change/add the workflow as you see fit. |
src-gen/[base.package.name]/Main.xpt |
The proposed Xpand template file. |
The generated editor supports a number of features known from other eclipse editors. Although most of them have a decent default implementation, we will see how to tweak and enhance each of them.
Code Completion is controlled using oAW extensions. The default implementation provides keyword proposals as well as proposals for cross references.
Have a look at the extension file
ContentAssist.ext
. A comment in this file describes
how to customize the default behaviour:
/* * There are two types of extensions one can define * * 1) completeMetaType_feature(ModelElement ele, String prefix) * This one is called for assignments only. It gets the underlying Modelelement and the current * prefix passed in. * * 2) completeMetaType(xtext::Element grammarEle, ModelElement ele, String prefix) * This one gets the grammarElement which should be completed passed in as the first parameter. * an xtext::Element can be of the following types : * - xtext::RuleName (a call to a lexer rule (e.g. ID)), * - xtext::Keyword, * - xtext::Assignment * * Overwrite rules are as follows: * 1) if the first one returns null for a given xtext::Assignment or does not exist the second one * is called. * 2) if the second one returns null for a given xtext::Keyword or does not exist a default keyword * proposal will be added. * * Note that only propals with wich match (case-in-sensitive) the current prefix will be proposed * in the editor */
The implementation for the navigation actions is implemented via
extensions, too. As for Code completion the same pattern applies: There
is a GenNavigation.ext
extension file in the
src-gen
folder which can be overwritten or
specialized using the Navigation.ext
file in the
src
folder (reexporting the generated
extensions).
There are two different actions supported by Xtext:
This action can be invoked via Ctrl-Shift-G or via the corresponding action context menu. The default implementation returns the cross references for a model element.
The signature to overwrite / specialize is:
List[UIContentNode] findReferences(String s, Object grammarelement, Object element) : ...;
A
UIContentNode
is a metaclass used by
Xtext. An UIContentNode represents an element
visualized in Eclipse.
Here is the declaration of UIContentNode
(pseudo code):
package tree; eclass UIContentNode { UIContentNode parent; UIContentNode[] children; String label; String image; emf::EObject context; }
A content node can have children and / or a parent (the tree
structure is not used for find references). The label is used for the
label in Eclipse, and the image points to an image relative to the
icons
folder in the editor project. The icon
instances are automatically cached and managed.
The context points to the actual model element. This is used to get the file, line and offset of the declaration. If you do not fill it, you cannot click on the item, in order to get to it.
This action can be invoked via F3 as well as by holding CTRL, hovering over an identifier and left click the mouse.
The default implementation goes to the declaration of a cross reference. You can implement or overwrite this action for all grammar elements.
emf::EObject findDeclaration(String identifier, emf::EObject grammarElement, emf::EObject modelElement) : ...;
Have a look at the generated extensions, to see how it works.
The outline view is constructed using a tree of
UIContentNode
objects (see above).
Each time the outline view is created, the following extension is called:
UIContentNode outlineTree(emf::EObject model)
It
is expected to be declared in Outline.ext
, which by
default exports a generic implementation from
GenOutline.ext
(the same pattern again).
You can either reuse the generic extension and just provide a
label() and image() extension
for your model elements (should be added in
EditorExtensions.ext
).
However, if you want to control the structure of the outline tree
you can overwrite the extension outlineTree(emf::EObject
model) in Outline.ext
.
You can define multiple outlines for a single DSL, each representing
a different viewpoint on your model. Each viewpoint needs has a unique name.
Override the getViewpoints()
extension to return a
list of all viewpoint names. You can customize each viewpoint using the same
extensions as above suffixed with the viewpoint's name (spaces are replaced
by underscores. Example:
List[String] viewpoints() : { "Entities Only" }; UIContentNode outlineTree_Entities_Only(emf::EObject model) : let x = model.createContentNode() : x.children.addAll(model.eContents.outlineTree_Entities_Only()) // return the node, not its children -> x; UIContentNode outlineTree_Entities_Only(Model model) : let x = model.createContentNode() : // add child nodes for elements of type Entity only x.children.addAll(model.types.typeSelect(Entity).outlineTree_Entities()) -> x; create UIContentNode createContentNode(emf::EObject model) : setLabel(model.label()) -> setImage(model.image()) -> setContext(model);
You can switch the viewpoints in the running editor by means of the drop-down menu behind the small triangle symbol in the action bar of the outline view.
The default syntax highlighting distinguishes between comments, string literals, keywords, and the rest.
You can adjust the specific display styles for keywords with regard
to the context they appear in by overriding the
fontstyle(String keyword, EObject metaClassName)
extension
in the Style.ext
extension file. The predefined method
createFontstyle
will help you creating the
FontStyle
object, e.g.
FontStyle fontstyle(String keyword, Node node) : node.eContainer.metaType==Node ? createFontStyle(true, false, false, false, createColor(200,200,200), createColor(255,255,255)) : createFontStyle(true, false, false, false, createColor(255,0,0), createColor(255,255,255)) ;
If you just want to specify which words to be coloured as keywords
you can extend the
[basepackage.][Languagename]Utilities.java
class
from the editor plugin. You need to overwrite the following
method.
public String[] allKeywords()
Do not change the Utilities
class
directly, because it will be overwritten the next time your start
the generator.
Each String
returned by the method
represents a keyword.
The utilities method is created within the
[LanguageName]EditorPlugin.java
. So, make sure that
you change the following lines, as well:
// OLD -> private MyLangUtilities utilities = new MyLangUtilities(); private MyCustomUtilities utilities = new MyCustomUtilities(); public LanguageUtilities getUtilities() { return utilities; }
This part of the documentation deals with the discussion and solution of different requirements and problems.
Since version 4.3, Xtext provides first class support for cross-references to elements in other model files. This allows to spread a single model across several files or to reuse parts of a model by referencing it. The former mechanism using the built-in model registry does not work any longer.
The following example illustrates how to implement cross-resource
references to models of the same DSL. Referencing elements of a foreign
DSLs is shown in
the section called “Cross-references to Models of a Different DSL/Metamodel”. First of all,
you have to enable model import in your DSLs grammar. This is achieved
by declaring a rule that matches the URI
token.
Model: (imports+=Import)* (elements+=Element)*; // declare an import statement by means of a rule matching the URI token Import: 'import' model=URI; Element: 'element' name=ID '{' (ref+=Reference)* '}'; Reference: 'ref' element=[Element];
This way, we can use the import
keyword followed by a URI
in our models to enable referencing. Note that import
is
just an arbitrary keyword and all the magic comes with the
URI
Token. It loads all elements from the given model and
makes them available in the allVisibleElements()
extension.
And how does it look like on the model level? Consider the following model
file:
// refModel.dsl Element externalElement { }
To reference the element externalElement
in another file
you'll just have to import the refModel.dsl
and then reference
// model.dsl // import the model to be referenced by matching the URI token import "platform:/resource/myplugin/model/refModel.dsl" Element internalElement { } Element myElement { ref internalElement // reference by ID ref externalElement // reference by ID, as if it was in the same file }
In some cases you may want to use Xtext to create a new concrete textual syntax for an existing Ecore model. You'll have to provide a way to make Xtext's rules instantiate existing EClasses rather than the generated ones.
The Ecore model to be referenced must reside in a plug-in that is deployed in your eclipse.
This plug-in must register the Ecore model to the
org.eclipse.emf.ecore.generated_package
or
org.eclipse.emf.ecore.dynamic_package
extension. Additionally,
load the imported metamodel to the generation workflow using the
StandaloneSetup
component:
<workflow> <property file='generate.properties'/> <bean class="org.eclipse.mwe.emf.StandaloneSetup"> <registerGeneratedEPackage value="my.MyPackage"/> </bean> <component file='org/openarchitectureware/xtext/Generator.oaw' inheritAll='true'/> </workflow>
The next step is to make the existing Ecore model available to your Xtext plug-in.
Use the importModel
statement followed by the nsURI of the model for that
purpose, e.g.
importMetamodel "http://www.oaw.org/entity" as refModel;
imports entity
and makes its elements available in the namespace
alias refModel
.
Now you make a parser rule instantiate an EClass from the imported model by adding the
qualified class name in brackets after the rule name. Assuming you want to instantiate
an EClass named Entity
which has an attribute
name
, simply write
MyRule [refModel::Entity] : name=ID;
If all rules in your grammar instantiate foreign EClasses, the automatic
generation of the Ecore model becomes obsolete. Switch it off using the
preventMMGeneration
directive at the beginning of your
grammar file:
preventMMGeneration importMetamodel "http://www.oaw.org/entity" as refModel;
These are the steps to take, if you want to implement cross-references to models that comply to a DSL that is different from the referencing model's DSL.
Follow the above instructions to make the referenced metamodel/DSL available in your grammar. The outgoing reference is defined - analogous to the instantiation of foreign model element - using brackets, e.g.
Referer : (entities+=[refModel::Entity])*;
If you want to extend an existing grammar or split a large grammar into multiple pieces, you
can use the importGrammar
statement, e.g.
importGrammar "platform:/resouce/my.dsl/src/mydsl.xtxt"
Imported rules can be overridden simply by redefinition. Note that for the Xtext artifact generation to succeed, the input grammar combined with its imported grammars must not contain unused rules.