diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 88907420eabc71f41a10b13cd76de0738cf2d5df..71fba4155eb9ee80538e42b1746c79a1f19b0ede 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,11 +2,10 @@ variables:
   GIT_SUBMODULE_STRATEGY: recursive
 
 stages:
-  - build
   - test
   - ragdoc_build
   - ragdoc_view
-  - publish
+  - deploy
 
 before_script:
   - export GRADLE_USER_HOME=`pwd`/.gradle
@@ -16,24 +15,46 @@ cache:
     - .gradle/wrapper
     - .gradle/caches
 
-build:
-  image: openjdk:8
-  stage: build
+## Hidden jobs, base configurations
+.test:
+  image: openjdk:11
+  stage: test
   script:
-    - ./gradlew --console=plain --no-daemon assemble
+    - ./gradlew --console=plain --no-daemon test
   artifacts:
+    reports:
+      junit: build/test-results/test/**/TEST-*.xml
     paths:
       - "src/gen"
     expire_in: 1 week
 
-test:
-  image: openjdk:8
-  stage: test
+.publish_dev:
+  image: openjdk:11
+  stage: deploy
   script:
-    - ./gradlew --console=plain --no-daemon test
-  artifacts:
-    reports:
-      junit: build/test-results/test/**/TEST-*.xml
+    - "./gradlew setDevVersionForCI"
+    - "./gradlew publish"
+
+## Real jobs
+test8:
+  extends: .test
+  image: "openjdk:8"
+
+test11:
+  extends: .test
+  image: "openjdk:11"
+
+publish_dev8:
+  extends: .publish_dev
+  image: "openjdk:8"
+  needs:
+    - test8
+
+publish_dev11:
+  extends: .publish_dev
+  image: "openjdk:11"
+  needs:
+    - test11
 
 ragdoc_build:
   image:
@@ -41,10 +62,12 @@ ragdoc_build:
     entrypoint: [""]
   stage: ragdoc_build
   needs:
-    - build
+    - test8
   script:
     - JAVA_FILES=$(find src/ -name '*.java')
     - /ragdoc-builder/start-builder.sh -excludeGenerated -d data/ $JAVA_FILES
+  only:
+    - master
   artifacts:
     paths:
       - "data/"
@@ -62,23 +85,24 @@ ragdoc_view:
     - OUTPUT_DIR=$(pwd -P)/pages/docs/ragdoc
     - cd /ragdoc-view/src/ && rm -rf data && ln -s $DATA_DIR
     - /ragdoc-view/build-view.sh --output-path=$OUTPUT_DIR
+  only:
+    - master
   artifacts:
     paths:
       - "pages/docs/ragdoc"
 
 pages:
   image: python:3.8-buster
-  stage: publish
+  stage: deploy
   needs:
     - ragdoc_view
-    - test
+    - test8
   before_script:
     - pip install -U mkdocs mkdocs-macros-plugin mkdocs-git-revision-date-localized-plugin
   script:
     - cd pages && mkdocs build
-#  only:
-#    - develop
-#    - master
+  only:
+    - master
   artifacts:
     paths:
       - public
diff --git a/.gitmodules b/.gitmodules
index 7f348667c77aac1df614eddfa1721935c68dddcf..69c2eebb1346bd35222841bfde7ebea1a182f7c5 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "src/main/jastadd/mustache"]
 	path = src/main/jastadd/mustache
 	url = ../mustache.git
+	branch = main
diff --git a/build-template.gradle b/build-template.gradle
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/build.gradle b/build.gradle
index cc16a72d97cc4efff8a2a44fcba801746c912d07..32a4835d54d57e38279258795fd60930c5c75499 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,9 +1,11 @@
 plugins {
+    id 'com.github.ben-manes.versions' version '0.42.0'
     id 'java-library'
     id 'application'
-    id 'org.jastadd'
+    id 'org.jastadd' version "${jastaddgradle_version}"
     id 'java'
     id 'idea'
+    id 'maven-publish'
     id 'java-test-fixtures'
 }
 
@@ -11,14 +13,23 @@ ext {
     mainClassName = 'org.jastadd.relast.compiler.RelastSourceToSourceCompiler'
 }
 
+group = 'org.jastadd'
+
 // set the main class name for `gradle run`
 application.mainClassName = "${mainClassName}"
 
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
-
 repositories {
     mavenCentral()
+    maven {
+        name 'gitlab-maven'
+        url 'https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven'
+    }
+}
+
+configurations {
+    grammar2uml
+    relast
+    jss
 }
 
 sourceSets {
@@ -27,33 +38,65 @@ sourceSets {
             srcDir "src/gen/java"
         }
     }
+    main {
+        compileClasspath += sourceSets.model.output
+        resources {
+            srcDir "src/main/jastadd"
+        }
+    }
+    test {
+        runtimeClasspath += sourceSets.model.output
+    }
+}
+
+File genSrc = file("src/gen/java")
+idea.module.generatedSourceDirs += genSrc
+
+def versionFile = 'src/main/resources/RelASTPreprocessorVersion.properties'
+def oldProps = new Properties()
+
+try {
+    file(versionFile).withInputStream { stream -> oldProps.load(stream) }
+    version = oldProps['version']
+} catch (ignored) {
+    // this happens, if either the properties file is not present, or cannot be read from
+    throw new GradleException("File ${versionFile} not found or unreadable. Aborting.")
+}
+
+task newVersion() {
+    doFirst {
+        def props = new Properties()
+        props['version'] = value
+        props.store(file(versionFile).newWriter(), null)
+    }
 }
 
