avatar

Xtext and more

  • Impressum

Leaving itemis

July 14, 2023 · Christian Dietrich · Tags: Xtext

I have been a user of Xtext from its early days back at openArchitectureWare. With Xtexts move to Eclipse I became more and more a power user of Xtext, in particular answering a ton of questions in the Xtext forum and building fancy DSLs in multiple (customer) projects. In 2016 I started committing regularly and in 2018 I also started leading Xtext as Co-Lead. In the past years I tried my very best to keep Xtext alive and maintained as part of my job and beyond. And I kept doing so until today. But now the time has come to set out for new shores: I will be leaving itemis end of the month and with the job change Xtext no longer will be part of my day job.

What impact will this have for Xtext?

  • I won’t drop the pen immediately, but as Xtext will be pure spare time joy, the time i will be able to invest will be heavily reduced.
  • Responsiveness to Questions/Bugs in Github or Forum will be slower
  • Xtext will rely more on the community / Xtext using companies for bugfixes and support for new features like support for new Java versions or dependency bumps / adaption of platform/jdt changes.
  • As in the past you all are invited to contribute.
  • If you have any questions feel free to approach me.

What will I do in the future?

  • I can’t talk about that yet, but I will stay participating the modeling-sphere.

Final words: I want to thank itemis and specially Holger for giving me the opportunity to work on Xtext and Sebastian for the good sparring we had and for all the effort he spent supporting me, as well as the other Xtext team members at itemis, other committers and the community for the good cooperation & time we had.

Code Coverage for Xtend

January 12, 2016 · Christian Dietrich · Tags: Coverage , Xtend

This is a short blog on how to do code coverage reports with Xtend. Imagine you have some nice Xtend Code like this one

package demo
 
class Demo {
 
    def doSomething() {
        return 1
    }
 
    def doSomethingElse() {
        return 2
    }
 
}

wouldn’t it be nice to have code coverage like

Coverage

Using the xtend-maven-plugin in version 2.9.0+ this works more or less out of the box

<plugin>
    <groupId>org.eclipse.xtend</groupId>
    <artifactId>xtend-maven-plugin</artifactId>
    <version>2.9.0</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>testCompile</goal>
                <goal>xtend-install-debug-info</goal>
                <goal>xtend-test-install-debug-info</goal>
            </goals>
            <configuration>
                <xtendAsPrimaryDebugSource>true</xtendAsPrimaryDebugSource>
                <outputDirectory>${project.build.directory}/xtend-gen/main</outputDirectory>
                <testOutputDirectory>${project.build.directory}/xtend-gen/test</testOutputDirectory>
                <writeTraceFiles>true</writeTraceFiles>
            </configuration>
        </execution>
    </executions>
</plugin>

The interesting - new - parts are the goals xtend-install-debug-info and xtend-test-install-debug-info as well as the configuration xtendAsPrimaryDebugSource.

Here is my complete sample pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>demo</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <build>
        <plugins>
 
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerId>eclipse</compilerId>
                    <source>1.8</source>
                    <target>1.8</target>
                    <optimize>true</optimize>
 
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.codehaus.plexus</groupId>
                        <artifactId>plexus-compiler-eclipse</artifactId>
                        <version>2.6</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
            </plugin>
 
 
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.7.5.201505241946</version>
 
 
                <executions>
                    <!-- Prepares the property pointing to the JaCoCo runtime agent which 
                        is passed as VM argument when Maven the Surefire plugin is executed. -->
                    <execution>
                        <id>pre-unit-test</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <!-- Sets the path to the file which contains the execution data. -->
                            <destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
                            <!-- Sets the name of the property containing the settings for JaCoCo 
                                runtime agent. -->
                            <propertyName>surefireArgLine</propertyName>
 
                        </configuration>
                    </execution>
                    <!-- Ensures that the code coverage report for unit tests is created 
                        after unit tests have been run. -->
                    <execution>
                        <id>post-unit-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
 
                            <!-- Sets the path to the file which contains the execution data. -->
                            <dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
                            <!-- Sets the output directory for the code coverage report. -->
                            <outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
 
            </plugin>
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.15</version>
                <configuration>
                    <!-- Sets the VM argument line used when unit tests are run. -->
                    <argLine>${surefireArgLine}</argLine>
                    <!-- Skips unit tests if the value of skip.unit.tests property is true -->
                    <skipTests>${skip.unit.tests}</skipTests>
                    <!-- Excludes integration tests when unit tests are run. -->
                    <excludes>
                        <exclude>**/IT*.java</exclude>
                    </excludes>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.eclipse.xtend</groupId>
                <artifactId>xtend-maven-plugin</artifactId>
                <version>2.9.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                            <goal>xtend-install-debug-info</goal>
                            <goal>xtend-test-install-debug-info</goal>
                        </goals>
                        <configuration>
                            <xtendAsPrimaryDebugSource>true</xtendAsPrimaryDebugSource>
                            <outputDirectory>${project.build.directory}/xtend-gen/main</outputDirectory>
                            <testOutputDirectory>${project.build.directory}/xtend-gen/test</testOutputDirectory>
                            <writeTraceFiles>true</writeTraceFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
 
    <dependencies>
        <dependency>
            <groupId>org.eclipse.xtend</groupId>
            <artifactId>org.eclipse.xtend.lib</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
 
    </dependencies>
