diff --git a/.gitignore b/.gitignore
index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..6dbaade2d929c7a425eca520d34b28c2a3a30369 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
 build
 src/gen-res/
 src/gen/
-out/
+out*/
 *.class
diff --git a/build.gradle b/build.gradle
index aff97ea032eb35353c0e1c547483b5fea7c29faa..86a6d5b06a320934adbda772b1d47aa29088e0dd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ apply plugin: "idea"
 
 sourceCompatibility = 1.8
 
-mainClassName = 'org.jastadd.relast.compiler.Compiler'
+mainClassName = 'org.jastadd.relast.compiler.JastAddExtensionCompiler'
 
 repositories {
     jcenter()
@@ -45,7 +45,7 @@ test {
 
 jar {
     manifest {
-        attributes "Main-Class": 'org.jastadd.relast.compiler.Compiler'
+        attributes "Main-Class": 'org.jastadd.relast.compiler.JastAddExtensionCompiler'
     }
 
     from {
diff --git a/src/main/jastadd/RelAst.relast b/src/main/jastadd/RelAst.relast
index b92cb924a986af6d13084792f597650e65329499..3de13a9562aaccff0b4202e79500d251f0f043a1 100644
--- a/src/main/jastadd/RelAst.relast
+++ b/src/main/jastadd/RelAst.relast
@@ -5,7 +5,8 @@ Program ::= GrammarFile *;
 abstract Grammar ::= Declaration*;
 GrammarFile : Grammar ::= <FileName> ;
 
-abstract Declaration;
+abstract Declaration ::= Comment*;
+EmptyDeclaration : Declaration;
 
 TypeDecl:Declaration ::= <Name> <Abstract:boolean>  Component*;
 
@@ -46,4 +47,8 @@ UnnamedRole : Role ;
 rel Role.Type <-> TypeDecl.Role*;
 
 // comments
-Comment : Declaration ::= <Text>;
+abstract Comment ::= <Text>;
+WhitespaceComment : Comment;
+SingleLineComment : Comment;
+MultiLineComment : Comment;
+DocComment : Comment;
diff --git a/src/main/jastadd/backend/AbstractGrammar.jadd b/src/main/jastadd/backend/AbstractGrammar.jadd
index 713efeed9b83ff4d5833323dd00e2e3fddeda50c..da803c5448c6129d4123bc55ad80cc1abedcc6e9 100644
--- a/src/main/jastadd/backend/AbstractGrammar.jadd
+++ b/src/main/jastadd/backend/AbstractGrammar.jadd
@@ -29,10 +29,14 @@ aspect BackendAbstractGrammar {
     }
   }
 
-  abstract public void Declaration.generateAbstractGrammar(StringBuilder b);
+  public void Declaration.generateAbstractGrammar(StringBuilder b) {
+    for (Comment comment : getCommentList()) {
+      comment.generateAbstractGrammar(b);
+    }
+  }
 
-  public void Comment.generateAbstractGrammar(StringBuilder b) {
-    b.append(getText()).append("\n");
+  public void EmptyDeclaration.generateAbstractGrammar(StringBuilder b) {
+    super.generateAbstractGrammar(b);
   }
 
   public void TypeDecl.generateAbstractGrammar(StringBuilder b) {
@@ -48,7 +52,8 @@ aspect BackendAbstractGrammar {
       b.append(" ");
       component.generateAbstractGrammar(b);
     }
-    b.append(";\n");
+    b.append(";");
+    super.generateAbstractGrammar(b);
   }
 
   public abstract void Component.generateAbstractGrammar(StringBuilder b);
@@ -135,14 +140,17 @@ aspect BackendAbstractGrammar {
     b.append(">");
   }
 
-  abstract public void Relation.generateAbstractGrammar(StringBuilder b);
+  public void Relation.generateAbstractGrammar(StringBuilder b) {
+    super.generateAbstractGrammar(b);
+  }
 
   public void DirectedRelation.generateAbstractGrammar(StringBuilder b) {
     b.append("rel ");
     getSource().generateAbstractGrammar(b);
     b.append(" -> ");
     getTarget().generateAbstractGrammar(b);
-    b.append(";\n");
+    b.append(";");
+    super.generateAbstractGrammar(b);
   }
 
   public void BidirectionalRelation.generateAbstractGrammar(StringBuilder b) {
@@ -150,7 +158,8 @@ aspect BackendAbstractGrammar {
     getLeft().generateAbstractGrammar(b);
     b.append(" <-> ");
     getRight().generateAbstractGrammar(b);
-    b.append(";\n");
+    b.append(";");
+    super.generateAbstractGrammar(b);
   }
 
   abstract public void Role.generateAbstractGrammar(StringBuilder b);
@@ -171,5 +180,22 @@ aspect BackendAbstractGrammar {
     b.append(getType().getName());
   }
 
+  abstract public void Comment.generateAbstractGrammar(StringBuilder b);
+
+  public void WhitespaceComment.generateAbstractGrammar(StringBuilder b) {
+    b.append(getText());
+  }
+
+  public void SingleLineComment.generateAbstractGrammar(StringBuilder b) {
+    b.append("//").append(getText()).append("\n");
+  }
+
+  public void MultiLineComment.generateAbstractGrammar(StringBuilder b) {
+    b.append("/*").append(getText()).append("*/");
+  }
+
+  public void DocComment.generateAbstractGrammar(StringBuilder b) {
+    b.append("/**").append(getText()).append("*/");
+  }
 
 }
diff --git a/src/main/jastadd/parser/RelAst.parser b/src/main/jastadd/parser/RelAst.parser
index c05c959f6f5f7729549024daeb2da3ea1d315a88..b483f830759d3e501d2a96d9d3dc07c9eb0074b2 100644
--- a/src/main/jastadd/parser/RelAst.parser
+++ b/src/main/jastadd/parser/RelAst.parser
@@ -1,16 +1,31 @@
 GrammarFile goal
-  = declaration.d goal.p     {: p.getDeclarationList().insertChild(d, 0); return p; :}
-  |                          {: return new GrammarFile(); :}
+  = comment_list.c       {: return new EmptyDeclaration(c); :}
+  | grammar_file
+;
+
+GrammarFile grammar_file
+  = declaration.d grammar_file.f {: f.getDeclarationList().insertChild(d, 0); return f; :}
+  |                              {: return new GrammarFile(); :}
 ;
 
 Declaration declaration
-  = type_decl
-  | relation
-  | comment
+  = type_decl.d comment_list.c {: d.setCommentList(c); return d; :}
+  | relation.r  comment_list.c {: r.setCommentList(c); return r; :}
+;
+
+// 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 comment
-  = COMMENT  {: return new Comment(COMMENT); :};
+  = WHITESPACE.c        {: return new WhitespaceComment(c); :}
+  | MULTILINECOMMENT.c  {: return new MultiLineComment(c.substring(2,c.length()-2));  :}
+  | DOCCOMMENT.c        {: return new DocComment(c.substring(3,c.length()-2));        :}
+  | SINGLELINECOMMENT.c {: return new SingleLineComment(c.substring(2)); :}
+;
 
 TypeDecl type_decl
   = ID components_opt.c SCOL
diff --git a/src/main/jastadd/scanner/Header.flex b/src/main/jastadd/scanner/Header.flex
index 73120659873e1498b07be82eb29d82fe3d1b5112..c343dcd66a49ccac8233775dde7a9282e87e8b87 100644
--- a/src/main/jastadd/scanner/Header.flex
+++ b/src/main/jastadd/scanner/Header.flex
@@ -13,5 +13,8 @@ import org.jastadd.relast.parser.RelAstParser.Terminals;
 %yylexthrow beaver.Scanner.Exception
 %scanerror RelAstScanner.ScannerError
 
+%x COMMENT
+%s DECLARATION
+
 %line
 %column
diff --git a/src/main/jastadd/scanner/Keywords.flex b/src/main/jastadd/scanner/Keywords.flex
index 76e6359ae9967bd16220eb09d5dac4a7ed5389cc..76397b9efcd1c748a3a1eb69675d23dbd4042178 100644
--- a/src/main/jastadd/scanner/Keywords.flex
+++ b/src/main/jastadd/scanner/Keywords.flex
@@ -1,2 +1,4 @@
-"abstract"   { return sym(Terminals.ABSTRACT); }
-"rel"        { return sym(Terminals.RELATION); }
+<YYINITIAL,DECLARATION> {
+  "abstract"            { yybegin(DECLARATION); return sym(Terminals.ABSTRACT); }
+  "rel"                 { yybegin(DECLARATION); return sym(Terminals.RELATION); }
+}
diff --git a/src/main/jastadd/scanner/Macros.flex b/src/main/jastadd/scanner/Macros.flex
index 167f51b882cfce44a1d225503589b76ad38012b9..f67e92684f270e5eb75d87369b2a281781774d53 100644
--- a/src/main/jastadd/scanner/Macros.flex
+++ b/src/main/jastadd/scanner/Macros.flex
@@ -1,5 +1,5 @@
-WhiteSpace = [ ] | \t | \f | \n | \r | \r\n
-ID = [a-zA-Z$_][a-zA-Z0-9$_]*
-TraditionalComment   = [/][*][^*]*[*]+([^*/][^*]*[*]+)*[/]
-EndOfLineComment = "//" [^\n\r]*
-Comment = {TraditionalComment} | {EndOfLineComment}
+WhiteSpace        = [ ] | \t | \f | \n | \r | \r\n
+ID                = [a-zA-Z$_][a-zA-Z0-9$_]*
+MultiLineComment  = [/][*][^*]+[*]+([^*/][^*]*[*]+)*[/]
+DocComment        = [/][*][*][^*]*[*]+([^*/][^*]*[*]+)*[/]
+SingleLineComment = [/][/] [^\n\r]* (\n | \r | \r\n)
diff --git a/src/main/jastadd/scanner/RulesPostamble.flex b/src/main/jastadd/scanner/RulesPostamble.flex
index 1ae39c6244e39c6f2bd2b0c6de41f088b3a51cde..5460a0b87e2002c06430aa5cc88abf0b39f30b9f 100644
--- a/src/main/jastadd/scanner/RulesPostamble.flex
+++ b/src/main/jastadd/scanner/RulesPostamble.flex
@@ -1,4 +1,7 @@
-{ID}         { return sym(Terminals.ID); }
-<<EOF>>      { return sym(Terminals.EOF); }
-
-[^]            { throw new ScannerError((yyline+1) +"," + (yycolumn+1) + ": Illegal character <"+yytext()+">"); }
+<YYINITIAL,DECLARATION> {
+  {ID}                  { yybegin(DECLARATION); return sym(Terminals.ID); }
+  [^]                   { throw new ScannerError((yyline+1) +"," + (yycolumn+1) + ": Illegal character <"+yytext()+">"); }
+}
+<YYINITIAL,DECLARATION,COMMENT> {
+  <<EOF>>               { return sym(Terminals.EOF); }
+}
diff --git a/src/main/jastadd/scanner/RulesPreamble.flex b/src/main/jastadd/scanner/RulesPreamble.flex
index a55876d60989c0f6640ad62e9478cdca48c0573d..63081dec970d3bd18bad2f4b2a387f578b01aabc 100644
--- a/src/main/jastadd/scanner/RulesPreamble.flex
+++ b/src/main/jastadd/scanner/RulesPreamble.flex
@@ -1,5 +1,16 @@
 
 %%
 
-{WhiteSpace} { /* ignore */ }
-{Comment}    { return sym(Terminals.COMMENT); }
+<DECLARATION> {
+  {WhiteSpace}          { /* ignore */ }
+  {MultiLineComment}    { /* ignore */ }
+  {DocComment}          { /* ignore */ }
+  {SingleLineComment}   { /* ignore */ }
+}
+
+<YYINITIAL,COMMENT> {
+  {WhiteSpace}+         { yybegin(YYINITIAL); return sym(Terminals.WHITESPACE); }
+  {MultiLineComment}    { yybegin(YYINITIAL); return sym(Terminals.MULTILINECOMMENT); }
+  {DocComment}          { yybegin(YYINITIAL); return sym(Terminals.DOCCOMMENT); }
+  {SingleLineComment}   { yybegin(YYINITIAL); return sym(Terminals.SINGLELINECOMMENT); }
+}
diff --git a/src/main/jastadd/scanner/Symbols.flex b/src/main/jastadd/scanner/Symbols.flex
index 2990e998f9373f259b0346d2ae3c24bb6a781452..7f05c115b570fd3681617106bb77bc25bb79d7b2 100644
--- a/src/main/jastadd/scanner/Symbols.flex
+++ b/src/main/jastadd/scanner/Symbols.flex
@@ -1,15 +1,17 @@
-";"          { return sym(Terminals.SCOL); }
-":"          { return sym(Terminals.COL); }
-"::="        { return sym(Terminals.ASSIGN); }
-"*"          { return sym(Terminals.STAR); }
-"."          { return sym(Terminals.DOT); }
-","          { return sym(Terminals.COMMA); }
-"<"          { return sym(Terminals.LT); }
-">"          { return sym(Terminals.GT); }
-"["          { return sym(Terminals.LBRACKET); }
-"]"          { return sym(Terminals.RBRACKET); }
-"/"          { return sym(Terminals.SLASH); }
-"?"          { return sym(Terminals.QUESTION_MARK); }
-"->"         { return sym(Terminals.RIGHT); }
-"<-"         { return sym(Terminals.LEFT); }
-"<->"        { return sym(Terminals.BIDIRECTIONAL); }
+<YYINITIAL,DECLARATION> {
+  ";"                   { yybegin(COMMENT);     return sym(Terminals.SCOL); }
+  ":"                   { yybegin(DECLARATION); return sym(Terminals.COL); }
+  "::="                 { yybegin(DECLARATION); return sym(Terminals.ASSIGN); }
+  "*"                   { yybegin(DECLARATION); return sym(Terminals.STAR); }
+  "."                   { yybegin(DECLARATION); return sym(Terminals.DOT); }
+  ","                   { yybegin(DECLARATION); return sym(Terminals.COMMA); }
+  "<"                   { yybegin(DECLARATION); return sym(Terminals.LT); }
+  ">"                   { yybegin(DECLARATION); return sym(Terminals.GT); }
+  "["                   { yybegin(DECLARATION); return sym(Terminals.LBRACKET); }
+  "]"                   { yybegin(DECLARATION); return sym(Terminals.RBRACKET); }
+  "/"                   { yybegin(DECLARATION); return sym(Terminals.SLASH); }
+  "?"                   { yybegin(DECLARATION); return sym(Terminals.QUESTION_MARK); }
+  "->"                  { yybegin(DECLARATION); return sym(Terminals.RIGHT); }
+  "<-"                  { yybegin(DECLARATION); return sym(Terminals.LEFT); }
+  "<->"                 { yybegin(DECLARATION); return sym(Terminals.BIDIRECTIONAL); }
+}
diff --git a/src/main/java/org/jastadd/JastAddConfiguration.java b/src/main/java/org/jastadd/JastAddConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..30774005356c0eb30354b7fb76e1bafe67d00873
--- /dev/null
+++ b/src/main/java/org/jastadd/JastAddConfiguration.java
@@ -0,0 +1,177 @@
+/* Copyright (c) 2013-2015, Jesper Öqvist <jesper.oqvist@cs.lth.se>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright notice,
+ *       this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the Lund University nor the names of its
+ *       contributors may be used to endorse or promote products derived from
+ *       this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jastadd;
+
+import org.jastadd.option.ArgumentParser;
+import org.jastadd.option.Option;
+
+import java.io.PrintStream;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Tracks JastAdd configuration options.
+ *
+ * @author Jesper Öqvist <jesper.oqvist@cs.lth.se>
+ */
+public class JastAddConfiguration extends org.jastadd.Configuration {
+
+  /**
+   * Indicates if there were unknown command-line options
+   */
+  final boolean unknownOptions;
+
+  private boolean isJastAddCompliant;
+
+  public boolean isJastAddCompliant() {
+    return isJastAddCompliant;
+  }
+
+  /**
+   * Parse options from an argument list.
+   *
+   * @param args Command-line arguments to build configuration from
+   * @param err  output stream to print configuration warnings to
+   */
+  public JastAddConfiguration(String[] args, PrintStream err, boolean isJastAddCompliant, Collection<Option<?>> extraOptions) {
+    ArgumentParser argParser = new ArgumentParser();
+    this.isJastAddCompliant = isJastAddCompliant;
+    if (isJastAddCompliant) {
+      Collection<Option<?>> jastAddOptions = allJastAddOptions();
+      argParser.addOptions(jastAddOptions);
+
+      // if the JastAdd options are supported, we have to check for duplicates!
+      Set<String> jastAddOptionNames = jastAddOptions.stream().map(o -> o.name()).collect(Collectors.toSet());
+      for (Option option : extraOptions) {
+        if (jastAddOptionNames.contains(option.name())) {
+          System.err.println("Unable to add option '" + option.name() + "', because there is a JastAdd option with the same name.");
+        } else {
+          argParser.addOption(option);
+        }
+      }
+    } else {
+      argParser.addOptions(extraOptions);
+    }
+
+    unknownOptions = !argParser.parseArgs(args, err);
+    filenames = argParser.getFilenames();
+  }
+
+  /**
+   * @return all files
+   */
+  @Override
+  public Collection<String> getFiles() {
+    return Collections.unmodifiableCollection(filenames);
+  }
+
+  private Collection<Option<?>> allJastAddOptions() {
+    Collection<Option<?>> allOptions = new LinkedList<>();
+    allOptions.add(ASTNodeOption);
+    allOptions.add(ListOption);
+    allOptions.add(OptOption);
+    allOptions.add(jjtreeOption);
+    allOptions.add(grammarOption);
+    allOptions.add(generateAnnotations);
+    allOptions.add(defaultMapOption);
+    allOptions.add(defaultSetOption);
+    allOptions.add(lazyMapsOption);
+    allOptions.add(privateOption);
+    allOptions.add(rewriteOption);
+    allOptions.add(beaverOption);
+    allOptions.add(lineColumnNumbersOption);
+    allOptions.add(visitCheckOption);
+    allOptions.add(traceVisitCheckOption);
+    allOptions.add(cacheCycleOption);
+    allOptions.add(componentCheckOption);
+    allOptions.add(inhEqCheckOption);
+    allOptions.add(suppressWarningsOption);
+    allOptions.add(refineLegacyOption);
+    allOptions.add(licenseOption);
+    allOptions.add(debugOption);
+    allOptions.add(outputDirOption);
+    allOptions.add(staticStateOption);
+    allOptions.add(tracingOption);
+    allOptions.add(flushOption);
+    allOptions.add(packageNameOption);
+    allOptions.add(versionOption);
+    allOptions.add(helpOption);
+    allOptions.add(printNonStandardOptionsOption);
+    allOptions.add(indentOption);
+    allOptions.add(minListSizeOption);
+    allOptions.add(cacheOption);
+    allOptions.add(incrementalOption);
+
+    // New since 2.1.11.
+    allOptions.add(dotOption);
+    allOptions.add(ASTNodeSuperOption);
+    allOptions.add(generateImplicitsOption);
+
+    // New since 2.1.12.
+    allOptions.add(stateClassNameOption);
+
+    // New since 2.2.1:
+    allOptions.add(safeLazyOption);
+
+    // New since 2.2.3:
+    allOptions.add(statisticsOption);
+
+    // New since 2.2.4:
+    allOptions.add(emptyContainerSingletons);
+    allOptions.add(concurrentOption);
+    allOptions.add(numThreadsOption);
+    allOptions.add(concurrentMap);
+
+    // 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/AbstractCompiler.java b/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java
new file mode 100644
index 0000000000000000000000000000000000000000..1330ebe284b62a90480959eb35eb23dbda831540
--- /dev/null
+++ b/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java
@@ -0,0 +1,63 @@
+package org.jastadd.relast.compiler;
+
+import org.jastadd.JastAddConfiguration;
+import org.jastadd.option.ArgumentParser;
+import org.jastadd.option.Option;
+
+import java.util.ArrayList;
+
+public abstract class AbstractCompiler {
+
+  private final boolean jastAddCompliant;
+  protected ArrayList<Option<?>> options;
+  private String name;
+  private ArgumentParser commandLine;
+
+  private JastAddConfiguration configuration;
+
+  public AbstractCompiler(String name, boolean jastaddCompliant) {
+    this.name = name;
+    this.jastAddCompliant = jastaddCompliant;
+  }
+
+  public JastAddConfiguration getConfiguration() throws CompilerException {
+    if (configuration == null) {
+      throw new CompilerException("Configuration only supported for JastAdd-compliant compilers!");
+    }
+    return configuration;
+  }
+
+  public int run(String[] args) throws CompilerException {
+
+    options = new ArrayList<>();
+    initOptions();
+    configuration = new JastAddConfiguration(args, System.err, jastAddCompliant, options);
+
+    return compile();
+  }
+
+  abstract int compile() throws CompilerException;
+
+  protected void initOptions() {
+    // there are no options by default
+  }
+
+  protected <OptionType extends Option<?>> OptionType addOption(OptionType option) {
+    options.add(option);
+    return option;
+  }
+
+  protected int error(String message) {
+    System.err.println("Error: " + message);
+    System.err.println();
+    System.err.println("Usage: java -jar " + name + ".jar [--option1] [--option2=value] ...  <filename1> <filename2> ... ");
+    System.err.println("Options:");
+    commandLine.printHelp(System.err);
+    return 1;
+  }
+
+  public String getName() {
+    return name;
+  }
+}
+
diff --git a/src/main/java/org/jastadd/relast/compiler/Compiler.java b/src/main/java/org/jastadd/relast/compiler/Compiler.java
deleted file mode 100644
index 4f82148370b1ca1c49799d9a93cbce5151945d51..0000000000000000000000000000000000000000
--- a/src/main/java/org/jastadd/relast/compiler/Compiler.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package org.jastadd.relast.compiler;
-
-import beaver.Parser;
-import org.jastadd.option.ArgumentParser;
-import org.jastadd.option.Option;
-import org.jastadd.option.ValueOption;
-import org.jastadd.relast.ast.GrammarFile;
-import org.jastadd.relast.ast.Program;
-import org.jastadd.relast.parser.RelAstParser;
-import org.jastadd.relast.scanner.RelAstScanner;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-
-public class Compiler {
-
-  private ValueOption optionOutputDir;
-  private ValueOption optionInputDir;
-
-  private ArrayList<Option<?>> options;
-  private ArgumentParser commandLine;
-
-  public Compiler() {
-    options = new ArrayList<>();
-    addOptions();
-  }
-
-  public static void main(String[] args) {
-    try {
-      new Compiler().run(args);
-    } catch (CompilerException e) {
-      System.err.println(e.getMessage());
-      System.exit(-1);
-    }
-  }
-
-  public int run(String[] args) throws CompilerException {
-    options = new ArrayList<>();
-    addOptions();
-    commandLine = new ArgumentParser();
-    commandLine.addOptions(options);
-    boolean unknownOptions = !commandLine.parseArgs(args, System.err);
-
-    Path inputPath;
-    if (optionInputDir.isMatched()) {
-      inputPath = Paths.get(optionInputDir.value());
-    } else {
-      inputPath = Paths.get(".");
-      printMessage("No input dir is set. Assuming current directory '" + inputPath.toAbsolutePath().toString() + "'.");
-    }
-
-    if (!inputPath.toFile().exists()) {
-      printMessage("Input path '" + inputPath.toAbsolutePath().toString() + "' does not exist. Exiting...");
-      System.exit(-1);
-    } else if (!inputPath.toFile().isDirectory()) {
-      printMessage("Input path '" + inputPath.toAbsolutePath().toString() + "' is not a directory. Exiting...");
-      System.exit(-1);
-    }
-
-    Path outputPath; // should not be used, but otherwise there is a compiler warning
-    if (optionOutputDir.isMatched()) {
-      outputPath = Paths.get(optionOutputDir.value());
-    } else {
-      outputPath = Paths.get("./gen/");
-      printMessage("No output dir is set. Assuming '" + outputPath.toAbsolutePath().toString() + "'.");
-    }
-
-    if (outputPath.toFile().exists() && !outputPath.toFile().isDirectory()) {
-      printMessage("Output path '" + inputPath.toAbsolutePath().toString() + "' exists, but is not a directory. Exiting...");
-    }
-
-    printMessage("Running RelAST Preprocessor");
-
-    if (unknownOptions) {
-      printMessage("Some options were unsupported!");
-    }
-
-    Program program = parseProgram(inputPath);
-
-    printMessage("Writing output files");
-
-    for (GrammarFile grammarFile : program.getGrammarFileList()) {
-      // TODO decide and document what the file name should be, the full path or a simple name?
-      writeToFile(outputPath + grammarFile.getFileName() + "/Grammar.relast", grammarFile.generateAbstractGrammar());
-    }
-
-    writeToFile(outputPath + "/Grammar.relast", program.generateAbstractGrammar());
-    return 0;
-  }
-
-  private void printMessage(String message) {
-    System.out.println(message);
-  }
-
-  private void writeToFile(String filename, String str) throws CompilerException {
-    try {
-      PrintWriter writer = new PrintWriter(filename);
-      writer.print(str);
-      writer.close();
-    } catch (Exception e) {
-      throw new CompilerException("Could not write to file " + filename, e);
-    }
-  }
-
-  private void addOptions() {
-    optionOutputDir = addOption(new ValueOption("outputDir", "target directory for the generated files."));
-    optionInputDir = addOption(new ValueOption("inputDir", "input directory."));
-  }
-
-  private <OptionType extends Option<?>> OptionType addOption(OptionType option) {
-    options.add(option);
-    return option;
-  }
-
-  private Program parseProgram(Path inputPath) throws CompilerException {
-    Program program = new Program();
-
-    try (DirectoryStream<Path> stream = Files.newDirectoryStream(inputPath, "*.relast")) {
-      RelAstParser parser = new RelAstParser();
-      stream.forEach(path -> {
-        try (BufferedReader reader = Files.newBufferedReader(path)) {
-          RelAstScanner scanner = new RelAstScanner(reader);
-          GrammarFile inputGrammar = (GrammarFile) parser.parse(scanner);
-          inputGrammar.dumpTree(System.out);
-          program.addGrammarFile(inputGrammar);
-          inputGrammar.treeResolveAll();
-        } catch (IOException | Parser.Exception e) {
-          printMessage("Could not parse grammar file " + path);
-          e.printStackTrace();
-        }
-      });
-    } catch (IOException e) {
-      printMessage("Unable to iterate over input path '" + inputPath.toAbsolutePath().toString() + "'. Exiting...");
-      e.printStackTrace();
-      System.exit(-1);
-    }
-
-
-    return program;
-  }
-
-  protected int error(String message) {
-    System.err.println("Error: " + message);
-    System.err.println();
-    System.err.println("Usage: java -jar relast.jar [--option1] [--option2=value] ...  <filename1> <filename2> ... ");
-    System.err.println("Options:");
-    commandLine.printHelp(System.err);
-    return 1;
-  }
-
-  public static class CompilerException extends Exception {
-    CompilerException(String message, Throwable cause) {
-      super(message, cause);
-    }
-  }
-}
-
diff --git a/src/main/java/org/jastadd/relast/compiler/CompilerException.java b/src/main/java/org/jastadd/relast/compiler/CompilerException.java
new file mode 100644
index 0000000000000000000000000000000000000000..6949c308da5f431c8bf4a6a1e32d3722f1a9d744
--- /dev/null
+++ b/src/main/java/org/jastadd/relast/compiler/CompilerException.java
@@ -0,0 +1,11 @@
+package org.jastadd.relast.compiler;
+
+public class CompilerException extends Exception {
+  public CompilerException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public CompilerException(String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d6949f5ff7ec74d2e1963e564bff9d633d4fbec
--- /dev/null
+++ b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java
@@ -0,0 +1,144 @@
+package org.jastadd.relast.compiler;
+
+import beaver.Parser;
+import org.jastadd.option.ValueOption;
+import org.jastadd.relast.ast.GrammarFile;
+import org.jastadd.relast.ast.Program;
+import org.jastadd.relast.parser.RelAstParser;
+import org.jastadd.relast.scanner.RelAstScanner;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Optional;
+
+public class RelastSourceToSourceCompiler extends AbstractCompiler {
+
+
+  protected ValueOption optionOutputBaseDir;
+  protected ValueOption optionInputBaseDir;
+
+  public RelastSourceToSourceCompiler(String name, boolean jastAddCompliant) {
+    super(name, jastAddCompliant);
+  }
+
+  public static void main(String[] args) {
+    try {
+      new RelastSourceToSourceCompiler("relast-preprocessor", true).run(args);
+    } catch (CompilerException e) {
+      System.err.println(e.getMessage());
+      System.exit(-1);
+    }
+  }
+
+  protected static boolean isGrammarFile(String fileName) {
+    String extension = fileName.subSequence(fileName.lastIndexOf("."), fileName.length()).toString();
+    return extension.equals(".relast") || extension.equals(".ast");
+  }
+
+  @Override
+  protected void initOptions() {
+    optionOutputBaseDir = addOption(new ValueOption("outputBaseDir", "base directory for generated files"));
+    optionInputBaseDir = addOption(new ValueOption("inputBaseDir", "base directory for input files"));
+  }
+
+  @Override
+  protected int compile() throws CompilerException {
+    final Path inputBasePath;
+    if (optionInputBaseDir.isMatched()) {
+      inputBasePath = Paths.get(optionInputBaseDir.value()).toAbsolutePath();
+    } else {
+      inputBasePath = Paths.get(".").toAbsolutePath();
+      printMessage("No input base dir is set. Assuming current directory '" + inputBasePath.toAbsolutePath().toString() + "'.");
+    }
+
+    if (!inputBasePath.toFile().exists()) {
+      printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' does not exist. Exiting...");
+      System.exit(-1);
+    } else if (!inputBasePath.toFile().isDirectory()) {
+      printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' is not a directory. Exiting...");
+      System.exit(-1);
+    }
+
+    final Path outputBasePath;
+    if (optionOutputBaseDir.isMatched()) {
+      outputBasePath = Paths.get(optionOutputBaseDir.value()).toAbsolutePath();
+    } else {
+      throw new CompilerException("No output base dir is set.");
+    }
+
+    if (outputBasePath.toFile().exists() && !outputBasePath.toFile().isDirectory()) {
+      printMessage("Output path '" + inputBasePath.toAbsolutePath().toString() + "' exists, but is not a directory. Exiting...");
+    }
+
+    printMessage("Running RelAST Preprocessor");
+
+    // gather all files
+    Collection<Path> inputFiles = new ArrayList<>();
+    getConfiguration().getFiles().forEach(name -> relativizeFileName(inputBasePath, Paths.get(name)).ifPresent(path -> inputFiles.add(path)));
+
+
+    Program program = parseProgram(inputFiles);
+
+    printMessage("Writing output files");
+
+    for (GrammarFile grammarFile : program.getGrammarFileList()) {
+      // 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());
+    }
+    return 0;
+  }
+
+  private Optional<Path> relativizeFileName(Path inputBasePath, Path filePath) {
+    if (filePath.isAbsolute()) {
+      if (filePath.startsWith(inputBasePath)) {
+        return Optional.of(filePath.relativize(inputBasePath));
+      } else {
+        printMessage("Path '" + filePath + "' is not contained in the base path '" + inputBasePath + "'.");
+        return Optional.empty();
+      }
+    } else {
+      return Optional.of(inputBasePath.resolve(filePath));
+    }
+  }
+
+  private void printMessage(String message) {
+    System.out.println(message);
+  }
+
+  private void writeToFile(Path path, String str) throws CompilerException {
+    try (PrintWriter writer = new PrintWriter(path.toFile())) {
+      writer.print(str);
+    } catch (Exception e) {
+      throw new CompilerException("Could not write to file " + path, e);
+    }
+  }
+
+  private Program parseProgram(Collection<Path> inputFiles) throws CompilerException {
+    Program program = new Program();
+
+    RelAstParser parser = new RelAstParser();
+    inputFiles.stream().filter(path -> isGrammarFile(path.toString())).forEach(
+        path -> {
+          try (BufferedReader reader = Files.newBufferedReader(path)) {
+            RelAstScanner scanner = new RelAstScanner(reader);
+            GrammarFile inputGrammar = (GrammarFile) parser.parse(scanner);
+            inputGrammar.setFileName(path.toString());
+            program.addGrammarFile(inputGrammar);
+            inputGrammar.treeResolveAll();
+          } catch (IOException | Parser.Exception e) {
+            printMessage("Could not parse grammar file " + path);
+            e.printStackTrace();
+          }
+        }
+    );
+
+    return program;
+}
+}
+
diff --git a/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java b/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java
index becda3fbdfb3152aa380b942a9cd1e76e32e4cc9..c87e51f7dbc70644b621998548d4f942160e0ea9 100644
--- a/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java
+++ b/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java
@@ -1,6 +1,7 @@
 package org.jastadd.ros2rag.tests;
 
-import org.jastadd.relast.compiler.Compiler;
+import org.jastadd.relast.compiler.CompilerException;
+import org.jastadd.relast.compiler.RelastSourceToSourceCompiler;
 import org.junit.jupiter.api.Test;
 
 import java.io.File;
@@ -11,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class RelAstTest {
 
-  void transform(String inputDir, String outputDir) throws Compiler.CompilerException {
+  void transform(boolean jastAddCompliant, String inputDir, String outputDir) throws CompilerException {
 
     System.out.println("Running test in directory '" + Paths.get(".").toAbsolutePath() + "'.");
     assertTrue(Paths.get(inputDir).toFile().exists(), "input directory does not exist");
@@ -28,15 +29,17 @@ public class RelAstTest {
     }
 
     String[] args = {
-        "--outputDir=" + outputDir,
-        "--inputDir=" + inputDir
+        "--outputBaseDir=" + outputDir,
+        "--inputBaseDir=" + inputDir,
+        "Example.relast"
     };
 
-    new Compiler().run(args);
+    new RelastSourceToSourceCompiler("testCompiler", jastAddCompliant).run(args);
   }
 
   @Test
-  void transformMinimalExample() throws Compiler.CompilerException {
-    transform("src/test/resources/in", "src/test/resources/out");
+  void transformMinimalExample() throws CompilerException {
+    transform(false,"src/test/resources/in", "src/test/resources/out-simple");
+    transform(true,"src/test/resources/in", "src/test/resources/out-compliant");
   }
 }