blob: ea257972c121dd85766774e72916de1dfe18fb95 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Xtext - 15 Minutes Tutorial - Extended</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/103_domainmodelnextsteps.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="domainmodel-next-steps">15 Minutes Tutorial - Extended</h1>
<p>After you have developed your first own DSL, the question arises how the behavior and the semantics of the language can be customized. Here you find a few mini-tutorials that illustrate common use cases when crafting an own DSL. These lessons are independent from each other. Each of them will be based on the language that was built in the first <a href="102_domainmodelwalkthrough.html">domain model tutorial</a>.</p>
<h2 id="tutorial-code-generation">Writing a Code Generator With Xtend</h2>
<p>As soon as you generate the Xtext artifacts for a grammar, a code generator stub is put into the runtime project of your language. Let’s dive into Xtend and see how you can integrate your own code generator with Eclipse.</p>
<p>In this lesson you will generate Java Beans for entities that are defined in the domain model DSL. For each <em>Entity</em>, a Java class is generated and each <em>Feature</em> will lead to a private field in that class including public getters and setters. For the sake of simplicity, we will use fully qualified names all over the generated code.</p>
<pre><code class="language-java">package my.company.blog;
public class HasAuthor {
private java.lang.String author;
public java.lang.String getAuthor() {
return author;
}
public void setAuthor(java.lang.String author) {
this.author = author;
}
}
</code></pre>
<p>First of all, locate the file <em>DomainmodelGenerator.xtend</em> in the package <em>org.example.domainmodel.generator</em>. This Xtend class is used to generate code for your models in the standalone scenario and in the interactive Eclipse environment. Let’s make the implementation more meaningful and start writing the code generator. The strategy is to find all entities within a resource and trigger code generation for each one.</p>
<ol>
<li>
<p>First of all, you will have to filter the contents of the resource down to the defined entities. Therefore we need to iterate a resource with all its deeply nested elements. This can be achieved with the method <code>getAllContents()</code>. To use the resulting <a href="https://github.com/eclipse/emf/blob/R2_9_0/plugins/org.eclipse.emf.common/src/org/eclipse/emf/common/util/TreeIterator.java">TreeIterator</a> in a <code>for</code> loop, we use the extension method <code>toIterable()</code> from the built-in library class <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib/IteratorExtensions.java">IteratorExtensions</a>.</p>
<pre><code class="language-xtend">class DomainmodelGenerator extends AbstractGenerator {
override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
for (e : resource.allContents.toIterable.filter(Entity)) {
}
}
}
</code></pre>
</li>
<li>
<p>Now let’s answer the question how we determine the file name of the Java class that each <em>Entity</em> should yield. This information should be derived from the qualified name of the <em>Entity</em> since Java enforces this pattern. The qualified name itself has to be obtained from a special service that is available for each language. Fortunately, Xtend allows to reuse that one easily. We simply inject the <a href="https://github.com/eclipse/xtext-core/blob/master/org.eclipse.xtext/src/org/eclipse/xtext/naming/IQualifiedNameProvider.java">IQualifiedNameProvider</a> into the generator.</p>
<pre><code class="language-xtend"> @Inject extension IQualifiedNameProvider
</code></pre>
<p>This allows to ask for the name of an entity. It is straightforward to convert the name into a file name:</p>
<pre><code class="language-xtend">override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
for (e : resource.allContents.toIterable.filter(Entity)) {
fsa.generateFile(
e.fullyQualifiedName.toString("/") + ".java",
e.compile)
}
}
</code></pre>
</li>
<li>
<p>The next step is to write the actual template code for an entity. For now, the function <code>Entity.compile</code> does not exist, but it is easy to create it:</p>
<pre><code class="language-xtend">def compile(Entity e) '''
package «e.eContainer.fullyQualifiedName»;
public class «e.name» {
}
'''
</code></pre>
</li>
<li>
<p>This small template is basically the first shot at a Java-Beans generator. However, it is currently rather incomplete and will fail if the <em>Entity</em> is not contained in a package. A small modification fixes this. The <code>package</code> declaration has to be wrapped in an <code>IF</code> expression:</p>
<pre><code class="language-xtend">def compile(Entity e) '''
«IF e.eContainer.fullyQualifiedName !== null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» {
}
'''
</code></pre>
<p>Let’s handle the <em>superType</em> of an <em>Entity</em> gracefully, too, by using another <code>IF</code> expression:</p>
<pre><code class="language-xtend">def compile(Entity e) '''
«IF e.eContainer.fullyQualifiedName !== null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» «IF e.superType !== null
»extends «e.superType.fullyQualifiedName» «ENDIF»{
}
'''
</code></pre>
</li>
<li>
<p>Even though the template will compile the <em>Entities</em> without any complaints, it still lacks support for the Java properties that each of the declared features should yield. For that purpose, you have to create another Xtend function that compiles a single feature to the respective Java code.</p>
<pre><code class="language-xtend">def compile(Feature f) '''
private «f.type.fullyQualifiedName» «f.name»;
public «f.type.fullyQualifiedName» get«f.name.toFirstUpper»() {
return «f.name»;
}
public void set«f.name.toFirstUpper»(«f.type.fullyQualifiedName» «f.name») {
this.«f.name» = «f.name»;
}
'''
</code></pre>
<p>As you can see, there is nothing fancy about this one. Last but not least, we have to make sure that the function is actually used.</p>
<pre><code class="language-xtend">def compile(Entity e) '''
«IF e.eContainer.fullyQualifiedName !== null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» «IF e.superType !== null
»extends «e.superType.fullyQualifiedName» «ENDIF»{
«FOR f:e.features»
«f.compile»
«ENDFOR»
}
'''
</code></pre>
</li>
</ol>
<p>The final code generator is listed below. Now you can give it a try! Launch a new Eclipse Application (<em>Run As → Eclipse Application</em> on the Xtext project) and create a <em>dmodel</em> file in a Java Project. Now simply create a new source folder <em>src-gen</em> in that project and see how the compiler will pick up your sample <em>Entities</em> and generate Java code for them.</p>
<pre><code class="language-xtend">package org.example.domainmodel.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.AbstractGenerator
import org.eclipse.xtext.generator.IFileSystemAccess2
import org.eclipse.xtext.generator.IGeneratorContext
import org.example.domainmodel.domainmodel.Entity
import org.example.domainmodel.domainmodel.Feature
import org.eclipse.xtext.naming.IQualifiedNameProvider
import com.google.inject.Inject
class DomainmodelGenerator extends AbstractGenerator {
@Inject extension IQualifiedNameProvider
override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
for (e : resource.allContents.toIterable.filter(Entity)) {
fsa.generateFile(
e.fullyQualifiedName.toString("/") + ".java",
e.compile)
}
}
def compile(Entity e) '''
«IF e.eContainer.fullyQualifiedName !== null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» «IF e.superType !== null
»extends «e.superType.fullyQualifiedName» «ENDIF»{
«FOR f : e.features»
«f.compile»
«ENDFOR»
}
'''
def compile(Feature f) '''
private «f.type.fullyQualifiedName» «f.name»;
public «f.type.fullyQualifiedName» get«f.name.toFirstUpper»() {
return «f.name»;
}
public void set«f.name.toFirstUpper»(«f.type.fullyQualifiedName» «f.name») {
this.«f.name» = «f.name»;
}
'''
}
</code></pre>
<p>If you want to play around with Xtend, you can try to use the Xtend tutorial which can be materialized into your workspace. Simply choose <em>New → Example → Xtend Examples → Xtend Introductory Examples</em> and have a look at the features of Xtend. As a small exercise, you could implement support for the <em>many</em> attribute of a <em>Feature</em> or enforce naming conventions, e.g. generated field names should start with an underscore.</p>
<h2 id="tutorial-unit-tests">Unit Testing the Language</h2>
<p>Automated tests are crucial for the maintanability and the quality of a software product. That is why it is strongly recommended to write unit tests for your language, too. The Xtext project wizard creates two test projects for that purpose. These simplify the setup procedure for testing the basic language features and the Eclipse UI integration.</p>
<p>This tutorial is about testing the parser and the linker for the <em>Domainmodel</em>. It leverages Xtend to write the test case.</p>
<ol>
<li>
<p>The core of the test infrastructure is the <a href="https://github.com/eclipse/xtext-core/blob/master/org.eclipse.xtext.testing/src/org/eclipse/xtext/testing/XtextRunner.java">XtextRunner</a> and the language-specific <a href="https://github.com/eclipse/xtext-core/blob/master/org.eclipse.xtext.testing/src/org/eclipse/xtext/testing/IInjectorProvider.java">IInjectorProvider</a>. Both have to be provided by means of class annotations. An example test class should have already been generated by the Xtext code generator, named <em>org.example.domainmodel.tests.DomainmodelParsingTest</em>:</p>
<pre><code class="language-xtend">@RunWith(XtextRunner)
@InjectWith(DomainmodelInjectorProvider)
class DomainmodelParsingTest{
@Inject
ParseHelper&lt;Domainmodel&gt; parseHelper
@Test
def void loadModel() {
val result = parseHelper.parse('''
Hello Xtext!
''')
Assert.assertNotNull(result)
}
}
</code></pre>
</li>
<li>
<p>The utility class <a href="https://github.com/eclipse/xtext-core/blob/master/org.eclipse.xtext.testing/src/org/eclipse/xtext/testing/util/ParseHelper.java">ParseHelper</a> allows to parse an arbitrary string into a <em>Domainmodel</em>. The model itself can be traversed and checked afterwards. A static import of <a href="http://junit.sourceforge.net/javadoc/org/junit/Assert.html">Assert</a> leads to concise and readable test cases. You can rewrite the generated example as follows:</p>
<pre><code class="language-xtend">import static org.junit.Assert.*
...
@Test
def void parseDomainmodel() {
val model = parser.parse(
"entity MyEntity {
parent: MyEntity
}")
val entity = model.elements.head as Entity
assertSame(entity, entity.features.head.type)
}
</code></pre>
</li>
<li>
<p>After saving the Xtend file, it is time to run the test. Select <em>Run As → JUnit Test</em> from the editor’s context menu.</p>
</li>
</ol>
<h2 id="tutorial-validation">Creating Custom Validation Rules</h2>
<p>One of the main advantages of DSLs is the possibility to statically validate domain-specific constraints. Because this is a common use case, Xtext provides a dedicated hook for this kind of validation rules. In this lesson, we want to ensure that the name of an <em>Entity</em> starts with an upper-case letter and that all features have distinct names across the inheritance relationship of an <em>Entity</em>.</p>
<p>Locate the class <em>DomainmodelValidator</em> in the package <em>org.example.domainmodel.validation</em> of the language project. Defining the constraint itself is only a matter of a few lines of code:</p>
<pre><code class="language-xtend">@Check
def void checkNameStartsWithCapital(Entity entity) {
if (!Character.isUpperCase(entity.name.charAt(0))) {
warning("Name should start with a capital",
DomainmodelPackage.Literals.TYPE__NAME)
}
}
</code></pre>
<p>Any name for the method will do. The important thing is the <a href="https://github.com/eclipse/xtext-core/blob/master/org.eclipse.xtext/src/org/eclipse/xtext/validation/Check.java">Check</a> annotation that advises the framework to use the method as a validation rule. If the name starts with a lower case letter, a warning will be attached to the name of the <em>Entity</em>.</p>
<p>The second validation rule is straight-forward, too. We traverse the inheritance hierarchy of the <em>Entity</em> and look for features with equal names.</p>
<pre><code class="language-xtend">@Check
def void checkFeatureNameIsUnique(Feature f) {
var superEntity = (f.eContainer as Entity).superType
while (superEntity !== null) {
for (other : superEntity.features) {
if (f.name == other.name) {
error("Feature names have to be unique",
DomainmodelPackage.Literals.FEATURE__NAME)
return
}
}
superEntity = superEntity.getSuperType();
}
}
</code></pre>
<p>The sibling features that are defined in the same entity are automatically validated by the Xtext framework, thus they do not have to be checked twice. Note that this implementation is not optimal in terms of execution time because the iteration over the features is done for all features of each entity.</p>
<hr />
<p><strong><a href="104_jvmdomainmodel.html">Next Chapter: Five simple steps to your JVM language</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>