</project>

Xtext and Strings as Cross-References

March 19, 2015 · Christian Dietrich · Tags: Cross-Reference , ProposalProvider , STRING , Xtext

Cross References are a often used concept in Xtext. They ususally work like this

Model:
    definitions+=Definition*
    usages+=Usage*
;
 
Definition:
    "define" name=ID
;
 
Usage:
   "use" definition=[Definition]
;

They can be used in the model like this

define Thing
use Thing

but what if i want to write something like

define "This is a Thing"
use "This is a Thing"

well the definition part is easily changed

Definition:
    "define" name=STRING
;

But what about the usage part? well it is quite easy as well. refName=[Type] is short for refName=[Type|ID] which means ‘Refererence a Type and parse an ID. So to use another Terminal or Data Type Rule we change it to refName=[Type|RULENAME]

Usage:
   "use" definition=[Definition|STRING]
;

Now the cross refs are working fine. but if we try the editor we find out what autoedit and content assist disturb each other. We type " and auto edit gets us to “|”. If we now type Crtl+Space for content assist we finally get “This is a Thing”" with an extra " at the end. To avoid this we have to tweak the proposal provider a bit.

package org.xtext.example.mydsl.ui.contentassist
 
import org.xtext.example.mydsl.ui.contentassist.AbstractMyDslProposalProvider
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtext.Assignment
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor.Delegate
import org.eclipse.jface.text.contentassist.ICompletionProposal
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal
 
class MyDslProposalProvider extends AbstractMyDslProposalProvider {
 
    override completeUsage_Definition(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
        super.completeUsage_Definition(model, assignment, context, new  StringProposalDelegate(acceptor, context))
    }
 
    static class StringProposalDelegate extends Delegate {
 
        ContentAssistContext ctx
 
        new(ICompletionProposalAcceptor delegate, ContentAssistContext ctx) {
            super(delegate)
            this.ctx = ctx
        }
 
        override accept(ICompletionProposal proposal) {
            if (proposal instanceof ConfigurableCompletionProposal) {
                val endPos = proposal.replacementOffset + proposal.replacementLength 
                if (ctx.document != null && ctx.document.length > endPos) {
                    // We are not at the end of the file
                    if ("\"" == ctx.document.get(endPos, 1)) {
                        proposal.replacementLength = proposal.replacementLength-1
                        proposal.replacementString = proposal.replacementString.substring(0,proposal.replacementString.length-1)
                    }
                }
            }
            super.accept(proposal)
        }
 
    }
 
}

what we basically do is detecting the situation and remove the extra "

Use of EcoreGenerator to customize EMFs generated Java Classes

July 26, 2013 · Christian Dietrich · Tags: Ecore , Xtext

This is a blog post on the usage of the org.eclipse.emf.mwe2.ecore.EcoreGenerator worflow component. It is an alternative to EMFs JMerge Based approach to customize generated Java Classes from an ecore model.

So let us asume we have the following Ecore file (/sample/model/sample.ecore)

<?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="sample" nsURI="http://www.eclipse.org/xtext/ecore/sample" nsPrefix="sample">
  <eClassifiers xsi:type="ecore:EClass" name="Person">
    <eOperations name="getFullname" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="firstname" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="lastname" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
  </eClassifiers>
</ecore:EPackage>

with the following genmodel (/sample/model/sample.genmodel)

<?xml version="1.0" encoding="UTF-8"?>
<genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
    xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" modelDirectory="/sample/src-gen" modelPluginID="sample" modelName="Sample"
    importerID="org.eclipse.emf.importer.ecore" complianceLevel="6.0" copyrightFields="false">
  <foreignModel>sample.ecore</foreignModel>
  <genPackages prefix="Sample" disposableProviderFactory="true" ecorePackage="sample.ecore#/">
    <genClasses ecoreClass="sample.ecore#//Person">
      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute sample.ecore#//Person/firstname"/>
      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute sample.ecore#//Person/lastname"/>
      <genOperations ecoreOperation="sample.ecore#//Person/getFullname"/>
    </genClasses>
  </genPackages>
