blob: ce8c926d238963d92c7810f504ee8b7fb791b1c6 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Xtext - 15 Minutes Tutorial</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description"
content="The website of Eclipse Xtext, an open-source framework for development of programming languages and domain-specific languages">
<meta name="author" content="Sven Efftinge">
<meta name="author" content="Miro Spoenemann">
<!-- styles -->
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="/Xtext/images/favicon.png">
<link href="/Xtext/css/bootstrap.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/bootstrap-responsive.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/shield-responsive.css" rel="stylesheet" type='text/css'>
<link href='/Xtext/css/fonts.css' rel='stylesheet' type='text/css'>
<link href="/Xtext/css/prettyPhoto.css" rel="stylesheet" media="screen" type='text/css'>
<link href="/Xtext/css/prettify.css" type="text/css" rel="stylesheet"/>
<link href="/Xtext/css/style.css" rel="stylesheet" type='text/css'>
<!-- cover flow -->
<link href="/Xtext/css/coverflow.css" rel="stylesheet" type='text/css'>
<!--[if lt IE 9]>
<link href="/css/iebugs.css" rel="stylesheet" type='text/css'>
<![endif]-->
<!-- BEGIN Cookie Consent
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.0.3/cookieconsent.min.css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.0.3/cookieconsent.min.js"></script>
<script>
window.addEventListener("load", function(){
window.cookieconsent.initialise({
"palette": {
"popup": {
"background": "#000"
},
"button": {
"background": "#f1d600"
}
},
"theme": "edgeless",
"type": "opt-in",
onInitialise: function (status) {
var type = this.options.type;
var didConsent = this.hasConsented();
if (type == 'opt-in' && didConsent) {
// TODO: enable cookies
}
if (type == 'opt-out' && !didConsent) {
// TODO: disable cookies
}
},
onStatusChange: function(status, chosenBefore) {
var type = this.options.type;
var didConsent = this.hasConsented();
if (type == 'opt-in' && didConsent) {
// TODO: enable cookies
}
if (type == 'opt-out' && !didConsent) {
// TODO: disable cookies
}
},
onRevokeChoice: function() {
var type = this.options.type;
if (type == 'opt-in') {
// TODO: disable cookies
}
if (type == 'opt-out') {
// TODO: enable cookies
}
},
"content": {
"href": "http://www.eclipse.org/legal/privacy.php"
}
})});
</script>
END Cookie Consent -->
</head>
<body>
<header class="site-header">
<!-- Navbar -->
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse"
data-target=".nav-collapse"> <span class="icon-bar"></span> <span
class="icon-bar"></span> <span class="icon-bar"></span>
</a> <a class="brand" href="/Xtext/index.html"></a>
<div class="nav-collapse collapse" style="height: 0px;">
<ul class="nav">
<!--li ><a href="/Xtext/news.html">News</a></li-->
<li ><a href="/Xtext/download.html">Download</a></li>
<li ><a href="/Xtext/documentation/index.html">Documentation</a></li>
<li ><a href="/Xtext/community.html">Community</a></li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Support &amp; Trainings<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="https://www.itemis.com/en/xtext/support-and-team/" target="_blank">itemis</a></li>
<li><a href="https://typefox.io/trainings-2" target="_blank">TypeFox</a></li>
</ul>
</li>
<li ><a href="http://xtend-lang.org">Xtend</a></li>
</ul>
<!--div class="nav pull-right">
<li ><a><iframe src="https://ghbtns.com/github-btn.html?user=eclipse&repo=xtext&type=star&count=true" frameborder="0" scrolling="0" width="170px" height="20px"></iframe></a></li>
</div-->
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<!-- Navbar End -->
</header>
<div class="page-content">
<script>
function startSearch(event) {
if (event.keyCode == 13) {
var q = 'site:eclipse.org/Xtext/documentation+' + event.target.value;
window.open('https://www.google.com/search?q=' + q, "_self");
}
}
</script>
<div class="wrapper">
<div id="page">
<div class="inner">
<div id="maincontainer" class="container">
<span class="edit-on-github pull-right">
<a href="https://github.com/eclipse/xtext/edit/website-published/xtext-website/documentation/102_domainmodelwalkthrough.md">Edit on Github</a>
</span>
<div class="span3" style="margin-left: 0px;">
<div class="search-bar">
<img src="/Xtext/images/search-gray.png"/>
<input type="search" id="google-search" onkeyup="startSearch(event);"/>
</div>
<ul id="nav-outline" style="margin-left: 0px;">
<li class="nav-part">Getting Started</li>
<li><a href="102_domainmodelwalkthrough.html">15 Minutes Tutorial</a></li>
<li><a href="103_domainmodelnextsteps.html">15 Minutes Tutorial - Extended</a></li>
<li><a href="104_jvmdomainmodel.html">Five simple steps to your JVM language</a></li>
<li class="nav-part">Reference Documentation</li>
<li><a href="301_grammarlanguage.html">The Grammar Language</a></li>
<li><a href="302_configuration.html">Configuration</a></li>
<li><a href="303_runtime_concepts.html">Language Implementation</a></li>
<li><a href="305_xbase.html">Integration with Java</a></li>
<li><a href="307_special_languages.html">Typical Language Configurations</a></li>
<li><a href="308_emf_integration.html">Integration with EMF</a></li>
<li><a href="310_eclipse_support.html">Eclipse Support</a></li>
<!--li><a href="320_idea_support.html">IntelliJ IDEA Support</a></li-->
<li><a href="330_web_support.html">Web Editor Support</a></li>
<li><a href="350_continuous_integration.html">Continuous Integration</a></li>
</ul>
</div>
<div class="span8 doc-contents">
<h1 id="domain-model-walkthrough">15 Minutes Tutorial</h1>
<p>In this tutorial we will implement a small domain-specific language to model entities and properties similar to what you may know from Rails, Grails or Spring Roo. The syntax is very suggestive :</p>
<pre><code class="language-domainexample">datatype String
entity Blog {
title: String
many posts: Post
}
entity HasAuthor {
author: String
}
entity Post extends HasAuthor {
title: String
content: String
many comments: Comment
}
entity Comment extends HasAuthor {
content: String
}
</code></pre>
<p>After you have installed Xtext on your machine, start Eclipse and set up a fresh workspace.</p>
<h2 id="create-a-new-xtext-project">Create A New Xtext Project</h2>
<p>In order to get started we first need to create some Eclipse projects. Use the Eclipse wizard to do so:</p>
<p><em>File → New → Project… → Xtext → Xtext project</em></p>
<p>Choose a meaningful project name, language name and file extension, e.g.</p>
<table>
<tbody>
<tr>
<td><strong>Project name:</strong></td>
<td>org.example.domainmodel</td>
</tr>
<tr>
<td><strong>Language name:</strong></td>
<td>org.example.domainmodel.Domainmodel</td>
</tr>
<tr>
<td><strong>Language extensions:</strong></td>
<td>dmodel</td>
</tr>
</tbody>
</table>
<p>Click on <em>Finish</em> to create the projects.</p>
<p><img src="images/30min_wizard.png" alt="" /></p>
<p>After you have successfully finished the wizard, you will find five new projects in your workspace.</p>
<table>
<tbody>
<tr>
<td>org.example.domainmodel</td>
<td>The grammar definition and all language-specific components (parser, lexer, linker, validation, etc.)</td>
</tr>
<tr>
<td>org.example.domainmodel.tests</td>
<td>Unit tests for the language</td>
</tr>
<tr>
<td>org.example.domainmodel.ide</td>
<td>Platform-independent IDE functionality (e.g. services for content assist)</td>
</tr>
<tr>
<td>org.example.domainmodel.ui</td>
<td>The Eclipse editor and other workbench related functionality</td>
</tr>
<tr>
<td>org.example.domainmodel.ui.tests</td>
<td>Unit tests for the Eclipse editor</td>
</tr>
</tbody>
</table>
<p><img src="images/30min_initialprojectlayout.png" alt="" /></p>
<h2 id="write-the-grammar">Write The Grammar</h2>
<p>The wizard will automatically open the grammar file <em>Domainmodel.xtext</em> in the editor. As you can see it already contains a simple <em>Hello World</em> grammar:</p>
<pre><code class="language-xtext">grammar org.example.domainmodel.Domainmodel with
org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.example.org/domainmodel/Domainmodel"
Model:
greetings+=Greeting*;
Greeting:
'Hello' name=ID '!';
</code></pre>
<p>Let’s now just replace that grammar definition with the one for our entities language:</p>
<pre><code class="language-xtext">grammar org.example.domainmodel.Domainmodel with
org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.example.org/domainmodel/Domainmodel"
Domainmodel :
(elements+=Type)*;
Type:
DataType | Entity;
DataType:
'datatype' name=ID;
Entity:
'entity' name=ID ('extends' superType=[Entity])? '{'
(features+=Feature)*
'}';
Feature:
(many?='many')? name=ID ':' type=[Type];
</code></pre>
<p>Let’s have a more detailed look at what the different grammar rules mean:</p>
<ol>
<li>
<p>The first rule in a grammar is always used as the start rule.</p>
<pre><code class="language-xtext">Domainmodel :
(elements+=Type)*;
</code></pre>
<p>It says that a <em>Domainmodel</em> contains an arbitrary number (<code>*</code>) of <em>Type</em>s which are added (<code>+=</code>) to a feature called <code>elements</code>.</p>
</li>
<li>
<p>The rule <em>Type</em> delegates to either the rule <em>DataType</em> or (<code>|</code>) the rule <em>Entity</em>.</p>
<pre><code class="language-xtext">Type:
DataType | Entity;
</code></pre>
</li>
<li>
<p>The rule <em>DataType</em> starts with a keyword <code>'datatype'</code>, followed by an identifier which is parsed by a rule called <em>ID</em>. The rule <em>ID</em> is defined in the super grammar <em>org.eclipse.xtext.common.Terminals</em> and parses a single word, a.k.a identifier. You can navigate to the declaration by using <em>F3</em> on the rule call. The value returned by the call to <em>ID</em> is assigned (<code>=</code>) to the feature <code>name</code>.</p>
<pre><code class="language-xtext">DataType:
'datatype' name=ID;
</code></pre>
</li>
<li>
<p>The rule <em>Entity</em> again starts with the definition of a keyword followed by a name.</p>
<pre><code class="language-xtext">Entity :
'entity' name=ID ('extends' superType=[Entity])? '{'
(features+=Feature)*
'}';
</code></pre>
<p>Next up there is the <code>extends</code> clause which is parenthesized and optional (<code>?</code>). Since the feature named <code>superType</code> is a cross reference (note the square brackets), the parser rule <em>Entity</em> is not called here, but only a single identifier (the <em>ID</em>-rule) is parsed. The actual <em>Entity</em> to assign to the <code>superType</code> reference is resolved during the linking phase. Finally between curly braces there can be any number of <em>Features</em>, which invokes the next rule.</p>
</li>
<li>
<p>Last but not least, the rule <em>Feature</em> is defined as follows:</p>
<pre><code class="language-xtext">Feature:
(many?='many')? name=ID ':' type=[Type];
</code></pre>
<p>The keyword <code>many</code> shall be used to model a multi-valued feature in this DSL. The assignment operator (<code>?=</code>) implies that the feature <code>many</code> is of type <em>boolean</em>. You are already familiar with the other syntax elements in this parser rule.</p>
</li>
</ol>
<p>This entities grammar already uses the most important concepts of Xtext’s grammar language. You have learned that keywords are written as string literals and a simple assignment uses a plain equal sign (<code>=</code>), whereas a multi-value assignment uses a plus-equals (<code>+=</code>). We have also seen the boolean assignment operator (<code>?=</code>). Furthermore the example contains syntax elements with different cardinalities (<code>?</code> = optional, <code>*</code> = any number, <code>+</code> = at least once) and demonstrates how cross-references can be declared. Please consult the <a href="301_grammarlanguage.html">Grammar Language Reference</a> for more details. Let’s now have a look what you can do with such a language description.</p>
<h2 id="generate-language-artifacts">Generate Language Artifacts</h2>
<p>Now that we have the grammar in place we need to execute the code generator that will derive the various language components. To do so, right-click into the grammar editor and select</p>
<p><em>Run As → Generate Xtext Artifacts</em>.</p>
<p>This action generates the parser and text editor and some additional infrastructure code. You will see its logging messages in the Console View.</p>
<p><img src="images/30min_rungenerator.png" alt="" /></p>
<h2 id="run-generated-plugin">Run the Generated Eclipse Plug-in</h2>
<p>We are now able to test the Eclipse IDE integration. If you right-click the project <code>org.example.domainmodel</code> in the Package Explorer and select <em>Run As → Eclipse Application</em>, a new run configuration is created and launched that starts a second instance of Eclipse including your new language plug-ins. In the new instance, create a new project of your choice, e.g. <em>File → New → Project… → Java Project</em> and therein a new file with the file extension you chose in the beginning (<em>*.dmodel</em>). This will open the generated entity editor. Try it and discover the default functionality for code completion, syntax highlighting, syntactic validation, linking errors, the outline view, find references etc.</p>
<p><img src="images/30min_editor.png" alt="" /></p>
<h2 id="add-imports">Second Iteration: Adding Packages and Imports</h2>
<p>After you have created your first DSL and had a look at the editor, the language should be refined and incrementally enhanced. The entities language should support the notion of <em>Packages</em> in order to avoid name clashes and to better fit with the target environment Java. A <em>Package</em> may contain <em>Types</em> and other packages. In order to allow for names in references, we will also add a way to declare imports.</p>
<p>In the end we want to be able to split the previously used model into to distinct files :</p>
<pre><code class="language-domainexample">// datatypes.dmodel
datatype String
</code></pre>
<pre><code class="language-domainexample">// commons.dmodel
package my.company.common {
entity HasAuthor {
author: String
}
}
</code></pre>
<pre><code class="language-domainexample">// blogs.dmodel
package my.company.blog {
import my.company.common.*
entity Blog {
title: String
many posts: Post
}
entity Post extends my.company.common.HasAuthor {
title: String
content: String
many comments: Comment
}
entity Comment extends HasAuthor {
content: String
}
}
</code></pre>
<p>Let’s start enhancing the grammar.</p>
<ol>
<li>
<p>Since a <em>Domainmodel</em> no longer contains types but also packages, the entry rule has to be modified. Furthermore, a common super type for <em>Packages</em> and <em>Types</em> should be introduced: the <em>AbstractElement</em>.</p>
<pre><code class="language-xtext">Domainmodel:
(elements+=AbstractElement)*;
AbstractElement:
PackageDeclaration | Type;
</code></pre>
</li>
<li>
<p>A <code>PackageDeclaration</code> in turn looks pretty much as expected. It contains a number of <em>Imports</em> and <em>AbstractElements</em>. Since <em>Imports</em> should be allowed for the root-Domainmodel, too, we add them as an alternative to the rule <code>AbstractElement</code>.</p>
<pre><code class="language-xtext">PackageDeclaration:
'package' name=QualifiedName '{'
(elements+=AbstractElement)*
'}';
AbstractElement:
PackageDeclaration | Type | Import;
QualifiedName:
ID ('.' ID)*;
</code></pre>
<p>The <code>QualifiedName</code> is a little special. It does not contain any assignments. Therefore it serves as a data type rule that returns a String. Hence the feature <code>name</code> of a <em>Package</em> is still of type <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/String.html">String</a>.</p>
</li>
<li>
<p>Imports can be defined in a very convenient way with Xtext. If you use the name <code>importedNamespace</code> in a parser rule, the framework will treat the value as an import. It even supports wildcards and handles them as expected:</p>
<pre><code class="language-xtext">Import:
'import' importedNamespace=QualifiedNameWithWildcard;
QualifiedNameWithWildcard:
QualifiedName '.*'?;
</code></pre>
<p>Similar to the rule <code>QualifiedName</code>, <code>QualifiedNameWithWildcard</code> returns a plain string.</p>
</li>
<li>
<p>The last step is to allow fully qualified names in cross-references, too. Otherwise one could not refer to an entity without adding an import statement.</p>
<pre><code class="language-xtext">Entity:
'entity' name=ID ('extends' superType=[Entity|QualifiedName])? '{'
(features+=Feature)*
'}';
Feature:
(many?='many')? name=ID ':' type=[Type|QualifiedName];
</code></pre>
<p>Please note that the bar (<code>|</code>) is not an alternative in the context of a cross-reference, but used to specify the syntax of the parsed string.</p>
</li>
</ol>
<p>That’s all for the grammar. It should now read as</p>
<pre><code class="language-xtext">grammar org.example.domainmodel.Domainmodel with
org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.example.org/domainmodel/Domainmodel"
Domainmodel:
(elements+=AbstractElement)*;
PackageDeclaration:
'package' name=QualifiedName '{'
(elements+=AbstractElement)*
'}';
AbstractElement:
PackageDeclaration | Type | Import;
QualifiedName:
ID ('.' ID)*;
Import:
'import' importedNamespace=QualifiedNameWithWildcard;
QualifiedNameWithWildcard:
QualifiedName '.*'?;
Type:
DataType | Entity;
DataType:
'datatype' name=ID;
Entity:
'entity' name=ID ('extends' superType=[Entity|QualifiedName])? '{'
(features+=Feature)*
'}';
Feature:
(many?='many')? name=ID ':' type=[Type|QualifiedName];
</code></pre>
<p>You should regenerate the language infrastructure as described in the previous section, and give the editor another try. You can even split up your model into smaller parts and have cross-references across file boundaries.</p>
<p><img src="images/30min_multipleeditors.png" alt="" /></p>
<hr />
<p><strong><a href="103_domainmodelnextsteps.html">Next Chapter: 15 Minutes Tutorial - Extended</a></strong></p>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div id="extra">
<div class="inner">
<div class="container">
<div class="row">
<div class="span6">
<h3 class="footer-links-header">Quick Links</h3>
<ul class="footer-links clearfix">
<li><a href="http://www.eclipse.org/legal/privacy.php">Privacy Policy</a></li>
<li><a href="http://www.eclipse.org/legal/termsofuse.php">Terms of Use</a></li>
<li><a href="http://www.eclipse.org/legal/copyright.php">Copyright Agent</a></li>
<li><a href="http://www.eclipse.org/legal/">Legal</a></li>
</ul>
<ul class="footer-links clearfix">
<li><a href="http://www.eclipse.org">Eclipse Home</a></li>
<li><a href="http://marketplace.eclipse.org/">Market Place</a></li>
<li><a href="http://live.eclipse.org/">Eclipse Live</a></li>
<li><a href="http://www.planeteclipse.org/">Eclipse Planet</a></li>
</ul>
</div>
<div class="span6">
<!-- Social Media Links -->
<h3 class="footer-links-header"">Social Media</h3>
<ul class="footer-links clearfix">
<li>
<a href="https://twitter.com/xtext"><img src="/Xtext/images/Twitter-bird-darkgray.png" class="img-responsive" style="margin-right: 5px;height: 1em;" alt="Twitter icon">@xtext on Twitter</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<a href="#" class="scrollup fadeOutRight animated" style="display: none;">ScrollUp</a>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/Xtext/js/jquery-1.11.3.min.js"></script>
<script src="/Xtext/js/bootstrap.min.js"></script>
<script src="/Xtext/js/jquery.easing.1.3.js" type="text/javascript"></script>
<script src="/Xtext/js/jquery.prettyPhoto.js" type="text/javascript"></script>
<script src="/Xtext/js/prettify.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-xtend.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-common.js" type="text/javascript"></script>
<script src="/Xtext/js/custom.js" type="text/javascript"></script>
<!--script src="https://apis.google.com/js/platform.js" async defer></script-->
<!--script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push([ '_setAccount', 'UA-2429174-3' ]);
_gaq.push([ '_trackPageview' ]);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl'
: 'http://www')
+ '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script-->
<script src="/Xtext/js/coverflow.min.js" type="text/javascript"></script>
<script>
$(function() {
$('#coverflow').coverflow({
active : 1,
visibleAside: 2,
overlap : 0.5,
scale : 0.9,
angle : 20,
trigger : {
"itemfocus" : true,
"swipe" : true,
"mousewheel" : false
}
});
$('#coverflow :hidden').toggle();
$(window).resize(function() {
$('#coverflow').coverflow();
});
});
</script>
</footer>
</body>
</html>