diff --git a/build.gradle b/build.gradle
index d6b7e06c97a59d3011c6e92cf6b6f09181c6d484..87e174cf6b7070f199f24805658fa691ce40db35 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,5 @@
-
-apply plugin: 'java'
 apply plugin: 'jastadd'
 apply plugin: 'application'
-apply plugin: "idea"
 
 sourceCompatibility = 1.8
 
@@ -20,14 +17,11 @@ buildscript {
 }
 
 dependencies {
-    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0'
-    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0'
-    testCompile 'org.assertj:assertj-core:3.12.1'
-    compile 'com.fasterxml.jackson.core:jackson-core:2.9.8'
-    compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
-    compile 'org.jastadd:jastadd:2.3.4'
+    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'
+    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
+//    api 'org.jastadd:jastadd:2.3.4'
     runtime 'org.jastadd:jastadd:2.3.4'
-    compile group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
+    api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 }
 
 sourceSets {
@@ -163,3 +157,5 @@ jastadd {
 }
 
 generateAst.dependsOn relast
+
+apply from: 'test.build.gradle'
diff --git a/src/main/jastadd/Errors.jrag b/src/main/jastadd/Errors.jrag
index 60bdf24708ed6b26bd1094bff8185ff07de62fe3..7fb6b00b626a04b0d0c9eb3036242d728c64e050 100644
--- a/src/main/jastadd/Errors.jrag
+++ b/src/main/jastadd/Errors.jrag
@@ -1,65 +1,77 @@
-//import java.util.Set;
-//import java.util.TreeSet;
-//import java.util.LinkedList;
-//
-//
-//
-//
-//aspect ErrorMessage {
-//  public class ErrorMessage implements Comparable<ErrorMessage> {
-//    private final ASTNode node;
-//    private final String filename;
-//    private final int line;
-//    private final int col;
-//    private final String message;
-//
-//    public ErrorMessage(ASTNode node, String message) {
-//      this.node = node;
-//      this.filename = node.containedFile().getFileName();
-//      this.line = node.getStartLine();
-//      this.col = node.getStartColumn();
-//      this.message = message;
-//    }
-//
-//    public ASTNode getNode() {
-//      return node;
-//    }
-//    public int getLine() {
-//      return line;
-//    }
-//    public int getCol() {
-//      return col;
-//    }
-//    public String getMessage() {
-//      return message;
-//    }
-//
-//    public String toString() {
-//      return filename + " Line " + line + ", column " + col + ": " + message;
-//    }
-//
-//    @Override
-//    public int compareTo(ErrorMessage err) {
-//      int n = filename.compareTo(err.filename);
-//      if (n != 0) {
-//        return n;
-//      }
-//
-//      n = line - err.line;
-//      if (n != 0) {
-//        return n;
-//      }
-//
-//      n = col-err.col;
-//      if (n != 0) {
-//        return n;
-//      }
-//
-//      return message.compareTo(err.message);
-//    }
-//  }
-//
-//  protected ErrorMessage ASTNode.error(String message) {
-//    return new ErrorMessage(this, message);
-//  }
-//}
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.LinkedList;
+
+aspect Errors {
+  coll Set<ErrorMessage> Ros2Rag.errors()
+    [new TreeSet<ErrorMessage>()]
+    root Ros2Rag;
+
+//  TypeUse contributes error("Type '" + getID() + "' not found")
+//    when decl() == null && !isToken()
+//    to Program.errors();
+
+    UpdateDefinition contributes error("")
+      when true
+      to Ros2Rag.errors();
+}
+
+
+aspect ErrorMessage {
+  public class ErrorMessage implements Comparable<ErrorMessage> {
+    private final ASTNode node;
+    private final String filename;
+    private final int line;
+    private final int col;
+    private final String message;
+
+    public ErrorMessage(ASTNode node, String message) {
+      this.node = node;
+      this.filename = node.containedFile().getFileName();
+      this.line = node.getStartLine();
+      this.col = node.getStartColumn();
+      this.message = message;
+    }
+
+    public ASTNode getNode() {
+      return node;
+    }
+    public int getLine() {
+      return line;
+    }
+    public int getCol() {
+      return col;
+    }
+    public String getMessage() {
+      return message;
+    }
+
+    public String toString() {
+      return filename + " Line " + line + ", column " + col + ": " + message;
+    }
+
+    @Override
+    public int compareTo(ErrorMessage err) {
+      int n = filename.compareTo(err.filename);
+      if (n != 0) {
+        return n;
+      }
+
+      n = line - err.line;
+      if (n != 0) {
+        return n;
+      }
+
+      n = col-err.col;
+      if (n != 0) {
+        return n;
+      }
+
+      return message.compareTo(err.message);
+    }
+  }
+
+  protected ErrorMessage ASTNode.error(String message) {
+    return new ErrorMessage(this, message);
+  }
+}
diff --git a/src/main/jastadd/backend/Generation.jadd b/src/main/jastadd/backend/Generation.jadd
index 3195a70108e14412150ada77dd9ac22c6066db6b..c5f253f4a5a2c2d1d289512bd003927233eb790c 100644
--- a/src/main/jastadd/backend/Generation.jadd
+++ b/src/main/jastadd/backend/Generation.jadd
@@ -90,7 +90,7 @@ aspect AspectGeneration {
   }
 
   public void Ros2Rag.generateGrammarExtension(StringBuilder sb) {
-    sb.append("aspect ros2rag.GrammarExtension {\n");
+    sb.append("aspect ROS2RAG {\n");
 
     for (UpdateDefinition def : getUpdateDefinitionList()) {
       def.generateAspect(sb);
@@ -144,7 +144,7 @@ aspect AspectGeneration {
     sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().newConnection(topic, message -> {\n");
     String lastResult = generateMappingApplication(sb, 3, "message");
     sb.append(ind(3)).append("set").append(getToken().getName()).append("(").append(lastResult).append(");\n");
-    sb.append(ind(2)).append("}\n");
+    sb.append(ind(2)).append("});\n");
     sb.append(ind(1)).append("}\n\n");
   }
 
@@ -168,7 +168,7 @@ aspect AspectGeneration {
     sb.append(ind(1)).append("}\n\n");
 
     // update method
-    sb.append(ind(1)).append("protected boolean").append(parentTypeName).append(".")
+    sb.append(ind(1)).append("protected boolean ").append(parentTypeName).append(".")
       .append(updateMethod()).append("() {\n");
     sb.append(ind(2)).append(tokenResetMethod()).append("();\n");
     String lastResult = generateMappingApplication(sb, 2, "get" + getToken().getName() + "()");
@@ -177,7 +177,7 @@ aspect AspectGeneration {
     sb.append(ind(1)).append("}\n\n");
 
     // write method
-    sb.append(ind(1)).append("protected void").append(parentTypeName).append(".")
+    sb.append(ind(1)).append("protected void ").append(parentTypeName).append(".")
       .append(writeMethod()).append("() {\n");
     // _mqttUpdater().publish(${writeTopic()}, ${lastValue()});
     sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().publish(")
@@ -202,7 +202,7 @@ aspect AspectGeneration {
     String sourceParentTypeName = getSource().containingTypeDecl().getName();
 
     // dependency method
-    sb.append(ind(1)).append("public void").append(targetParentTypeName).append(".")
+    sb.append(ind(1)).append("public void ").append(targetParentTypeName).append(".")
       .append(dependencyMethod()).append("(").append(sourceParentTypeName).append(" source) {\n");
     sb.append(ind(2)).append("add").append(internalRelationPrefix()).append("Source(source);\n");
     sb.append(ind(1)).append("}\n\n");
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java b/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
index afae43aacba3080c023adecaa8de95b12efd451e..9fd90f037abad335414f1c54fea0a548c34b2909 100644
--- a/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
+++ b/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
@@ -4,6 +4,7 @@ import beaver.Parser;
 import org.jastadd.ros2rag.ast.*;
 import org.jastadd.ros2rag.compiler.options.CommandLine;
 import org.jastadd.ros2rag.compiler.options.CommandLine.CommandLineException;
+import org.jastadd.ros2rag.compiler.options.FlagOption;
 import org.jastadd.ros2rag.compiler.options.Option;
 import org.jastadd.ros2rag.compiler.options.StringOption;
 import org.jastadd.ros2rag.parser.Ros2RagParser;
@@ -22,6 +23,7 @@ public class Compiler {
   private StringOption optionInputGrammar;
   private StringOption optionRootNode;
   private StringOption optionInputRos2Rag;
+  private FlagOption optionHelp;
 
   private ArrayList<Option<?>> options;
   private CommandLine commandLine;
@@ -31,23 +33,34 @@ public class Compiler {
     addOptions();
   }
 
-  public int run(String[] args) throws CommandLineException, CompilerException {
+  public void run(String[] args) throws CommandLineException, CompilerException {
     options = new ArrayList<>();
     addOptions();
     commandLine = new CommandLine(options);
     commandLine.parse(args);
 
-    String outputDir = "gen"; // should not be used, but otherwise there is a compiler warning
+    if (optionHelp.isSet()) {
+      printUsage();
+      return;
+    }
+
+    String outputDir;
     if (optionOutputDir.isSet()) {
       outputDir = optionOutputDir.getValue();
     } else {
+      outputDir = "gen";
       System.out.println("No output output dir is set. Assuming '" + outputDir + "'.");
     }
+    try {
+      Files.createDirectories(Paths.get(outputDir));
+    } catch (IOException e) {
+      throw new CompilerException("Error creating output dir " + outputDir, e);
+    }
 
-    printMessage("Running ROS2RAG Preprocessor");
+    printMessage("Running Ros2Rag Preprocessor");
 
     if (anyRequiredOptionIsUnset()) {
-      return error("Aborting.");
+      throw new CompilerException("Aborting due to missing values for required options.");
     }
 
     List<String> otherArgs = commandLine.getArguments();
@@ -59,15 +72,14 @@ public class Compiler {
     printMessage("Writing output files");
     // copy MqttUpdater into outputDir
     try {
-      Files.copy(Paths.get("src", "main", "jastadd", "MqttUpdater.java_class"),
-          Paths.get(outputDir, "MqttUpdater.java"),
+      Files.copy(Paths.get("src", "main", "resources", "MqttUpdater.jadd"),
+          Paths.get(outputDir, "MqttUpdater.jadd"),
           StandardCopyOption.REPLACE_EXISTING);
     } catch (IOException e) {
       throw new CompilerException("Could not copy MqttUpdater.java", e);
     }
     writeToFile(outputDir + "/Grammar.relast", ros2Rag.getProgram().generateAbstractGrammar());
     writeToFile(outputDir + "/ROS2RAG.jadd", ros2Rag.generateAspect(optionRootNode.getValue()));
-    return 0;
   }
 
   private boolean anyRequiredOptionIsUnset() {
@@ -111,6 +123,7 @@ public class Compiler {
     optionInputGrammar = addOption(new StringOption("inputGrammar", "base grammar."));
     optionRootNode = addOption(new StringOption("rootNode", "root node in the base grammar."));
     optionInputRos2Rag = addOption(new StringOption("inputRos2Rag", "ros2rag definition file."));
+    optionHelp = addOption(new FlagOption("help", "Print usage and exit."));
   }
 
   private <OptionType extends Option<?>> OptionType addOption(OptionType option) {
@@ -149,35 +162,18 @@ public class Compiler {
     return ros2Rag;
   }
 
-  private void parse(Program program, Reader reader, String file) {
-    Ros2RagScanner scanner = new Ros2RagScanner(reader);
-    Ros2RagParser parser = new Ros2RagParser();
-
-    try {
-      GrammarFile newFile = (GrammarFile) parser.parse(scanner);
-      program.addGrammarFile(newFile);
-    } catch (IOException e) {
-      error(e.getMessage());
-    } catch (Parser.Exception e) {
-      System.err.println("Parse error in file " + file);
-      System.err.println(e.getMessage());
-      System.exit(1);
-    }
-  }
-
-  protected int error(String message) {
-    System.err.println("Error: " + message);
-    System.err.println();
-    System.err.println("Usage: java -jar ros2rag.jar [--option1] [--option2=value] ...  <filename1> <filename2> ... ");
-    System.err.println("Options:");
-    System.err.print(commandLine.printOptionHelp());
-    return 1;
+  protected void printUsage() {
+    System.out.println("Usage: java -jar ros2rag.jar [--option1] [--option2=value] ...  <filename1> <filename2> ... ");
+    System.out.println("Options:");
+    System.out.print(commandLine.printOptionHelp());
   }
 
   public static class CompilerException extends Exception {
+    CompilerException(String message) {
+      super(message);
+    }
     CompilerException(String message, Throwable cause) {
       super(message, cause);
     }
   }
 }
-
diff --git a/src/main/jastadd/MqttUpdater.java_class b/src/main/resources/MqttUpdater.jadd
similarity index 65%
rename from src/main/jastadd/MqttUpdater.java_class
rename to src/main/resources/MqttUpdater.jadd
index 835a7313bb1020a82ca53a727c275cf862f5072b..53fa6cf5dacd874382416d2855eca9cbef225cd5 100644
--- a/src/main/jastadd/MqttUpdater.java_class
+++ b/src/main/resources/MqttUpdater.jadd
@@ -1,23 +1,3 @@
-package org.jastadd.ros2rag.compiler.mqtt;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.fusesource.hawtbuf.Buffer;
-import org.fusesource.hawtbuf.UTF8Buffer;
-import org.fusesource.mqtt.client.*;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Consumer;
-
 /**
  * Helper class to receive updates via MQTT and use callbacks to handle those messages.
  *
@@ -25,33 +5,33 @@ import java.util.function.Consumer;
  */
 public class MqttUpdater {
 
-  private final Logger logger;
+  private final org.apache.logging.log4j.Logger logger;
   private final String name;
 
   /** The host running the MQTT broker. */
-  private URI host;
+  private java.net.URI host;
   /** The connection to the MQTT broker. */
-  private CallbackConnection connection;
+  private org.fusesource.mqtt.client.CallbackConnection connection;
   /** Whether we are subscribed to the topics yet */
-  private final Condition readyCondition;
-  private final Lock readyLock;
+  private final java.util.concurrent.locks.Condition readyCondition;
+  private final java.util.concurrent.locks.Lock readyLock;
   private boolean ready;
-  private QoS qos;
+  private org.fusesource.mqtt.client.QoS qos;
   /** Dispatch knowledge */
-  private final Map<String, Consumer<byte[]>> callbacks;
+  private final java.util.Map<String, java.util.function.Consumer<byte[]>> callbacks;
 
   public MqttUpdater() {
     this("Ros2Rag");
   }
 
   public MqttUpdater(String name) {
-    this.name = Objects.requireNonNull(name, "Name must be set");
-    this.logger = LogManager.getLogger(MqttUpdater.class);
-    this.callbacks = new HashMap<>();
-    this.readyLock = new ReentrantLock();
+    this.name = java.util.Objects.requireNonNull(name, "Name must be set");
+    this.logger = org.apache.logging.log4j.LogManager.getLogger(MqttUpdater.class);
+    this.callbacks = new java.util.HashMap<>();
+    this.readyLock = new java.util.concurrent.locks.ReentrantLock();
     this.readyCondition = readyLock.newCondition();
     this.ready = false;
-    this.qos = QoS.AT_LEAST_ONCE;
+    this.qos = org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE;
   }
 
   /**
@@ -59,18 +39,18 @@ public class MqttUpdater {
    * @throws IOException if could not connect, or could not subscribe to a topic
    * @return self
    */
-  public MqttUpdater setHost(String host, int port) throws IOException {
-    this.host = URI.create("tcp://" + host + ":" + port);
+  public MqttUpdater setHost(String host, int port) throws java.io.IOException {
+    this.host = java.net.URI.create("tcp://" + host + ":" + port);
     logger.debug("Host for {} is {}", this.name, this.host);
 
-    Objects.requireNonNull(this.host, "Host need to be set!");
-    MQTT mqtt = new MQTT();
+    java.util.Objects.requireNonNull(this.host, "Host need to be set!");
+    org.fusesource.mqtt.client.MQTT mqtt = new org.fusesource.mqtt.client.MQTT();
     mqtt.setHost(this.host);
     connection = mqtt.callbackConnection();
-    AtomicReference<Throwable> error = new AtomicReference<>();
+    java.util.concurrent.atomic.AtomicReference<Throwable> error = new java.util.concurrent.atomic.AtomicReference<>();
 
     // add the listener to dispatch messages later
-    connection.listener(new ExtendedListener() {
+    connection.listener(new org.fusesource.mqtt.client.ExtendedListener() {
       public void onConnected() {
         logger.debug("Connected");
       }
@@ -81,9 +61,9 @@ public class MqttUpdater {
       }
 
       @Override
-      public void onPublish(UTF8Buffer topic, Buffer body, Callback<Callback<Void>> ack) {
+      public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topic, org.fusesource.hawtbuf.Buffer body, org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) {
         String topicString = topic.toString();
-        Consumer<byte[]> callback = callbacks.get(topicString);
+        java.util.function.Consumer<byte[]> callback = callbacks.get(topicString);
         if (callback == null) {
           logger.debug("Got a message, but no callback to call. Forgot to unsubscribe?");
         } else {
@@ -95,7 +75,7 @@ public class MqttUpdater {
       }
 
       @Override
-      public void onPublish(UTF8Buffer topicBuffer, Buffer body, Runnable ack) {
+      public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topicBuffer, org.fusesource.hawtbuf.Buffer body, Runnable ack) {
         logger.warn("onPublish should not be called");
       }
 
@@ -108,10 +88,10 @@ public class MqttUpdater {
     throwIf(error);
 
     // actually establish the connection
-    connection.connect(new Callback<Void>() {
+    connection.connect(new org.fusesource.mqtt.client.Callback<Void>() {
       @Override
       public void onSuccess(Void value) {
-        connection.publish("components", (name + " is connected").getBytes(), QoS.AT_LEAST_ONCE, false, new Callback<Void>() {
+        connection.publish("components", (name + " is connected").getBytes(), org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE, false, new org.fusesource.mqtt.client.Callback<Void>() {
           @Override
           public void onSuccess(Void value) {
             logger.debug("success sending welcome message");
@@ -141,21 +121,21 @@ public class MqttUpdater {
     return this;
   }
 
-  public URI getHost() {
+  public java.net.URI getHost() {
     return host;
   }
 
-  private void throwIf(AtomicReference<Throwable> error) throws IOException {
+  private void throwIf(java.util.concurrent.atomic.AtomicReference<Throwable> error) throws java.io.IOException {
     if (error.get() != null) {
-      throw new IOException(error.get());
+      throw new java.io.IOException(error.get());
     }
   }
 
-  public void setQoSForSubscription(QoS qos) {
+  public void setQoSForSubscription(org.fusesource.mqtt.client.QoS qos) {
     this.qos = qos;
   }
 
-  public void newConnection(String topic, Consumer<byte[]> callback) {
+  public void newConnection(String topic, java.util.function.Consumer<byte[]> callback) {
     if (!ready) {
       // TODO should maybe be something more kind than throwing an exception here
       throw new IllegalStateException("Updater not ready");
@@ -165,7 +145,7 @@ public class MqttUpdater {
 
     // subscribe at broker
     Topic[] topicArray = { new Topic(topic, this.qos) };
-    connection.subscribe(topicArray, new Callback<byte[]>() {
+    connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() {
       @Override
       public void onSuccess(byte[] qoses) {
         logger.debug("Subscribed to {}, qoses: {}", topic, qoses);
@@ -187,7 +167,7 @@ public class MqttUpdater {
    * @param unit the time unit of the time argument
    * @return whether this updater is ready
    */
-  public boolean waitUntilReady(long time, TimeUnit unit) {
+  public boolean waitUntilReady(long time, java.util.concurrent.TimeUnit unit) {
     try {
       readyLock.lock();
       if (ready) {
@@ -207,7 +187,7 @@ public class MqttUpdater {
       logger.warn("Stopping without connection. Was setHost() called?");
       return;
     }
-    connection.disconnect(new Callback<Void>() {
+    connection.disconnect(new org.fusesource.mqtt.client.Callback<Void>() {
       @Override
       public void onSuccess(Void value) {
         logger.info("Disconnected {} from {}", name, host);
@@ -221,7 +201,7 @@ public class MqttUpdater {
   }
 
   public void publish(String topic, byte[] bytes) {
-    connection.publish(topic, bytes, qos, false, new Callback<Void>() {
+    connection.publish(topic, bytes, qos, false, new org.fusesource.mqtt.client.Callback<Void>() {
       @Override
       public void onSuccess(Void value) {
         logger.debug("Published some bytes to {}", topic);
diff --git a/src/test/.gitignore b/src/test/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e372444da686be9b60c0a1ef74a2821d13fcb46f
--- /dev/null
+++ b/src/test/.gitignore
@@ -0,0 +1,3 @@
+02-after-ros2rag/*
+03-after-relast/*
+java-gen/*
diff --git a/src/test/resources/Example.jadd b/src/test/01-input/example/Example.jadd
similarity index 100%
rename from src/test/resources/Example.jadd
rename to src/test/01-input/example/Example.jadd
diff --git a/src/test/resources/Example.relast b/src/test/01-input/example/Example.relast
similarity index 73%
rename from src/test/resources/Example.relast
rename to src/test/01-input/example/Example.relast
index 93168fbb1b7e879d35ede813a443790a4e6f07f3..5afeb2250283d049143a0134247fb1e374ac4296 100644
--- a/src/test/resources/Example.relast
+++ b/src/test/01-input/example/Example.relast
@@ -6,7 +6,7 @@ Zone ::= Coordinate* ;
 
 RobotArm ::= Joint* EndEffector <AttributeTestSource:int> /<AppropriateSpeed:double>/ ; // normally this would be: <AttributeTestSource:int> ;
 
-Joint ::= <Name> <CurrentPosition:IntPosition> ;  // normally this would be: <CurrentPosition:IntPosition>
+Joint ::= <Name:String> <CurrentPosition:IntPosition> ;  // normally this would be: <CurrentPosition:IntPosition>
 
 EndEffector : Joint;
 
diff --git a/src/test/resources/Example.ros2rag b/src/test/01-input/example/Example.ros2rag
similarity index 100%
rename from src/test/resources/Example.ros2rag
rename to src/test/01-input/example/Example.ros2rag
diff --git a/src/test/01-input/example/linkstate.proto b/src/test/01-input/example/linkstate.proto
new file mode 100644
index 0000000000000000000000000000000000000000..dc95138ba49f35497f4bdf061496145168292c9d
--- /dev/null
+++ b/src/test/01-input/example/linkstate.proto
@@ -0,0 +1,38 @@
+syntax = "proto3";
+
+package panda;
+
+message PandaLinkState {
+
+  string name = 1;
+
+  message Position {
+    float positionX = 1;
+    float positionY = 2;
+    float positionZ = 3;
+  }
+
+  message Orientation {
+    float orientationX = 1;
+    float orientationY = 2;
+    float orientationZ = 3;
+    float orientationW = 4;
+  }
+
+  message TwistLinear {
+    float twistLinearX = 1;
+    float twistLinearY = 2;
+    float twistLinearZ = 3;
+  }
+
+  message TwistAngular {
+    float twistAngularX = 1;
+    float twistAngularY = 2;
+    float twistAngularZ = 3;
+  }
+
+  Position pos = 2;
+  Orientation orient = 3;
+  TwistLinear tl = 4;
+  TwistAngular ta = 5;
+}
diff --git a/src/test/01-input/example/robotconfig.proto b/src/test/01-input/example/robotconfig.proto
new file mode 100644
index 0000000000000000000000000000000000000000..c3e0862e0216e5ea00f79966dff8d5ce434f3dac
--- /dev/null
+++ b/src/test/01-input/example/robotconfig.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package config;
+
+message RobotConfig {
+
+  double speed = 1;
+  bool loopTrajectory = 2;
+
+  enum PlanningMode {
+    FLUID = 0;
+    CARTESIAN = 1;
+  }
+
+  PlanningMode planningMode = 3;
+}
diff --git a/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java b/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b41810c351943775074c5fe1a0abd9e47a5afc6
--- /dev/null
+++ b/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java
@@ -0,0 +1,58 @@
+package org.jastadd.ros2rag.tests;
+
+import example.ast.*;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case "example".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class ExampleTest {
+
+  @Test
+  public void buildModel() {
+    Model model = new Model();
+    model.MqttSetHost("localhost");
+
+    ZoneModel zoneModel = new ZoneModel();
+    zoneModel.setSize(makePosition(1, 1, 1));
+
+    IntPosition myPosition = makePosition(0, 0, 0);
+    Coordinate myCoordinate = new Coordinate(myPosition);
+    Coordinate leftPosition = new Coordinate(makePosition(-1, 0, 0));
+    Coordinate rightPosition = new Coordinate(makePosition(1, 0, 0));
+
+    Zone safetyZone = new Zone();
+    safetyZone.addCoordinate(myCoordinate);
+    safetyZone.addCoordinate(leftPosition);
+    safetyZone.addCoordinate(rightPosition);
+    zoneModel.addSafetyZone(safetyZone);
+    model.setZoneModel(zoneModel);
+
+    RobotArm robotArm = new RobotArm();
+    robotArm.setAttributeTestSource(1);  // set initial value, no trigger
+
+    Joint joint1 = new Joint();
+    joint1.setName("joint1");
+    joint1.setCurrentPosition(myPosition);
+
+    EndEffector endEffector = new EndEffector();
+    endEffector.setName("gripper");
+    endEffector.setCurrentPosition(makePosition(2, 2, 3));
+
+    robotArm.addJoint(joint1);
+    robotArm.setEndEffector(endEffector);
+    model.setRobotArm(robotArm);
+
+    // add dependencies
+    robotArm.addDependency1(joint1);
+    robotArm.addDependency1(endEffector);
+    robotArm.addDependency2(robotArm);
+  }
+
+  private static IntPosition makePosition(int x, int y, int z) {
+    return IntPosition.of(x, y, z);
+  }
+
+}
diff --git a/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java b/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
index d7d482a65f37bfa39f416feb72dc9c0af293f3f6..adf19335a371d70a3b30efaf0d08a91a60360571 100644
--- a/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
+++ b/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
@@ -2,6 +2,7 @@ package org.jastadd.ros2rag.tests;
 
 import org.jastadd.ros2rag.compiler.Compiler;
 import org.jastadd.ros2rag.compiler.options.CommandLine;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.io.File;
@@ -10,6 +11,7 @@ import java.util.Objects;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+@Disabled("migrating build logic from test to build, this test is disabled for now")
 public class RosToRagTest {
 
   void transform(String inputGrammar, String inputRos2Rag, String rootNode, String outputDir) throws CommandLine.CommandLineException, Compiler.CompilerException {
diff --git a/test.build.gradle b/test.build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..04252f02b04ded4c49d6ff7860935f6dd480eef8
--- /dev/null
+++ b/test.build.gradle
@@ -0,0 +1,68 @@
+import org.jastadd.relast.plugin.RelastPlugin
+import org.jastadd.relast.plugin.RelastTest
+apply plugin: RelastPlugin
+
+relastTest {
+    compilerLocation = '../libs/relast.jar'
+}
+
+dependencies {
+    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0'
+    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0'
+    testImplementation 'org.assertj:assertj-core:3.12.1'
+    testImplementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
+    testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
+    testImplementation 'com.google.protobuf:protobuf-java:3.0.0'
+}
+
+sourceSets {
+    test {
+        java.srcDir "src/test/java-gen"
+    }
+}
+
+task preprocessExampleTest(type: JavaExec, group: 'verification') {
+
+    doFirst {
+        delete 'src/test/02-after-ros2rag/example/Grammar.relast',
+                'src/test/02-after-ros2rag/example/MqttUpdater.java',
+                'src/test/02-after-ros2rag/example/ROS2RAG.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ros2rag.compiler.Compiler'
+    //noinspection GroovyAssignabilityCheck
+    args '--outputDir=src/test/02-after-ros2rag/example',
+            '--inputGrammar=src/test/01-input/example/Example.relast',
+            '--inputRos2Rag=src/test/01-input/example/Example.ros2rag',
+            '--rootNode=Model'
+}
+
+//task compileExampleTest(type: JavaExec, group: 'verification') {
+//
+//    doFirst {
+//        delete 'src/test/java-gen/example'
+//    }
+//
+//    classpath = sourceSets.main.runtimeClasspath
+//    main = 'org.jastadd.JastAdd'
+//    //noinspection GroovyAssignabilityCheck
+//    args '--o=src/test/java-gen/', '--package=example.ast',
+//            'src/test/jastadd-gen/example/Grammar.relast',
+//            'src/test/jastadd-gen/example/MqttUpdater.java',
+//            'src/test/jastadd-gen/example/ROS2RAG.jadd',
+//            'src/test/jastadd/Example.jadd'
+//}
+
+task compileExampleTest(type: RelastTest) {
+    verbose = true
+    relastFiles 'src/test/02-after-ros2rag/example/Grammar.relast'
+    grammarName = 'src/test/03-after-relast/example/example'
+    packageName = 'example.ast'
+    moreInputFiles 'src/test/01-input/example/Example.jadd',
+            'src/test/02-after-ros2rag/example/MqttUpdater.jadd',
+            'src/test/02-after-ros2rag/example/ROS2RAG.jadd'
+}
+
+test.dependsOn compileExampleTest
+compileExampleTest.dependsOn preprocessExampleTest