</genmodel:GenModel>

I have changed the modelDirectory="/sample/src-gen"

Now we want to implement the getFullname() method. we would usually implement the code in PersonImpl and change the javadoc to @generated NOT or remove the @generated. (and checkin the generated code as well)

But let us try another approach

so let us create following class

(/sample/src/sample/impl/PersonImplCustom.java)

package sample.impl;
 
import sample.impl.PersonImpl;
 
public class PersonImplCustom extends PersonImpl {
     
    @Override
    public String getFullname() {
        return getFirstname() + " " + getLastname();
    }
 
}

and workflow

(/sample/src/test.mwe2)

module test
 
Workflow {
     
    bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
        platformUri = ".."
    }
     
    component = org.eclipse.emf.mwe.utils.DirectoryCleaner {
        directory = "src-gen"
    }
     
    component = org.eclipse.emf.mwe2.ecore.EcoreGenerator {
        generateCustomClasses = false
        genModel = "platform:/resource/sample/model/sample.genmodel"
        srcPath = "platform:/resource/sample/src"   
    }
     
}

here is the Manifest for the deps (/sample/META-INF/MANIFEST.MF)

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: sample
Bundle-SymbolicName: sample; singleton:=true
Bundle-Version: 0.1.0
Require-Bundle: org.eclipse.emf.mwe2.lib,
 org.eclipse.emf.mwe2.launch,
 org.apache.commons.logging,
 org.apache.commons.lang,
 org.eclipse.xtext.xbase.lib,
 org.eclipse.xtext,
 org.eclipse.xtext.generator,
 org.apache.commons.logging,
 org.eclipse.ui.workbench,
 org.eclipse.core.runtime,
 org.eclipse.core.commands,
 org.eclipse.xtext.ui,
 org.eclipse.core.expressions,
 org.apache.log4j
Export-Package: sample.impl,
 sample.util

If we run the workflow emf is configured to take PersonImplCustom instead of PersonImpl so that the following will work nicely

public class Test {
     
    public static void main(String[] args) {
        Person p = SampleFactory.eINSTANCE.createPerson();
        p.setFirstname("Christian");
        p.setLastname("Dietrich");
        System.out.println(p.getFullname());
    }
 
}

Xtext and Dot/Path-Expressions

May 18, 2013 · Christian Dietrich · Tags: Dot-Expresion , Path , Scoping , Xtext

If you have a DSL that describes structure (e.g. like an Entity DSL) you often need to “walk” on this structure using dot/path-expressions.

Let us asume we have a grammar like

Model:
    entities+=Entity*
;
 
Entity:
    "entity" name=ID "{"
        features+=Feature*
    "}"
;
 
Feature:
    Attribute | Reference
;
 
Attribute:
    "attr" name=ID ":" type=DataType
;
 
enum DataType:
    string | int
;
 
Reference:
    "ref" name=ID ":" type=[Entity]
;

and a model like

entity A {
    attr a1 : int
    attr a2 : string
    ref b : B
    ref c : C
}
 
entity B {
    attr b1 : string
    attr b2 : string
    ref a : A
    ref c : C
}
 
entity C {
    attr c1 : string
    attr c2 : int
}

and want to have expressions like

use A.b.b2
use A.b.c.c1
use A.a1
use A.b.a.a1

but how to do this with Xtext? there are several possibility but the following was working well in my usecase:

Model:
    entities+=Entity*
    usages+=Usage*
;
Usage:
    "use" ref=DotExpression
;
 
DotExpression returns Ref:
    EntityRef ({DotExpression.ref=current}  "." tail=[Feature])*
;
 
EntityRef returns Ref:
    {EntityRef} entity=[Entity]
; 

a bit of scoping

class MyDslScopeProvider extends AbstractDeclarativeScopeProvider {
 
    def IScope scope_DotExpression_tail(DotExpression exp, EReference ref) {
        val head = exp.ref;
        switch (head) {
            EntityRef : Scopes::scopeFor(head.entity.features)
            DotExpression : {
                val tail = head.tail
                switch (tail) {
                    Attribute : IScope::NULLSCOPE
                    Reference : Scopes::scopeFor(tail.type.features)
                    default: IScope::NULLSCOPE
                }
            }
             
            default: IScope::NULLSCOPE
        }
    }
 
}

and it works fine.

as an additional note: the ast of the expressions look like

AST

© 2023 - Powered by Hugo with the Type Theme