-task modelJar(type: Jar) {
-    group = "build"
-    archiveBaseName = 'model'
-    archiveVersion = ''
-    from sourceSets.model.output
+task printVersion() {
+    doLast {
+        println(version)
+    }
 }
 
-artifacts {
-    archives modelJar
+task setDevVersionForCI() {
+    doFirst {
+        def props = new Properties()
+        props['version'] = version + "-$System.env.CI_PIPELINE_IID"
+        props.store(file(versionFile).newWriter(), null)
+    }
 }
 
 dependencies {
 
-    modelImplementation group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
     modelImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 
-    implementation files(modelJar.archiveFile.get())
-    api group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
+    grammar2uml group: 'de.tudresden.inf.st', name: 'grammar2uml', version: "${grammar2uml_version}"
+    relast group: 'org.jastadd', name: 'relast', version: "${relast_version}"
+
+    api group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
     api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
-    implementation group: 'com.github.jknack', name: 'handlebars', version: '4.2.0'
+    implementation group: 'com.github.jknack', name: 'handlebars', version: '4.3.0'
     implementation group: 'org.yaml', name: 'snakeyaml', version: '1.27'
 
-    // test
-    testRuntimeClasspath files(modelJar.archiveFile.get())
-
     // test fixtures
     testFixturesApi group: 'org.slf4j', name: 'slf4j-jdk14', version: '1.7.30'
     testFixturesApi group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0'
@@ -64,25 +107,8 @@ dependencies {
     testFixturesApi group: 'commons-io', name: 'commons-io', version: '2.8.0'
 }
 
-def versionFile = 'src/main/resources/preprocessor.properties'
-def versionProps = new Properties()
-
-try {
-    file(versionFile).withInputStream { stream -> versionProps.load(stream) }
-    version = versionProps['version']
-} catch (e) {
-    // this happens, if either the properties file is not present, or cannot be read from
-    throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e)
-}
-
 jar {
-    manifest {
-        attributes "Main-Class": "${mainClassName}"
-    }
-
-    from {
-        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
-    }
+    archiveAppendix = "base"
 }
 
 test {
@@ -104,8 +130,9 @@ def relastOutputFiles = [
 ]
 
 task relast(type: JavaExec) {
-    classpath = files("libs/relast.jar")
     group = 'Build'
+    classpath = configurations.relast
+    mainClass = 'org.jastadd.relast.compiler.Compiler'
 
     doFirst {
         delete relastOutputFiles
@@ -184,12 +211,34 @@ jastadd {
     jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
 }
 
-generateAst.dependsOn relast
+// publish gitlab project
+publishing {
+    publications {
+        maven(MavenPublication) {
+            artifactId = project.getName() + (JavaVersion.current() == JavaVersion.VERSION_1_8 ? '-java8' : '')
 
-clean.dependsOn(cleanGen)
+            from components.java
+        }
+    }
+    repositories {
+        maven {
+            url "https://git-st.inf.tu-dresden.de/api/v4/projects/$System.env.CI_PROJECT_ID/packages/maven"
+            // Uncomment the following lines to publish manually (and comment out the other credentials section)
+//            credentials(HttpHeaderCredentials) {
+//                name = "Private-Token"
+//                value = gitLabPrivateToken // the variable resides in ~/.gradle/gradle.properties
+//            }
+            credentials(HttpHeaderCredentials) {
+                name = 'Job-Token'
+                value = System.getenv('CI_JOB_TOKEN')
+            }
+            authentication {
+                header(HttpHeaderAuthentication)
+            }
+        }
 
-modelJar.dependsOn(generateAst, modelClasses)
-modelClasses.dependsOn(generateAst)
-compileJava.dependsOn(modelJar)
+    }
+}
 
-jar.dependsOn(modelJar)
+generateAst.dependsOn relast
+clean.dependsOn cleanGen
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..0e0cc086ffe4f79f86c59464b82fb53d4e99d4cb
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,6 @@
+relast_version = 0.4.0
+relast2uml_version = 0.3.7-59
+jupyter_version = 5.8.2
+assertj_version = 3.22.0
+grammar2uml_version = 0.2.1
+jastaddgradle_version = 1.14.5
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index f3d88b1c2faf2fc91d853cd5d4242b5547257070..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index be52383ef49cdf484098989f96738b3d82d7810d..41dfb87909a877d96c3af4adccce4c7a301b55a2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 2fe81a7d95e4f9ad2c9b2a046707d36ceb3980b3..4f906e0c811fc9e230eb44819f509cd0627f2600 100755
--- a/gradlew
+++ b/gradlew
@@ -82,6 +82,7 @@ esac
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
+
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
 if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
     JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
diff --git a/gradlew.bat b/gradlew.bat
index 24467a141f791695fc1009c78d913b2c849d1412..ac1b06f93825db68fb0c0b5150917f340eaa5d02 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
 set APP_BASE_NAME=%~n0
 set APP_HOME=%DIRNAME%
 
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
 
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
 
 set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
 
 echo.
 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
 set JAVA_HOME=%JAVA_HOME:"=%
 set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
 
 echo.
 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
 
 goto fail
 
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
 :execute
 @rem Setup the command line
 
 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 
+
 @rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
 
 :end
 @rem End local scope for the variables with windows NT shell
diff --git a/libs/relast.jar b/libs/relast.jar
deleted file mode 100644
index 9f1d60c7c99a1e35d9cf5558d5f329c5aa7ba66e..0000000000000000000000000000000000000000
Binary files a/libs/relast.jar and /dev/null differ
diff --git a/pages/docs/using.md b/pages/docs/using.md
index a42891831f635201bd179762e0fa9fc01ed91774..5377ecae0873e6eed048be75a5381e2cd4a5e001 100644
--- a/pages/docs/using.md
+++ b/pages/docs/using.md
@@ -44,8 +44,9 @@ def relastOutputFiles = [
 ]
 
 task relast(type: JavaExec) {
-    classpath = files("relast.preprocessor/libs/relast.jar")
     group = 'Build'
+    classpath = configurations.relast
+    mainClass = 'org.jastadd.relast.compiler.Compiler'
 
     doFirst {
         delete relastOutputFiles
diff --git a/settings.gradle b/settings.gradle
index 5f99c5a83ca1b73205acb1de3960ccc501af1cc4..2aeef63375455191b0c0e5fc1f30bb3196c81807 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1 @@
-pluginManagement {
-    plugins {
-        id 'org.jastadd' version '1.13.3'
-    }
-}
+rootProject.name = 'relast-preprocessor'
\ No newline at end of file
diff --git a/src/main/jastadd/GeneratedNavigation.jrag b/src/main/jastadd/GeneratedNavigation.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..1034e7b2994bf35d3db51e8ecd725c643e7895c2
--- /dev/null
+++ b/src/main/jastadd/GeneratedNavigation.jrag
@@ -0,0 +1,315 @@
+aspect Navigation {
+
+  /** Tests if Comment is a WhitespaceComment.
+  *  @return 'true' if this is a WhitespaceComment, otherwise 'false'
+  */
+  syn boolean Comment.isWhitespaceComment() = false;
+  eq WhitespaceComment.isWhitespaceComment() = true;
+
+  /** Tests if Comment is a SingleLineComment.
+  *  @return 'true' if this is a SingleLineComment, otherwise 'false'
+  */
+  syn boolean Comment.isSingleLineComment() = false;
+  eq SingleLineComment.isSingleLineComment() = true;
+
+  /** Tests if Comment is a MultiLineComment.
+  *  @return 'true' if this is a MultiLineComment, otherwise 'false'
+  */
+  syn boolean Comment.isMultiLineComment() = false;
+  eq MultiLineComment.isMultiLineComment() = true;
+
+  /** Tests if Comment is a DocComment.
+  *  @return 'true' if this is a DocComment, otherwise 'false'
+  */
+  syn boolean Comment.isDocComment() = false;
+  eq DocComment.isDocComment() = true;
+
+  /** Tests if DirectedRelation is a LeftDirectedRelation.
+  *  @return 'true' if this is a LeftDirectedRelation, otherwise 'false'
+  */
+  syn boolean DirectedRelation.isLeftDirectedRelation() = false;
+  eq LeftDirectedRelation.isLeftDirectedRelation() = true;
+
+  /** Tests if DirectedRelation is a RightDirectedRelation.
+  *  @return 'true' if this is a RightDirectedRelation, otherwise 'false'
+  */
+  syn boolean DirectedRelation.isRightDirectedRelation() = false;
+  eq RightDirectedRelation.isRightDirectedRelation() = true;
+
+  /** Tests if TypeComponent is a NormalComponent.
+  *  @return 'true' if this is a NormalComponent, otherwise 'false'
+  */
+  syn boolean TypeComponent.isNormalComponent() = false;
+  eq NormalComponent.isNormalComponent() = true;
+
+  /** Tests if TypeComponent is a ListComponent.
+  *  @return 'true' if this is a ListComponent, otherwise 'false'
+  */
+  syn boolean TypeComponent.isListComponent() = false;
+  eq ListComponent.isListComponent() = true;
+
+  /** Tests if TypeComponent is a OptComponent.
+  *  @return 'true' if this is a OptComponent, otherwise 'false'
+  */
+  syn boolean TypeComponent.isOptComponent() = false;
+  eq OptComponent.isOptComponent() = true;
+
+  /** Tests if Grammar is a GrammarFile.
+  *  @return 'true' if this is a GrammarFile, otherwise 'false'
+  */
+  syn boolean Grammar.isGrammarFile() = false;
+  eq GrammarFile.isGrammarFile() = true;
+
+  /** Tests if NavigableRole is a NormalRole.
+  *  @return 'true' if this is a NormalRole, otherwise 'false'
+  */
+  syn boolean NavigableRole.isNormalRole() = false;
+  eq NormalRole.isNormalRole() = true;
+
+  /** Tests if NavigableRole is a ListRole.
+  *  @return 'true' if this is a ListRole, otherwise 'false'
+  */
+  syn boolean NavigableRole.isListRole() = false;
+  eq ListRole.isListRole() = true;
+
+  /** Tests if NavigableRole is a OptRole.
+  *  @return 'true' if this is a OptRole, otherwise 'false'
+  */
+  syn boolean NavigableRole.isOptRole() = false;
+  eq OptRole.isOptRole() = true;
+
+  /** Tests if Component is a TypeComponent.
+  *  @return 'true' if this is a TypeComponent, otherwise 'false'
+  */
+  syn boolean Component.isTypeComponent() = false;
+  eq TypeComponent.isTypeComponent() = true;
+
+  /** Tests if Component is a TokenComponent.
+  *  @return 'true' if this is a TokenComponent, otherwise 'false'
+  */
+  syn boolean Component.isTokenComponent() = false;
+  eq TokenComponent.isTokenComponent() = true;
+
+  /** Tests if JavaTypeUse is a SimpleJavaTypeUse.
+  *  @return 'true' if this is a SimpleJavaTypeUse, otherwise 'false'
+  */
+  syn boolean JavaTypeUse.isSimpleJavaTypeUse() = false;
+  eq SimpleJavaTypeUse.isSimpleJavaTypeUse() = true;
+
+  /** Tests if JavaTypeUse is a ParameterizedJavaTypeUse.
+  *  @return 'true' if this is a ParameterizedJavaTypeUse, otherwise 'false'
+  */
+  syn boolean JavaTypeUse.isParameterizedJavaTypeUse() = false;
+  eq ParameterizedJavaTypeUse.isParameterizedJavaTypeUse() = true;
+
+  /** Tests if Relation is a DirectedRelation.
+  *  @return 'true' if this is a DirectedRelation, otherwise 'false'
+  */
+  syn boolean Relation.isDirectedRelation() = false;
+  eq DirectedRelation.isDirectedRelation() = true;
+
+  /** Tests if Relation is a BidirectionalRelation.
+  *  @return 'true' if this is a BidirectionalRelation, otherwise 'false'
+  */
+  syn boolean Relation.isBidirectionalRelation() = false;
+  eq BidirectionalRelation.isBidirectionalRelation() = true;
+
+  /** Tests if Role is a NavigableRole.
+  *  @return 'true' if this is a NavigableRole, otherwise 'false'
+  */
+  syn boolean Role.isNavigableRole() = false;
+  eq NavigableRole.isNavigableRole() = true;
+
+  /** Tests if Role is a UnnamedRole.
+  *  @return 'true' if this is a UnnamedRole, otherwise 'false'
+  */
+  syn boolean Role.isUnnamedRole() = false;
+  eq UnnamedRole.isUnnamedRole() = true;
+
+  /** Tests if Declaration is a EmptyDeclaration.
+  *  @return 'true' if this is a EmptyDeclaration, otherwise 'false'
+  */
+  syn boolean Declaration.isEmptyDeclaration() = false;
+  eq EmptyDeclaration.isEmptyDeclaration() = true;
+
+  /** Tests if Declaration is a TypeDecl.
+  *  @return 'true' if this is a TypeDecl, otherwise 'false'
+  */
+  syn boolean Declaration.isTypeDecl() = false;
+  eq TypeDecl.isTypeDecl() = true;
+
+  /** Tests if Declaration is a Relation.
+  *  @return 'true' if this is a Relation, otherwise 'false'
+  */
+  syn boolean Declaration.isRelation() = false;
+  eq Relation.isRelation() = true;
+
+  /** casts a Comment into a WhitespaceComment if possible.
+   *  @return 'this' cast to a WhitespaceComment or 'null'
+   */
+  syn WhitespaceComment Comment.asWhitespaceComment();
+  eq Comment.asWhitespaceComment() = null;
+  eq WhitespaceComment.asWhitespaceComment() = this;
+
+  /** casts a Comment into a SingleLineComment if possible.
+   *  @return 'this' cast to a SingleLineComment or 'null'
+   */
+  syn SingleLineComment Comment.asSingleLineComment();
+  eq Comment.asSingleLineComment() = null;
+  eq SingleLineComment.asSingleLineComment() = this;
+
+  /** casts a Comment into a MultiLineComment if possible.
+   *  @return 'this' cast to a MultiLineComment or 'null'
+   */
+  syn MultiLineComment Comment.asMultiLineComment();
+  eq Comment.asMultiLineComment() = null;
+  eq MultiLineComment.asMultiLineComment() = this;
+
+  /** casts a Comment into a DocComment if possible.
+   *  @return 'this' cast to a DocComment or 'null'
+   */
+  syn DocComment Comment.asDocComment();
+  eq Comment.asDocComment() = null;
+  eq DocComment.asDocComment() = this;
+
+  /** casts a DirectedRelation into a LeftDirectedRelation if possible.
+   *  @return 'this' cast to a LeftDirectedRelation or 'null'
+   */
+  syn LeftDirectedRelation DirectedRelation.asLeftDirectedRelation();
+  eq DirectedRelation.asLeftDirectedRelation() = null;
+  eq LeftDirectedRelation.asLeftDirectedRelation() = this;
+
+  /** casts a DirectedRelation into a RightDirectedRelation if possible.
+   *  @return 'this' cast to a RightDirectedRelation or 'null'
+   */
+  syn RightDirectedRelation DirectedRelation.asRightDirectedRelation();
+  eq DirectedRelation.asRightDirectedRelation() = null;
+  eq RightDirectedRelation.asRightDirectedRelation() = this;
+
+  /** casts a TypeComponent into a NormalComponent if possible.
+   *  @return 'this' cast to a NormalComponent or 'null'
+   */
+  syn NormalComponent TypeComponent.asNormalComponent();
+  eq TypeComponent.asNormalComponent() = null;
+  eq NormalComponent.asNormalComponent() = this;
+
+  /** casts a TypeComponent into a ListComponent if possible.
+   *  @return 'this' cast to a ListComponent or 'null'
+   */
+  syn ListComponent TypeComponent.asListComponent();
+  eq TypeComponent.asListComponent() = null;
+  eq ListComponent.asListComponent() = this;
+
+  /** casts a TypeComponent into a OptComponent if possible.
+   *  @return 'this' cast to a OptComponent or 'null'
+   */
+  syn OptComponent TypeComponent.asOptComponent();
+  eq TypeComponent.asOptComponent() = null;
+  eq OptComponent.asOptComponent() = this;
+
+  /** casts a Grammar into a GrammarFile if possible.
+   *  @return 'this' cast to a GrammarFile or 'null'
+   */
+  syn GrammarFile Grammar.asGrammarFile();
+  eq Grammar.asGrammarFile() = null;
+  eq GrammarFile.asGrammarFile() = this;
+
+  /** casts a NavigableRole into a NormalRole if possible.
+   *  @return 'this' cast to a NormalRole or 'null'
+   */
+  syn NormalRole NavigableRole.asNormalRole();
+  eq NavigableRole.asNormalRole() = null;
+  eq NormalRole.asNormalRole() = this;
+
+  /** casts a NavigableRole into a ListRole if possible.
+   *  @return 'this' cast to a ListRole or 'null'
+   */
+  syn ListRole NavigableRole.asListRole();
+  eq NavigableRole.asListRole() = null;
+  eq ListRole.asListRole() = this;
+
+  /** casts a NavigableRole into a OptRole if possible.
+   *  @return 'this' cast to a OptRole or 'null'
+   */
+  syn OptRole NavigableRole.asOptRole();
+  eq NavigableRole.asOptRole() = null;
+  eq OptRole.asOptRole() = this;
+
+  /** casts a Component into a TypeComponent if possible.
+   *  @return 'this' cast to a TypeComponent or 'null'
+   */
+  syn TypeComponent Component.asTypeComponent();
+  eq Component.asTypeComponent() = null;
+  eq TypeComponent.asTypeComponent() = this;
+
+  /** casts a Component into a TokenComponent if possible.
+   *  @return 'this' cast to a TokenComponent or 'null'
+   */
+  syn TokenComponent Component.asTokenComponent();
+  eq Component.asTokenComponent() = null;
+  eq TokenComponent.asTokenComponent() = this;
+
+  /** casts a JavaTypeUse into a SimpleJavaTypeUse if possible.
+   *  @return 'this' cast to a SimpleJavaTypeUse or 'null'
+   */
+  syn SimpleJavaTypeUse JavaTypeUse.asSimpleJavaTypeUse();
+  eq JavaTypeUse.asSimpleJavaTypeUse() = null;
+  eq SimpleJavaTypeUse.asSimpleJavaTypeUse() = this;
+
+  /** casts a JavaTypeUse into a ParameterizedJavaTypeUse if possible.
+   *  @return 'this' cast to a ParameterizedJavaTypeUse or 'null'
+   */
+  syn ParameterizedJavaTypeUse JavaTypeUse.asParameterizedJavaTypeUse();
+  eq JavaTypeUse.asParameterizedJavaTypeUse() = null;
+  eq ParameterizedJavaTypeUse.asParameterizedJavaTypeUse() = this;
+
+  /** casts a Relation into a DirectedRelation if possible.
+   *  @return 'this' cast to a DirectedRelation or 'null'
+   */
+  syn DirectedRelation Relation.asDirectedRelation();
+  eq Relation.asDirectedRelation() = null;
+  eq DirectedRelation.asDirectedRelation() = this;
+
+  /** casts a Relation into a BidirectionalRelation if possible.
+   *  @return 'this' cast to a BidirectionalRelation or 'null'
+   */
+  syn BidirectionalRelation Relation.asBidirectionalRelation();
+  eq Relation.asBidirectionalRelation() = null;
+  eq BidirectionalRelation.asBidirectionalRelation() = this;
+
+  /** casts a Role into a NavigableRole if possible.
+   *  @return 'this' cast to a NavigableRole or 'null'
+   */
+  syn NavigableRole Role.asNavigableRole();
+  eq Role.asNavigableRole() = null;
+  eq NavigableRole.asNavigableRole() = this;
+
+  /** casts a Role into a UnnamedRole if possible.
+   *  @return 'this' cast to a UnnamedRole or 'null'
+   */
+  syn UnnamedRole Role.asUnnamedRole();
+  eq Role.asUnnamedRole() = null;
+  eq UnnamedRole.asUnnamedRole() = this;
+
+  /** casts a Declaration into a EmptyDeclaration if possible.
+   *  @return 'this' cast to a EmptyDeclaration or 'null'
+   */
+  syn EmptyDeclaration Declaration.asEmptyDeclaration();
+  eq Declaration.asEmptyDeclaration() = null;
+  eq EmptyDeclaration.asEmptyDeclaration() = this;
+
+  /** casts a Declaration into a TypeDecl if possible.
+   *  @return 'this' cast to a TypeDecl or 'null'
+   */
+  syn TypeDecl Declaration.asTypeDecl();
+  eq Declaration.asTypeDecl() = null;
+  eq TypeDecl.asTypeDecl() = this;
+
+  /** casts a Declaration into a Relation if possible.
+   *  @return 'this' cast to a Relation or 'null'
+   */
+  syn Relation Declaration.asRelation();
+  eq Declaration.asRelation() = null;
+  eq Relation.asRelation() = this;
+
+}
diff --git a/src/main/jastadd/Navigation.jrag b/src/main/jastadd/Navigation.jrag
index 5a9e43ffeb17a301c61a53fbbc1f30622a61193a..7834c25eced07c31cc2c3331f05965ead638fb45 100644
--- a/src/main/jastadd/Navigation.jrag
+++ b/src/main/jastadd/Navigation.jrag
@@ -2,6 +2,8 @@ aspect Navigation {
 
   // --- program ---
   inh Program ASTNode.program();
+  eq Grammar.getChild().program() = null;
+  eq GrammarFile.getChild().program() = program();
   eq Program.getChild().program() = this;
 
   // --- typeDecls ---
@@ -28,66 +30,10 @@ aspect Navigation {
 
   // --- containedFile ---
   inh GrammarFile ASTNode.containedFile();
+  eq Grammar.getChild().containedFile() = null;
   eq Program.getChild().containedFile() = null;
   eq GrammarFile.getChild().containedFile() = this;
 
   // --- containedFileName ---
-  inh String ASTNode.containedFileName();
-  eq GrammarFile.getChild().containedFileName() = getFileName();
-
-  // --- isTokenComponent ---
-  syn boolean Component.isTokenComponent() = false;
-  eq TokenComponent.isTokenComponent() = true;
-
-  // --- asTokenComponent ---
-  syn TokenComponent Component.asTokenComponent() = null;
-  eq TokenComponent.asTokenComponent() = this;
-
-  // --- isTypeDecl (should be in preprocessor) ---
-  syn boolean Declaration.isTypeDecl() = false;
-  eq TypeDecl.isTypeDecl() = true;
-
-  // --- asTypeDecl (should be in preprocessor) ---
-  syn TypeDecl Declaration.asTypeDecl() = null;
-  eq TypeDecl.asTypeDecl() = this;
-
-  // --- isTypeComponent (should be in preprocessor) ---
-  syn boolean Component.isTypeComponent() = false;
-  eq TypeComponent.isTypeComponent() = true;
-
-  // --- asTypeComponent (should be in preprocessor) ---
-  syn TypeComponent Component.asTypeComponent() = null;
-  eq TypeComponent.asTypeComponent() = this;
-
-  // --- isNormalComponent (should be in preprocessor) ---
-  syn boolean Component.isNormalComponent() = false;
-  eq NormalComponent.isNormalComponent() = true;
-
-  // --- asNormalComponent (should be in preprocessor) ---
-  syn NormalComponent Component.asNormalComponent() = null;
-  eq NormalComponent.asNormalComponent() = this;
-
-  // --- isListComponent (should be in preprocessor) ---
-  syn boolean Component.isListComponent() = false;
-  eq ListComponent.isListComponent() = true;
-
-  // --- asListComponent (should be in preprocessor) ---
-  syn ListComponent Component.asListComponent() = null;
-  eq ListComponent.asListComponent() = this;
-
-  // --- isDirectedRelation (should be in preprocessor) ---
-  syn boolean Relation.isDirectedRelation() = false;
-  eq DirectedRelation.isDirectedRelation() = true;
-
-  // --- asDirectedRelation (should be in preprocessor) ---
-  syn DirectedRelation Relation.asDirectedRelation() = null;
-  eq DirectedRelation.asDirectedRelation() = this;
-
-  // --- asBidirectionalRelation (should be in preprocessor) ---
-  syn BidirectionalRelation Relation.asBidirectionalRelation() = null;
-  eq BidirectionalRelation.asBidirectionalRelation() = this;
-
-  // --- isListRole (should be in preprocessor) ---
-  syn boolean Role.isListRole() = false;
-  eq ListRole.isListRole() = true;
+  syn String ASTNode.containedFileName() = containedFile().getFileName();
 }
diff --git a/src/main/jastadd/backend/AbstractGrammar.jadd b/src/main/jastadd/backend/AbstractGrammar.jadd
index 3683fe27c2fba45459a5e7eb325f62ab1ad8e102..6cef6edff2052183efba997bdf25295ce4cedf32 100644
--- a/src/main/jastadd/backend/AbstractGrammar.jadd
+++ b/src/main/jastadd/backend/AbstractGrammar.jadd
@@ -127,6 +127,7 @@ aspect BackendAbstractGrammar {
   }
 
   public void ParameterizedJavaTypeUse.generateAbstractGrammar(StringBuilder b) {
+    b.append(getName());
     b.append("<");
     boolean first = true;
     for (JavaTypeUse javaTypeUse : getJavaTypeUseList()) {
diff --git a/src/main/jastadd/mustache b/src/main/jastadd/mustache
index c10bed0d03e3fa18b8133ce1de48de7646899615..b9ad898b4da5d53ede0107c76f9f73fe818073a1 160000
--- a/src/main/jastadd/mustache
+++ b/src/main/jastadd/mustache
@@ -1 +1 @@
-Subproject commit c10bed0d03e3fa18b8133ce1de48de7646899615
+Subproject commit b9ad898b4da5d53ede0107c76f9f73fe818073a1
diff --git a/src/main/jastadd/parser/RelAst.parser b/src/main/jastadd/parser/RelAst.parser
index c4cfd43d096b226f9ee3c98fe5f75975be4f10e1..628e452c42cd935902cd79ef1293b8c46a1fb0b5 100644
--- a/src/main/jastadd/parser/RelAst.parser
+++ b/src/main/jastadd/parser/RelAst.parser
@@ -1,6 +1,5 @@
 GrammarFile goal
   = comment_list.c grammar_file.f {: f.getDeclarationList().insertChild(new EmptyDeclaration(c), 0); return f; :}
-  | grammar_file
 ;
 
 GrammarFile grammar_file
@@ -16,8 +15,8 @@ Declaration declaration
 // this method would be create by the JAstAddParser from a usage of
 // 'comment+' in a rule, but only for the standard list class 'List'.
 JastAddList comment_list
-  = comment.n {: return new JastAddList().add(n); :}
-  | comment_list.l comment.n {: return l.add(n); :}
+  = comment.c comment_list.l {: l.insertChild(c, 0); return l; :}
+  | /* epsilon */            {: return new JastAddList(); :}
 ;
 
 Comment comment
diff --git a/src/main/jastadd/scanner/Keywords.flex b/src/main/jastadd/scanner/Keywords.flex
index 76397b9efcd1c748a3a1eb69675d23dbd4042178..27524e3d2ee6768c8ec6117059967c5780be15ce 100644
--- a/src/main/jastadd/scanner/Keywords.flex
+++ b/src/main/jastadd/scanner/Keywords.flex
@@ -1,4 +1,4 @@
-<YYINITIAL,DECLARATION> {
+<YYINITIAL,COMMENT,DECLARATION> {
   "abstract"            { yybegin(DECLARATION); return sym(Terminals.ABSTRACT); }
   "rel"                 { yybegin(DECLARATION); return sym(Terminals.RELATION); }
 }
diff --git a/src/main/jastadd/scanner/RulesPostamble.flex b/src/main/jastadd/scanner/RulesPostamble.flex
index 5460a0b87e2002c06430aa5cc88abf0b39f30b9f..37f546ff66770d94feaa71ff8b74bd4462f190d8 100644
--- a/src/main/jastadd/scanner/RulesPostamble.flex
+++ b/src/main/jastadd/scanner/RulesPostamble.flex
@@ -1,7 +1,7 @@
-<YYINITIAL,DECLARATION> {
+<YYINITIAL,COMMENT,DECLARATION> {
   {ID}                  { yybegin(DECLARATION); return sym(Terminals.ID); }
-  [^]                   { throw new ScannerError((yyline+1) +"," + (yycolumn+1) + ": Illegal character <"+yytext()+">"); }
 }
-<YYINITIAL,DECLARATION,COMMENT> {
+<YYINITIAL,COMMENT,DECLARATION> {
   <<EOF>>               { return sym(Terminals.EOF); }
-}
+  [^]                   { throw new ScannerError((yyline+1) +"," + (yycolumn+1) + ": Illegal character <"+yytext()+">"); }
+}
\ No newline at end of file
diff --git a/src/main/jastadd/scanner/Symbols.flex b/src/main/jastadd/scanner/Symbols.flex
index 7f05c115b570fd3681617106bb77bc25bb79d7b2..5ac1c2d1855c91514b2d18483896ae0bb5ba1d08 100644
--- a/src/main/jastadd/scanner/Symbols.flex
+++ b/src/main/jastadd/scanner/Symbols.flex
@@ -1,4 +1,4 @@
-<YYINITIAL,DECLARATION> {
+<YYINITIAL,COMMENT,DECLARATION> {
   ";"                   { yybegin(COMMENT);     return sym(Terminals.SCOL); }
   ":"                   { yybegin(DECLARATION); return sym(Terminals.COL); }
   "::="                 { yybegin(DECLARATION); return sym(Terminals.ASSIGN); }
diff --git a/src/main/java/org/jastadd/PreprocessorConfiguration.java b/src/main/java/org/jastadd/PreprocessorConfiguration.java
index 39fd43963ecdc1cece0649cf3e1123ed228e4505..913b8b56532b33566db14180377d78be0dae0777 100644
--- a/src/main/java/org/jastadd/PreprocessorConfiguration.java
+++ b/src/main/java/org/jastadd/PreprocessorConfiguration.java
@@ -140,7 +140,6 @@ public class PreprocessorConfiguration extends org.jastadd.Configuration {
     allOptions.add(cacheCycleOption);
     allOptions.add(componentCheckOption);
     allOptions.add(inhEqCheckOption);
-    allOptions.add(suppressWarningsOption);
     allOptions.add(refineLegacyOption);
     allOptions.add(licenseOption);
     allOptions.add(debugOption);
@@ -180,27 +179,6 @@ public class PreprocessorConfiguration extends org.jastadd.Configuration {
     // New since 2.3.4
     allOptions.add(optimizeImports);
 
-    // Deprecated in 2.1.5.
-    allOptions.add(doxygenOption);
-    allOptions.add(cacheAllOption);
-    allOptions.add(noCachingOption);
-    allOptions.add(cacheNoneOption);
-    allOptions.add(cacheImplicitOption);
-    allOptions.add(ignoreLazyOption);
-    allOptions.add(fullFlushOption);
-
-    // Deprecated in 2.1.9.
-    allOptions.add(docOption);
-    allOptions.add(java1_4Option); // Disabled in 2.1.10.
-    allOptions.add(noLazyMapsOption);
-    allOptions.add(noVisitCheckOption);
-    allOptions.add(noCacheCycleOption);
-    allOptions.add(noRefineLegacyOption);
-    allOptions.add(noComponentCheckOption);
-    allOptions.add(noInhEqCheckOption);
-    allOptions.add(noStaticOption);
-    allOptions.add(deterministicOption);
-
     return allOptions;
   }
 
diff --git a/src/main/java/org/jastadd/relast/compiler/Mustache.java b/src/main/java/org/jastadd/relast/compiler/Mustache.java
index 24d368abf9d13cbca1882caef1a49084943499a9..e5b09f3922452275ae598c32332af6cf4a0a4722 100644
--- a/src/main/java/org/jastadd/relast/compiler/Mustache.java
+++ b/src/main/java/org/jastadd/relast/compiler/Mustache.java
@@ -39,9 +39,11 @@ public class Mustache {
 
     Handlebars handlebars = new Handlebars(loader);
     handlebars.prettyPrint(true); // set handlebars to mustache mode (skip some whitespace)
+    handlebars.infiniteLoops(true); // allow partial recursion
     Template template = handlebars.compile(templateFileName);
 
     try (Writer w = new FileWriter(outputFileName)) {
+      System.out.println("Writing " + outputFileName);
       template.apply(context, w);
       w.flush();
     }
diff --git a/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java b/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java
index 943583517f406e9b557e3bb34262b15185b38438..858934970ccb72e5db62ed427210d25b0f0db99e 100644
--- a/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java
+++ b/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java
@@ -25,9 +25,20 @@ public abstract class RelAstProcessor extends AbstractCompiler {
     super(name, jastAddCompliant);
   }
 
-  protected boolean isGrammarFile(String fileName) {
-    String extension = fileName.subSequence(fileName.lastIndexOf('.'), fileName.length()).toString();
-    return extension.equals(".relast") || extension.equals(".ast");
+  protected boolean isGrammarFile(Path path) {
+    if (path.getFileName() == null) { return false; }
+    String fileName = path.getFileName().toString();
+    int dotIndex = fileName.lastIndexOf('.');
+    if (dotIndex < 0) {
+      printMessage(path + " has no extension, ignoring it.");
+      return false;
+    }
+    String extension = fileName.subSequence(dotIndex, fileName.length()).toString();
+    boolean isGrammar = extension.equals(".relast") || extension.equals(".ast");
+    if (!isGrammar) {
+      printMessage(path + " is not a grammar file, ignoring it.");
+    }
+    return isGrammar;
   }
 
   @Override
@@ -44,14 +55,14 @@ public abstract class RelAstProcessor extends AbstractCompiler {
       inputBasePath = Paths.get(optionInputBaseDir.value()).toAbsolutePath();
     } else {
       inputBasePath = Paths.get(".").toAbsolutePath();
-      printMessage("No input base dir is set. Assuming current directory '" + inputBasePath.toAbsolutePath().toString() + "'.");
+      printMessage("No input base dir is set. Assuming current directory '" + inputBasePath.toAbsolutePath() + "'.");
     }
 
     if (!inputBasePath.toFile().exists()) {
-      printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' does not exist. Exiting...");
+      printMessage("Input path '" + inputBasePath.toAbsolutePath() + "' does not exist. Exiting...");
       System.exit(-1);
     } else if (!inputBasePath.toFile().isDirectory()) {
-      printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' is not a directory. Exiting...");
+      printMessage("Input path '" + inputBasePath.toAbsolutePath() + "' is not a directory. Exiting...");
       System.exit(-1);
     }
 
@@ -63,29 +74,29 @@ public abstract class RelAstProcessor extends AbstractCompiler {
     }
 
     if (outputBasePath.toFile().exists() && !outputBasePath.toFile().isDirectory()) {
-      printMessage("Output path '" + inputBasePath.toAbsolutePath().toString() + "' exists, but is not a directory. Exiting...");
+      printMessage("Output path '" + inputBasePath.toAbsolutePath() + "' exists, but is not a directory. Exiting...");
     }
 
     printMessage("Running " + getName());
 
     // gather all files
     Collection<Path> inputFiles = new ArrayList<>();
-    getConfiguration().getFiles().forEach(name -> relativizeFileName(inputBasePath, Paths.get(name)).ifPresent(inputFiles::add));
+    getConfiguration().getFiles().forEach(name -> checkFileName(inputBasePath, Paths.get(name)).ifPresent(inputFiles::add));
 
 
-    Program program = parseProgram(inputFiles);
+    Program program = parseProgram(inputBasePath, inputFiles);
 
     return processGrammar(program, inputBasePath, outputBasePath);
   }
 
   protected abstract int processGrammar(Program program, Path inputBasePath, Path outputBasePath) throws CompilerException;
 
-  private Optional<Path> relativizeFileName(Path inputBasePath, Path filePath) {
+  private Optional<Path> checkFileName(Path inputBasePath, Path filePath) {
     if (filePath.isAbsolute()) {
-      if (filePath.startsWith(inputBasePath)) {
-        return Optional.of(filePath.relativize(inputBasePath));
+      if (filePath.normalize().startsWith(inputBasePath.normalize())) {
+        return Optional.of(filePath);
       } else {
-        printMessage("Path '" + filePath + "' is not contained in the base path '" + inputBasePath + "'.");
+        printMessage("Path '" + filePath + "' is not contained in the base path '" + inputBasePath + "', ignoring it.");
         return Optional.empty();
       }
     } else {
@@ -107,19 +118,18 @@ public abstract class RelAstProcessor extends AbstractCompiler {
     }
   }
 
-  private Program parseProgram(Collection<Path> inputFiles) {
+  private Program parseProgram(Path inputBasePath, Collection<Path> inputFiles) {
     Program program = new Program();
 
     RelAstParser parser = new RelAstParser();
 
-    inputFiles.stream().filter(path -> isGrammarFile(path.toString())).forEach(
+    inputFiles.stream().filter(this::isGrammarFile).forEach(
         path -> {
           try (BufferedReader reader = Files.newBufferedReader(path)) {
             RelAstScanner scanner = new RelAstScanner(reader);
             GrammarFile inputGrammar = (GrammarFile) parser.parse(scanner);
-            inputGrammar.setFileName(path.toString());
+            inputGrammar.setFileName(inputBasePath.relativize(path).toString());
             program.addGrammarFile(inputGrammar);
-            inputGrammar.treeResolveAll();
           } catch (IOException | beaver.Parser.Exception e) {
             printMessage("Could not parse grammar file " + path);
             e.printStackTrace();
@@ -127,6 +137,8 @@ public abstract class RelAstProcessor extends AbstractCompiler {
         }
     );
 
+    program.treeResolveAll();
+
     return program;
   }
 }
diff --git a/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java
index 696c3c17ef59872ffaf174d4b8e4b4b7e8e9bdd1..b3347cc3ec74ca1812805df5e59433ad3066e9ed 100644
--- a/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java
+++ b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java
@@ -22,12 +22,6 @@ public class RelastSourceToSourceCompiler extends RelAstProcessor {
     }
   }
 
-  @Override
-  protected boolean isGrammarFile(String fileName) {
-    String extension = fileName.subSequence(fileName.lastIndexOf("."), fileName.length()).toString();
-    return extension.equals(".relast") || extension.equals(".ast");
-  }
-
   @Override
   protected int processGrammar(Program program, Path inputBasePath, Path outputBasePath) throws CompilerException {
 
@@ -36,7 +30,7 @@ public class RelastSourceToSourceCompiler extends RelAstProcessor {
     for (GrammarFile grammarFile : program.getGrammarFileList()) {
       printMessage("Writing output file " + grammarFile.getFileName());
       // TODO decide and document what the file name should be, the full path or a simple name?
-      writeToFile(outputBasePath.resolve(inputBasePath.relativize(Paths.get(grammarFile.getFileName()))), grammarFile.generateAbstractGrammar());
+      writeToFile(outputBasePath.resolve(grammarFile.getFileName()), grammarFile.generateAbstractGrammar());
     }
     return 0;
   }
diff --git a/src/main/resources/RelASTPreprocessorVersion.properties b/src/main/resources/RelASTPreprocessorVersion.properties
new file mode 100644
index 0000000000000000000000000000000000000000..de55ab654e5845e918eedddeabe2aea9b9518b5c
--- /dev/null
+++ b/src/main/resources/RelASTPreprocessorVersion.properties
@@ -0,0 +1 @@
+version=0.1.0
diff --git a/src/main/resources/preprocessor.properties b/src/main/resources/preprocessor.properties
deleted file mode 100644
index fb55bf09edd40713c458341e14b57714f11924db..0000000000000000000000000000000000000000
--- a/src/main/resources/preprocessor.properties
+++ /dev/null
@@ -1 +0,0 @@
-version=1.0.0-pre-release
diff --git a/src/test/resources/GrammarFileOrder/config.yaml b/src/test/resources/GrammarFileOrder/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d18791ee656f768847a06afdd6a2501e590f4011
--- /dev/null
+++ b/src/test/resources/GrammarFileOrder/config.yaml
@@ -0,0 +1,11 @@
+- name: "dependencies between relast files"
+  args:
+    - "--inputBaseDir=in"
+    - "--outputBaseDir=out"
+    - "Rel.relast"
+    - "A.relast"
+    - "B.relast"
+    - "C.relast"
+  out: "out"
+  expected: "in"
+  compare: true
diff --git a/src/test/resources/GrammarFileOrder/in/A.relast b/src/test/resources/GrammarFileOrder/in/A.relast
new file mode 100644
index 0000000000000000000000000000000000000000..3089a16f06dc47eb87be35f073bb21d050023730
--- /dev/null
+++ b/src/test/resources/GrammarFileOrder/in/A.relast
@@ -0,0 +1,3 @@
+A ::= A1 A2;
+A1 ::= B;
+A2 ::= C;
diff --git a/src/test/resources/GrammarFileOrder/in/B.relast b/src/test/resources/GrammarFileOrder/in/B.relast
new file mode 100644
index 0000000000000000000000000000000000000000..a98f9404c6c3687aff85b0ad119a2d19d96dbd0a
--- /dev/null
+++ b/src/test/resources/GrammarFileOrder/in/B.relast
@@ -0,0 +1,3 @@
+B ::= B1 B2;
+B1 ::= A;
+B2 ::= C;
diff --git a/src/test/resources/GrammarFileOrder/in/C.relast b/src/test/resources/GrammarFileOrder/in/C.relast
new file mode 100644
index 0000000000000000000000000000000000000000..ff70879a33a29cd911905ebd97a010a9c8961d59
--- /dev/null
+++ b/src/test/resources/GrammarFileOrder/in/C.relast
@@ -0,0 +1,3 @@
+C ::= [C1] [C2];
+C1 ::= A;
+C2 ::= B;
diff --git a/src/test/resources/GrammarFileOrder/in/Rel.relast b/src/test/resources/GrammarFileOrder/in/Rel.relast
new file mode 100644
index 0000000000000000000000000000000000000000..6acc2faab21dba3b4100cc4b52205142c86f39d4
--- /dev/null
+++ b/src/test/resources/GrammarFileOrder/in/Rel.relast
@@ -0,0 +1,3 @@
+rel A.b? -> B;
+rel B.c? -> C;
+rel C.a? -> A;
diff --git a/src/test/resources/MinimalGrammar/config.yaml b/src/test/resources/MinimalGrammar/config.yaml
index 135f6bd74b057a00cc5582b7ca78e83995167e48..6cb1344294db04658dfc2c43e0bb339140017c14 100644
--- a/src/test/resources/MinimalGrammar/config.yaml
+++ b/src/test/resources/MinimalGrammar/config.yaml
@@ -4,6 +4,7 @@
     - "--outputBaseDir=out"
     - "Example.relast"
     - "CommentInFront.relast"
+    - "DefaultTypeOfToken.ast"
   out: "out"
   expected: "in"
   compare: true
diff --git a/src/test/resources/MinimalGrammar/in/DefaultTypeOfToken.ast b/src/test/resources/MinimalGrammar/in/DefaultTypeOfToken.ast
new file mode 100644
index 0000000000000000000000000000000000000000..ebc30a7668962c74dffd8aa66461576612b6eb7e
--- /dev/null
+++ b/src/test/resources/MinimalGrammar/in/DefaultTypeOfToken.ast
@@ -0,0 +1 @@
+A ::= <B>;
diff --git a/src/test/resources/MinimalGrammar/in/Example.relast b/src/test/resources/MinimalGrammar/in/Example.relast
index 7c28b7bcd84c826aedd622460eb93da08ee80c3d..64200adaac4461f09ada5fc7342aa5a085032be7 100644
--- a/src/test/resources/MinimalGrammar/in/Example.relast
+++ b/src/test/resources/MinimalGrammar/in/Example.relast
@@ -11,3 +11,5 @@ Joint ::= <Name> <CurrentPosition:IntPosition>;  // normally this would be: <Cur
 EndEffector : Joint;
 
 Coordinate ::= <Position:IntPosition>;
+
+TestingParameterized : Coordinate ::= <ListPosition:java.util.List<IntPosition>>;
diff --git a/src/test/resources/NoWhitespaceAfterRules/config.yaml b/src/test/resources/NoWhitespaceAfterRules/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0b4111a0c86ed8510b2a9d72b02a58319e246f7b
--- /dev/null
+++ b/src/test/resources/NoWhitespaceAfterRules/config.yaml
@@ -0,0 +1,9 @@
+- name: "No whitespace/comment between and after rules required"
+  args:
+    - "--inputBaseDir=in"
+    - "--outputBaseDir=out"
+    - "Grammar.relast"
+    - "Relation.relast"
+  out: "out"
+  expected: "in"
+  compare: true
diff --git a/src/test/resources/NoWhitespaceAfterRules/in/Grammar.relast b/src/test/resources/NoWhitespaceAfterRules/in/Grammar.relast
new file mode 100644
index 0000000000000000000000000000000000000000..5ef2b62144e2711ce5bb834bd332088f57725226
--- /dev/null
+++ b/src/test/resources/NoWhitespaceAfterRules/in/Grammar.relast
@@ -0,0 +1,2 @@
+Person ::= <FullName> <Address> Gender;abstract Gender;
+Male : Gender;Female : Gender;Diverse : Gender;
\ No newline at end of file
diff --git a/src/test/resources/NoWhitespaceAfterRules/in/Relation.relast b/src/test/resources/NoWhitespaceAfterRules/in/Relation.relast
new file mode 100644
index 0000000000000000000000000000000000000000..3401591959fee1ffdbdf9d8e83c7c7d63b1045ce
--- /dev/null
+++ b/src/test/resources/NoWhitespaceAfterRules/in/Relation.relast
@@ -0,0 +1 @@
+rel Person.Friend -> Person;rel Person.Child <-> Person.Parent;
\ No newline at end of file
diff --git a/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java b/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java
index e83746c04a4a85327eabf5db0a1de580e6a42e3c..250295942024ec2badc3a87d538ef60177b80348 100644
--- a/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java
+++ b/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java
@@ -8,14 +8,13 @@ import org.apache.commons.io.filefilter.FileFilterUtils;
 import org.apache.commons.io.filefilter.TrueFileFilter;
 import org.assertj.core.util.Files;
 import org.jastadd.relast.tests.config.Configuration;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DynamicTest;
-import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.*;
 
 import java.io.*;
 import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.util.*;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 public class RelAstProcessorTestBase {
@@ -77,41 +76,51 @@ public class RelAstProcessorTestBase {
     return runProcess(workingDirectory, command, outStringBuider, errStringBuilder);
   }
 
-  protected void directoryTest(Class<?> mainClass, Path dir) throws IOException, InterruptedException {
+  protected Iterator<DynamicNode> directoryTest(Class<?> mainClass, Path dir) {
     dir = dir.toAbsolutePath();
     Path configFile = dir.resolve("config.yaml");
     ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
-    List<Configuration> configs = mapper.readValue(configFile.toFile(), new TypeReference<List<Configuration>>() {
-    });
+    List<Configuration> configs = null;
+    try {
+      configs = mapper.readValue(configFile.toFile(), new TypeReference<List<Configuration>>() {
+      });
+    } catch (IOException e) {
+      e.printStackTrace(System.err);
+      return Collections.emptyIterator();
+    }
 
-    for (Configuration config : configs) {
+    Path finalDir = dir;
 
-      FileUtils.forceMkdir(dir.resolve(config.getOut()).toFile());
-      FileUtils.cleanDirectory(dir.resolve(config.getOut()).toFile());
+    return configs.stream().map(config ->
+            (DynamicNode) DynamicTest.dynamicTest(config.getName(), () -> {
 
-      StringBuilder outBuilder = new StringBuilder();
-      StringBuilder errBuilder = new StringBuilder();
-      int returnValue = runJavaProcess(mainClass, dir.toFile(), Arrays.asList(config.getArgs()), outBuilder, errBuilder);
-      String out = outBuilder.toString();
-      String err = errBuilder.toString();
+        FileUtils.forceMkdir(finalDir.resolve(config.getOut()).toFile());
+        FileUtils.cleanDirectory(finalDir.resolve(config.getOut()).toFile());
 
-      System.out.println(out);
-      System.err.println(err);
+        StringBuilder outBuilder = new StringBuilder();
+        StringBuilder errBuilder = new StringBuilder();
+        int returnValue = runJavaProcess(mainClass, finalDir.toFile(), Arrays.asList(config.getArgs()), outBuilder, errBuilder);
+        String out = outBuilder.toString();
+        String err = errBuilder.toString();
 
-      if (config.shouldFail()) {
-        Assertions.assertNotEquals(0, returnValue, config.getName() + ": Zero return value of preprocessor for negative test.");
-      } else {
-        Assertions.assertEquals(0, returnValue, config.getName() + ": Non-Zero return value of preprocessor for positive test.");
-      }
+        System.out.println(out);
+        System.err.println(err);
 
-      checkOutput(config, out, err);
+        if (config.shouldFail()) {
+          Assertions.assertNotEquals(0, returnValue, config.getName() + ": Zero return value of preprocessor for negative test.");
+        } else {
+          Assertions.assertEquals(0, returnValue, config.getName() + ": Non-Zero return value of preprocessor for positive test.");
+        }
 
-      if (config.shouldCompare()) {
-        Path outPath = dir.resolve(config.getOut());
-        Path expectedPath = dir.resolve(config.getExpected());
-        comparePaths(outPath, expectedPath);
-      }
-    }
+        checkOutput(config, out, err);
+
+        if (config.shouldCompare()) {
+          Path outPath = finalDir.resolve(config.getOut());
+          Path expectedPath = finalDir.resolve(config.getExpected());
+          comparePaths(outPath, expectedPath);
+        }
+
+      })).iterator();
   }
 
   private void checkOutput(Configuration config, String out, String err) {
@@ -162,15 +171,18 @@ public class RelAstProcessorTestBase {
   }
 
   @TestFactory
-  Stream<DynamicTest> testAll() {
+  Stream<DynamicContainer> testAll() {
     File baseDir = new File("src/test/resources/");
 
     Assertions.assertTrue(baseDir.exists());
     Assertions.assertTrue(baseDir.isDirectory());
     File[] files = baseDir.listFiles((FileFilter) FileFilterUtils.directoryFileFilter());
     Assertions.assertNotNull(files);
-    return Arrays.stream(files).map(File::toPath).map(f -> DynamicTest.dynamicTest(f.getFileName().toString(),
-        () -> directoryTest(mainClass, f)));
+    return Arrays.stream(files)
+        // TODO consider also supporting "config.yml"
+        .filter(f -> Objects.requireNonNull(f.listFiles(x -> x.getName().matches("config\\.yaml"))).length == 1)
+        .map(File::toPath)
+        .map(f -> DynamicContainer.dynamicContainer(f.getFileName().toString(), () -> directoryTest(mainClass, f)));
   }
 
 }