From ae54ec88cef57f9584eeccade8fe18c7604ddf8c Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Fri, 26 Aug 2022 09:49:50 +0200
Subject: [PATCH] API + some enhancements

- added programmable interface (Grammar2UmlProcessor)
- add types to tokens
- API: add styling for TypeDecl
- API: add option to inline superclass
---
 grammar2uml/build.gradle                      |   7 +
 grammar2uml/src/main/jastadd/Grammar2Uml.jadd |   5 +
 .../src/main/jastadd/Grammar2Uml.relast       |   4 +-
 .../src/main/jastadd/MustacheNodes.relast     |  16 +-
 .../jastadd/backend/AspectGeneration.jrag     |  50 +++++
 .../src/main/jastadd/backend/Generation.jadd  | 102 +++--------
 .../grammar2uml/compiler/Compiler.java        | 133 ++------------
 .../compiler/Grammar2UmlProcessor.java        | 171 ++++++++++++++++++
 .../{Grammar2UmlMain.java => SimpleMain.java} |  28 ++-
 .../src/main/resources/Containment.mustache   |   2 +-
 .../src/main/resources/Inheritance.mustache   |   1 -
 .../src/main/resources/Relation.mustache      |   2 +-
 .../src/main/resources/TypeDecl.mustache      |  11 +-
 .../src/main/resources/grammar2uml.mustache   |   1 -
 14 files changed, 323 insertions(+), 210 deletions(-)
 create mode 100644 grammar2uml/src/main/jastadd/Grammar2Uml.jadd
 create mode 100644 grammar2uml/src/main/jastadd/backend/AspectGeneration.jrag
 create mode 100644 grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Grammar2UmlProcessor.java
 rename grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/{Grammar2UmlMain.java => SimpleMain.java} (66%)
 delete mode 100644 grammar2uml/src/main/resources/Inheritance.mustache

diff --git a/grammar2uml/build.gradle b/grammar2uml/build.gradle
index 63f1b71..288271b 100644
--- a/grammar2uml/build.gradle
+++ b/grammar2uml/build.gradle
@@ -174,6 +174,13 @@ ext {
 
 application.mainClassName = "${mainClassName}"
 
+task simpleRun(type: JavaExec) {
+    group 'application'
+    classpath sourceSets.main.runtimeClasspath
+    main = "de.tudresden.inf.st.jastadd.grammar2uml.compiler.SimpleMain"
+
+}
+
 jar {
     manifest.attributes "Main-Class": "${mainClassName}"
 }
diff --git a/grammar2uml/src/main/jastadd/Grammar2Uml.jadd b/grammar2uml/src/main/jastadd/Grammar2Uml.jadd
new file mode 100644
index 0000000..8e76c85
--- /dev/null
+++ b/grammar2uml/src/main/jastadd/Grammar2Uml.jadd
@@ -0,0 +1,5 @@
+aspect GrammarTypes {
+  public interface StyleDefinition extends java.util.function.BiConsumer<TypeDecl, Style> {
+
+  }
+}
diff --git a/grammar2uml/src/main/jastadd/Grammar2Uml.relast b/grammar2uml/src/main/jastadd/Grammar2Uml.relast
index 9eab123..1541b08 100644
--- a/grammar2uml/src/main/jastadd/Grammar2Uml.relast
+++ b/grammar2uml/src/main/jastadd/Grammar2Uml.relast
@@ -1,4 +1,6 @@
-Grammar2Uml ::= Program <FileName> Folder* ;
+Grammar2Uml ::= Program <FileName> Folder* <StyleDefinition:StyleDefinition> ;
 
 Folder ::= <Name:String> ;
 rel Folder.Type* <-> TypeDecl.SourceFolder?;
+
+Style ::= <BackgroundColor> <InlineAsSuperType:boolean> ;
diff --git a/grammar2uml/src/main/jastadd/MustacheNodes.relast b/grammar2uml/src/main/jastadd/MustacheNodes.relast
index d4b684c..faf2ef8 100644
--- a/grammar2uml/src/main/jastadd/MustacheNodes.relast
+++ b/grammar2uml/src/main/jastadd/MustacheNodes.relast
@@ -1,21 +1,19 @@
-MGrammar2Uml ::= Folder:MFolder* OtherType:MTypeDecl* Containment:MContainment* Relation:MRelation* Inheritance:MInheritance* ;
+MGrammar2Uml ::= Folder:MFolder* OtherType:MTypeDecl* Containment:MContainment* Relation:MRelation* ;
 MFolder ::= InnerTypeDecl:MTypeDecl*;
-MTypeDecl ::= InnerTokenComponent:MTokenComponent*;
+MTypeDecl ::= InnerTokenComponent:MTokenComponent* <Name> Style;
 MTokenComponent;
 abstract MContainment ::= <Label:String> ;
 MSingleContainment : MContainment;
 MOptContainment : MContainment;
 MListContainment : MContainment;
 MRelation ::= <Label> <LeftModifier> <RightModifier> <Bidirectional:boolean>;
-MInheritance ;
 
 rel MGrammar2Uml.Grammar2Uml -> Grammar2Uml;
 rel MFolder.Folder -> Folder;
 rel MTypeDecl.Type -> TypeDecl;
 rel MTokenComponent.Token -> TokenComponent;
-rel MContainment.Type -> TypeDecl;
-rel MContainment.Component -> TypeDecl;
-rel MRelation.Left -> TypeDecl;
-rel MRelation.Right -> TypeDecl;
-rel MInheritance.SuperClass -> TypeDecl;
-rel MInheritance.SubClass -> TypeDecl;
+rel MContainment.Type -> MTypeDecl;
+rel MContainment.Component -> MTypeDecl;
+rel MRelation.Left -> MTypeDecl;
+rel MRelation.Right -> MTypeDecl;
+rel MTypeDecl.SuperClass? -> MTypeDecl;
diff --git a/grammar2uml/src/main/jastadd/backend/AspectGeneration.jrag b/grammar2uml/src/main/jastadd/backend/AspectGeneration.jrag
new file mode 100644
index 0000000..2faa596
--- /dev/null
+++ b/grammar2uml/src/main/jastadd/backend/AspectGeneration.jrag
@@ -0,0 +1,50 @@
+aspect AspectGeneration {
+  syn String Grammar2Uml.generateAspect() = toMustache().generateAspect();
+
+  syn String MGrammar2Uml.generateAspect() {
+    StringBuilder sb = new StringBuilder();
+    com.github.mustachejava.reflect.ReflectionObjectHandler roh = new com.github.mustachejava.reflect.ReflectionObjectHandler() {
+      @Override
+      public com.github.mustachejava.Binding createBinding(String name, final com.github.mustachejava.TemplateContext tc, com.github.mustachejava.Code code) {
+        return new com.github.mustachejava.reflect.GuardedBinding(this, name, tc, code) {
+          @Override
+          protected synchronized com.github.mustachejava.util.Wrapper getWrapper(String name, java.util.List<Object> scopes) {
+            com.github.mustachejava.util.Wrapper wrapper = super.getWrapper(name, scopes);
+            if (wrapper instanceof com.github.mustachejava.reflect.MissingWrapper) {
+              throw new com.github.mustachejava.MustacheException(name + " not found in " + tc);
+            }
+            return wrapper;
+          }
+        };
+      }
+    };
+    com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory();
+//    mf.setObjectHandler(roh);
+    com.github.mustachejava.Mustache m = mf.compile("grammar2uml.mustache");
+    m.execute(new java.io.PrintWriter(new AppendableWriter(sb)), this);
+    return sb.toString();
+  }
+  public class AppendableWriter extends java.io.Writer {
+    private final StringBuilder sb;
+
+    public AppendableWriter(StringBuilder sb) {
+      this.sb = sb;
+    }
+
+    @Override
+    public void write(char[] chars, int off, int len) {
+      sb.append(chars, off, len);
+    }
+
+    @Override
+    public void write(String str) {
+      sb.append(str);
+    }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public void close() {}
+  }
+}
diff --git a/grammar2uml/src/main/jastadd/backend/Generation.jadd b/grammar2uml/src/main/jastadd/backend/Generation.jadd
index ed5a3bb..fca8e4a 100644
--- a/grammar2uml/src/main/jastadd/backend/Generation.jadd
+++ b/grammar2uml/src/main/jastadd/backend/Generation.jadd
@@ -26,34 +26,32 @@ aspect AttributesForMustache {
   eq MTypeDecl.getInnerTokenComponent(int i).isLast() = i == getNumInnerTokenComponent() - 1;
 
   syn boolean MTypeDecl.isAbstract() = getType().getAbstract();
-  syn String MTypeDecl.name() = getType().getName();
+
+  syn String MTypeDecl.backgroundColor() = getStyle().getBackgroundColor();
 
   // --- MTokenComponent ---
   syn String MTokenComponent.name() = getToken().getName();
+  syn String MTokenComponent.type() = getToken().hasJavaTypeUse() ? getToken().getJavaTypeUse().getName() : "String";
   inh boolean MTokenComponent.isFirst();
   inh boolean MTokenComponent.isLast();
 
   // --- MContainment ---
-  syn String MContainment.typeName() = getType().getName();
-  syn String MContainment.componentName() = getComponent().getName();
+  syn String MContainment.typeName() = "\"" + getType().getName() + "\"";
+  syn String MContainment.componentName() = "\"" + getComponent().getName() + "\"";
   syn String MContainment.modifier();
   eq MSingleContainment.modifier() = "\"1\"";
   eq MOptContainment.modifier() = "\"0 .. 1\"";
   eq MListContainment.modifier() = "\"*\"";
 
   // --- MRelation ---
-  syn String MRelation.leftName() = getLeft().getName();
-  syn String MRelation.rightName() = getRight().getName();
+  syn String MRelation.leftName() = "\"" + getLeft().getName() + "\"";
+  syn String MRelation.rightName() = "\"" + getRight().getName() + "\"";
   syn boolean MRelation.isBidirectional() = getBidirectional();
 //  syn String MRelation.modifier();
 //  eq MSingleRelation.modifier() = "\"1\"";
 //  eq MOptRelation.modifier() = "\"0 .. 1\"";
 //  eq MListRelation.modifier() = "\"*\"";
 
-  // --- MInheritance ---
-  syn String MInheritance.superClassName() = getSuperClass().getName();
-  syn String MInheritance.subClassName() = getSubClass().getName();
-
   // --- toMContainment ---
   syn MContainment TypeComponent.toMContainment();
   eq NormalComponent.toMContainment() = new MSingleContainment();
@@ -85,6 +83,10 @@ aspect AttributesForMustache {
     throw new RuntimeException("UnnamedRole cannot be converted to MRelation");
   }
 
+  private Style TypeDecl.createDefaultStyle() {
+    return new Style().setBackgroundColor("white");
+  }
+
   // --- toMustache ---
   syn lazy MGrammar2Uml Grammar2Uml.toMustache() {
     MGrammar2Uml result = new MGrammar2Uml();
@@ -100,34 +102,28 @@ aspect AttributesForMustache {
         if (component.isTypeComponent()) {
           TypeComponent typeComponent = component.asTypeComponent();
           MContainment containment = typeComponent.toMContainment();
-          containment.setType(typeDecl);
-          containment.setComponent(component.asTypeComponent().getTypeDecl());
+          containment.setType(typeDecl.toMustache());
+          containment.setComponent(component.asTypeComponent().getTypeDecl().toMustache());
           if (!component.getName().isEmpty() && !component.getName().equals(component.asTypeComponent().getTypeDecl().getName())) {
             containment.setLabel(component.getName());
           }
           result.addContainment(containment);
         }
       }
-      if (typeDecl.hasSuperType()) {
-        MInheritance inheritance = new MInheritance();
-        inheritance.setSuperClass(typeDecl.getSuperType());
-        inheritance.setSubClass(typeDecl);
-        result.addInheritance(inheritance);
-      }
     }
     for (Relation relation : getProgram().relations()) {
       if (relation.isDirectedRelation()) {
         DirectedRelation directedRelation = relation.asDirectedRelation();
         MRelation mRelation = directedRelation.toMRelation();
-        mRelation.setLeft(directedRelation.getSource().getType());
-        mRelation.setRight(directedRelation.getTarget().getType());
+        mRelation.setLeft(directedRelation.getSource().getType().toMustache());
+        mRelation.setRight(directedRelation.getTarget().getType().toMustache());
         mRelation.setLabel(directedRelation.getSource().getName());
         result.addRelation(mRelation);
       } else {
         BidirectionalRelation bidiRelation = relation.asBidirectionalRelation();
         MRelation mRelation = bidiRelation.toMRelation();
-        mRelation.setLeft(bidiRelation.getLeft().getType());
-        mRelation.setRight(bidiRelation.getRight().getType());
+        mRelation.setLeft(bidiRelation.getLeft().getType().toMustache());
+        mRelation.setRight(bidiRelation.getRight().getType().toMustache());
 //        mRelation.setLabel(bidiRelation.getSource().getName());
         result.addRelation(mRelation);
       }
@@ -152,6 +148,19 @@ aspect AttributesForMustache {
         result.addInnerTokenComponent(component.asTokenComponent().toMustache());
       }
     }
+    result.setName(this.getName());
+
+    Style style = createDefaultStyle();
+    grammar2uml().getStyleDefinition().accept(this, style);
+    result.setStyle(style);
+    if (this.hasSuperType()) {
+      Style parentStyle = this.getSuperType().toMustache().getStyle();
+      if (parentStyle.getInlineAsSuperType()) {
+        result.setName(getName() + ":" + this.getSuperType().getName());
+      } else {
+        result.setSuperClass(this.getSuperType().toMustache());
+      }
+    }
     return result;
   }
 
@@ -162,54 +171,3 @@ aspect AttributesForMustache {
   }
 
 }
-
-aspect AspectGeneration {
-  syn String Grammar2Uml.generateAspect() = toMustache().generateAspect();
-
-  syn String MGrammar2Uml.generateAspect() {
-    StringBuilder sb = new StringBuilder();
-    com.github.mustachejava.reflect.ReflectionObjectHandler roh = new com.github.mustachejava.reflect.ReflectionObjectHandler() {
-      @Override
-      public com.github.mustachejava.Binding createBinding(String name, final com.github.mustachejava.TemplateContext tc, com.github.mustachejava.Code code) {
-        return new com.github.mustachejava.reflect.GuardedBinding(this, name, tc, code) {
-          @Override
-          protected synchronized com.github.mustachejava.util.Wrapper getWrapper(String name, java.util.List<Object> scopes) {
-            com.github.mustachejava.util.Wrapper wrapper = super.getWrapper(name, scopes);
-            if (wrapper instanceof com.github.mustachejava.reflect.MissingWrapper) {
-              throw new com.github.mustachejava.MustacheException(name + " not found in " + tc);
-            }
-            return wrapper;
-          }
-        };
-      }
-    };
-    com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory();
-//    mf.setObjectHandler(roh);
-    com.github.mustachejava.Mustache m = mf.compile("grammar2uml.mustache");
-    m.execute(new java.io.PrintWriter(new AppendableWriter(sb)), this);
-    return sb.toString();
-  }
-  public class AppendableWriter extends java.io.Writer {
-    private final StringBuilder sb;
-
-    public AppendableWriter(StringBuilder sb) {
-      this.sb = sb;
-    }
-
-    @Override
-    public void write(char[] chars, int off, int len) {
-      sb.append(chars, off, len);
-    }
-
-    @Override
-    public void write(String str) {
-      sb.append(str);
-    }
-
-    @Override
-    public void flush() {}
-
-    @Override
-    public void close() {}
-  }
-}
diff --git a/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Compiler.java b/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Compiler.java
index 1b6bd40..41116fa 100644
--- a/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Compiler.java
+++ b/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Compiler.java
@@ -1,16 +1,9 @@
 package de.tudresden.inf.st.jastadd.grammar2uml.compiler;
 
 import beaver.Parser;
-import de.tudresden.inf.st.jastadd.grammar2uml.ast.ErrorMessage;
 import de.tudresden.inf.st.jastadd.grammar2uml.ast.Grammar2Uml;
-import de.tudresden.inf.st.jastadd.grammar2uml.ast.GrammarFile;
-import de.tudresden.inf.st.jastadd.grammar2uml.ast.Program;
 import de.tudresden.inf.st.jastadd.grammar2uml.parser.Grammar2UmlParser;
 import de.tudresden.inf.st.jastadd.grammar2uml.scanner.Grammar2UmlScanner;
-import net.sourceforge.plantuml.FileFormat;
-import net.sourceforge.plantuml.FileFormatOption;
-import net.sourceforge.plantuml.FileUtils;
-import net.sourceforge.plantuml.SourceStringReader;
 import org.jastadd.option.BooleanOption;
 import org.jastadd.option.ValueOption;
 import org.jastadd.relast.compiler.AbstractCompiler;
@@ -19,13 +12,13 @@ import org.jastadd.relast.compiler.CompilerException;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.nio.file.Files;
-import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 
 public class Compiler extends AbstractCompiler {
 
+  private final Grammar2UmlProcessor processor;
   private ValueOption optionOutputFile;
   private ValueOption optionInputGrammar2Uml;
   private BooleanOption optionDefaultFolders;
@@ -35,6 +28,7 @@ public class Compiler extends AbstractCompiler {
 
   public Compiler() {
     super("grammar2uml" , false);
+    processor = new Grammar2UmlProcessor();
   }
 
   /**
@@ -66,10 +60,6 @@ public class Compiler extends AbstractCompiler {
     }
   }
 
-  private void printMessage(String message) {
-    System.out.println(message);
-  }
-
   protected void initOptions() {
     optionOutputFile = addOption(
         new ValueOption("output" , "target file to be generated.")
@@ -94,52 +84,6 @@ public class Compiler extends AbstractCompiler {
             .defaultValue(false));
   }
 
-  private Grammar2Uml parseProgram() throws CompilerException {
-    Program program = new Program();
-    Grammar2Uml grammar2Uml;
-
-    for (String inputGrammarFileName : getConfiguration().getFiles()) {
-      printMessage("Parsing " + inputGrammarFileName);
-      GrammarFile inputGrammar;
-      try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputGrammarFileName))) {
-        Grammar2UmlScanner scanner = new Grammar2UmlScanner(reader);
-        Grammar2UmlParser parser = new Grammar2UmlParser();
-        inputGrammar = (GrammarFile) parser.parse(scanner);
-        if (optionVerbose.value()) {
-          inputGrammar.dumpTree(System.out);
-        }
-        program.addGrammarFile(inputGrammar);
-        inputGrammar.setFileName(inputGrammarFileName);
-      } catch (IOException | Parser.Exception e) {
-        throw new CompilerException("Could not parse grammar file " + inputGrammarFileName, e);
-      }
-    }
-
-    if (optionInputGrammar2Uml.isMatched()) {
-      String inputGrammar2UmlFileName = optionInputGrammar2Uml.value();
-      try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputGrammar2UmlFileName))) {
-        Grammar2UmlScanner scanner = new Grammar2UmlScanner(reader);
-        Grammar2UmlParser parser = new Grammar2UmlParser();
-        grammar2Uml = (Grammar2Uml) parser.parse(scanner, Grammar2UmlParser.AltGoals.grammar2uml);
-        grammar2Uml.setFileName(inputGrammar2UmlFileName);
-      } catch (IOException | Parser.Exception e) {
-        throw new CompilerException("Could not parse grammar2uml file " + inputGrammar2UmlFileName, e);
-      }
-    } else {
-      // no special setting given
-      grammar2Uml = new Grammar2Uml();
-      grammar2Uml.setFileName("<none>");
-    }
-    grammar2Uml.setProgram(program);
-    grammar2Uml.treeResolveAll();
-    if (optionDefaultFolders.value()) {
-      for (GrammarFile grammarFile : program.getGrammarFileList()) {
-        grammar2Uml.addFolder(grammarFile.defaultFolder());
-      }
-    }
-    return grammar2Uml;
-  }
-
   @Override
   protected int compile() throws CompilerException {
     if (optionVersion.value()) {
@@ -151,71 +95,28 @@ public class Compiler extends AbstractCompiler {
       return 0;
     }
 
-    printMessage("Running grammar2uml " + readVersion());
-
-    Path destination = getDestinationPath();
-    Path parent = Paths.get(optionOutputFile.value()).toAbsolutePath().getParent();
-    try {
-      Files.createDirectories(parent);
-    } catch (IOException e) {
-      throw new CompilerException("Error creating output dir " + parent, e);
-    }
-
-    if (getConfiguration().getFiles().isEmpty()) {
-      throw new CompilerException("No input grammars specified!");
-    }
+    System.out.println("Running grammar2uml " + readVersion());
 
-    Grammar2Uml grammar2uml = parseProgram();
+    getConfiguration().getFiles().forEach(processor::addGrammar);
 
-    if (!grammar2uml.errors().isEmpty()) {
-      System.err.println("Errors:");
-      for (ErrorMessage e : grammar2uml.errors()) {
-        System.err.println(e);
+    if (optionInputGrammar2Uml.isMatched()) {
+      String inputGrammar2UmlFileName = optionInputGrammar2Uml.value();
+      try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputGrammar2UmlFileName))) {
+        Grammar2UmlScanner scanner = new Grammar2UmlScanner(reader);
+        Grammar2UmlParser parser = new Grammar2UmlParser();
+        Grammar2Uml grammar2Uml = (Grammar2Uml) parser.parse(scanner, Grammar2UmlParser.AltGoals.grammar2uml);
+        processor.addPostProcessing(g -> g.setFileName(inputGrammar2UmlFileName));
+        grammar2Uml.getFolderList().forEach(processor::addFolder);
+      } catch (IOException | Parser.Exception e) {
+        throw new CompilerException("Could not parse grammar2uml file " + inputGrammar2UmlFileName, e);
       }
-      System.exit(1);
     }
 
-    printMessage("Writing output file " + destination);
-
-    String sourceCode = grammar2uml.generateAspect();
-
-    //
-    String extension = fileExtensionOf(destination).toUpperCase();
-    FileFormatOption plantUmlOption = null;
-    switch (extension) {
-      case "MD":
-        try {
-          Files.writeString(destination, sourceCode);
-        } catch (Exception e) {
-          throw new CompilerException("Could not write to file " + destination, e);
-        }
-        break;
-      case "HTML":
-      case "PNG":
-      case "PDF":
-      case "SVG":
-        plantUmlOption = new FileFormatOption(FileFormat.valueOf(extension));
-        break;
-    }
-    if (plantUmlOption != null) {
-      try {
-        SourceStringReader reader = new SourceStringReader(sourceCode);
-        reader.outputImage(Files.newOutputStream(destination), plantUmlOption);
-      } catch (Exception e) {
-        throw new CompilerException("Could not write to file " + destination, e);
-      }
-    }
+    processor.setUseDefaultFolders(optionDefaultFolders.value())
+        .setVerbose(optionVerbose.value())
+        .writeFile(Paths.get(optionOutputFile.value()).toAbsolutePath());
 
     return 0;
   }
 
-  private String fileExtensionOf(Path path) {
-    String fileName = path.toFile().getName();
-    int lastIndexOfDot = fileName.lastIndexOf(".");
-    return fileName.substring(lastIndexOfDot + 1);
-  }
-
-  private Path getDestinationPath() {
-    return Paths.get(optionOutputFile.value()).toAbsolutePath();
-  }
 }
diff --git a/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Grammar2UmlProcessor.java b/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Grammar2UmlProcessor.java
new file mode 100644
index 0000000..447214a
--- /dev/null
+++ b/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Grammar2UmlProcessor.java
@@ -0,0 +1,171 @@
+package de.tudresden.inf.st.jastadd.grammar2uml.compiler;
+
+import beaver.Parser;
+import de.tudresden.inf.st.jastadd.grammar2uml.ast.*;
+import de.tudresden.inf.st.jastadd.grammar2uml.parser.Grammar2UmlParser;
+import de.tudresden.inf.st.jastadd.grammar2uml.scanner.Grammar2UmlScanner;
+import net.sourceforge.plantuml.FileFormat;
+import net.sourceforge.plantuml.FileFormatOption;
+import net.sourceforge.plantuml.SourceStringReader;
+import org.jastadd.relast.compiler.CompilerException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Producing images from grammars.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class Grammar2UmlProcessor {
+  private final List<String> inputGrammarFiles = new ArrayList<>();
+  private boolean verbose;
+  private boolean useDefaultFolders;
+  private final List<Folder> folders = new ArrayList<>();
+  private String generatedAspect = null;
+  private final List<Consumer<Grammar2Uml>> callbacks = new ArrayList<>();
+  private StyleDefinition styleDefinition = (typeDecl, style) -> {};
+
+  public Grammar2UmlProcessor addGrammar(String... inputGrammarFiles) {
+    Collections.addAll(this.inputGrammarFiles, inputGrammarFiles);
+    return this;
+  }
+
+  public Grammar2UmlProcessor setVerbose(boolean value) {
+    this.verbose = value;
+    return this;
+  }
+
+  public Grammar2UmlProcessor addFolder(Folder... folders) {
+    Collections.addAll(this.folders, folders);
+    return this;
+  }
+
+  public Grammar2UmlProcessor setUseDefaultFolders(boolean value) {
+    useDefaultFolders = value;
+    return this;
+  }
+
+  public Grammar2UmlProcessor addPostProcessing(Consumer<Grammar2Uml> callback) {
+    this.callbacks.add(callback);
+    return this;
+  }
+
+  public Grammar2UmlProcessor setStyleDefinition(StyleDefinition definition) {
+    this.styleDefinition = definition;
+    return this;
+  }
+
+  public void writeFile(Path destination) throws CompilerException {
+    build();
+
+    Path parent = destination.toAbsolutePath().getParent();
+    try {
+      Files.createDirectories(parent);
+    } catch (IOException e) {
+      throw new CompilerException("Error creating output dir " + parent, e);
+    }
+
+    if (inputGrammarFiles.isEmpty()) {
+      throw new CompilerException("No input grammars specified!");
+    }
+
+    printMessage("Writing output file " + destination);
+    String extension = fileExtensionOf(destination).toUpperCase();
+    FileFormatOption plantUmlOption = null;
+    switch (extension) {
+      case "MD":
+        try {
+          Files.writeString(destination, generatedAspect);
+        } catch (Exception e) {
+          throw new CompilerException("Could not write to file " + destination, e);
+        }
+        break;
+      case "HTML":
+      case "PNG":
+      case "PDF":
+      case "SVG":
+        plantUmlOption = new FileFormatOption(FileFormat.valueOf(extension));
+        break;
+    }
+    if (plantUmlOption != null) {
+      try {
+        SourceStringReader reader = new SourceStringReader(generatedAspect);
+        reader.outputImage(Files.newOutputStream(destination), plantUmlOption);
+      } catch (Exception e) {
+        throw new CompilerException("Could not write to file " + destination, e);
+      }
+    }
+  }
+
+  private void build() throws CompilerException {
+    if (generatedAspect != null) {
+      return;  // already built
+    }
+
+    Program program = new Program();
+    Grammar2Uml grammar2uml = new Grammar2Uml();
+    for (String inputGrammarFileName : inputGrammarFiles) {
+      printMessage("Parsing " + inputGrammarFileName);
+      GrammarFile inputGrammar;
+      try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputGrammarFileName))) {
+        Grammar2UmlScanner scanner = new Grammar2UmlScanner(reader);
+        Grammar2UmlParser parser = new Grammar2UmlParser();
+        inputGrammar = (GrammarFile) parser.parse(scanner);
+        if (verbose) {
+          inputGrammar.dumpTree(System.out);
+        }
+        program.addGrammarFile(inputGrammar);
+        inputGrammar.setFileName(inputGrammarFileName);
+      } catch (IOException | Parser.Exception e) {
+        throw new CompilerException("Could not parse grammar file " + inputGrammarFileName, e);
+      }
+    }
+
+    if (useDefaultFolders) {
+      for (GrammarFile grammarFile : program.getGrammarFileList()) {
+        grammar2uml.addFolder(grammarFile.defaultFolder());
+      }
+    }
+
+    for (Folder folder : folders) {
+      grammar2uml.addFolder(folder);
+    }
+
+    grammar2uml.setStyleDefinition(styleDefinition);
+    callbacks.forEach(consumer -> consumer.accept(grammar2uml));
+
+    grammar2uml.setProgram(program);
+    grammar2uml.treeResolveAll();
+
+    if (!grammar2uml.errors().isEmpty()) {
+      System.err.println("Errors:");
+      for (ErrorMessage e : grammar2uml.errors()) {
+        System.err.println(e);
+      }
+      throw new CompilerException("Exiting because of previous errors.");
+    }
+
+    generatedAspect = grammar2uml.generateAspect();
+  }
+
+  private void printMessage(String message) {
+    if (verbose) {
+      System.out.println(message);
+    }
+  }
+
+  private String fileExtensionOf(Path path) {
+    String fileName = path.toFile().getName();
+    int lastIndexOfDot = fileName.lastIndexOf(".");
+    return fileName.substring(lastIndexOfDot + 1);
+  }
+
+}
diff --git a/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Grammar2UmlMain.java b/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/SimpleMain.java
similarity index 66%
rename from grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Grammar2UmlMain.java
rename to grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/SimpleMain.java
index 17b105c..443b263 100644
--- a/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/Grammar2UmlMain.java
+++ b/grammar2uml/src/main/java/de/tudresden/inf/st/jastadd/grammar2uml/compiler/SimpleMain.java
@@ -4,6 +4,7 @@ import beaver.Parser;
 import de.tudresden.inf.st.jastadd.grammar2uml.ast.*;
 import de.tudresden.inf.st.jastadd.grammar2uml.parser.Grammar2UmlParser;
 import de.tudresden.inf.st.jastadd.grammar2uml.scanner.Grammar2UmlScanner;
+import org.jastadd.relast.compiler.CompilerException;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -16,11 +17,32 @@ import java.nio.file.Paths;
  *
  * @author rschoene - Initial contribution
  */
-public class Grammar2UmlMain {
+public class SimpleMain {
 
-  public static void main(String[] args) {
+  public static void main(String[] args) throws Exception {
 //    testing();
-    processManualAST();
+//    processManualAST();
+    useAPI();
+  }
+
+  private static void useAPI() throws CompilerException {
+    Grammar2UmlProcessor processor = new Grammar2UmlProcessor();
+    // run --args 'src/main/jastadd/Grammar2Uml.relast src/gen/jastadd-sources/relast.preprocessor/RelAst.relast --defaultFolders --output=uml.md'
+    processor.addGrammar("src/main/jastadd/Grammar2Uml.relast",
+        "src/gen/jastadd-sources/relast.preprocessor/RelAst.relast")
+        .setStyleDefinition((typeDecl, style) -> {
+          switch (typeDecl.getName()) {
+            case "Component":
+            case "Comment":
+              style.setInlineAsSuperType(true);
+              break;
+            case "TypeDecl":
+              style.setBackgroundColor("blue\\9932CC");
+          }
+        })
+        .setUseDefaultFolders(true);
+    processor.writeFile(Paths.get("api.md"));
+    processor.writeFile(Paths.get("api.png"));
   }
 
   public static Grammar2Uml createManualAST() {
diff --git a/grammar2uml/src/main/resources/Containment.mustache b/grammar2uml/src/main/resources/Containment.mustache
index 48dbc88..58bc4e9 100644
--- a/grammar2uml/src/main/resources/Containment.mustache
+++ b/grammar2uml/src/main/resources/Containment.mustache
@@ -1 +1 @@
-{{typeName}} *-- {{{modifier}}} {{componentName}} {{#Label}}: {{Label}}{{/Label}}
+{{{typeName}}} *-- {{{modifier}}} {{{componentName}}} {{#Label}}: {{Label}}{{/Label}}
diff --git a/grammar2uml/src/main/resources/Inheritance.mustache b/grammar2uml/src/main/resources/Inheritance.mustache
deleted file mode 100644
index a8f10c0..0000000
--- a/grammar2uml/src/main/resources/Inheritance.mustache
+++ /dev/null
@@ -1 +0,0 @@
-{{superClassName}} <|-- {{subClassName}}
diff --git a/grammar2uml/src/main/resources/Relation.mustache b/grammar2uml/src/main/resources/Relation.mustache
index 7f23515..6e9de53 100644
--- a/grammar2uml/src/main/resources/Relation.mustache
+++ b/grammar2uml/src/main/resources/Relation.mustache
@@ -1 +1 @@
-{{leftName}} {{{leftModifier}}} {{#isBidirectional}}<{{/isBidirectional}}--> {{{rightModifier}}} {{rightName}} {{#Label}}: {{Label}}{{/Label}}
+{{{leftName}}} {{{leftModifier}}} {{#isBidirectional}}<{{/isBidirectional}}-[norank]-> {{{rightModifier}}} {{{rightName}}} {{#Label}}: {{Label}}{{/Label}}
diff --git a/grammar2uml/src/main/resources/TypeDecl.mustache b/grammar2uml/src/main/resources/TypeDecl.mustache
index a72b5dc..4196fea 100644
--- a/grammar2uml/src/main/resources/TypeDecl.mustache
+++ b/grammar2uml/src/main/resources/TypeDecl.mustache
@@ -1,5 +1,6 @@
-{{=<% %>=}}<%#isAbstract%>abstract <%/isAbstract%>class <%name%><%#InnerTokenComponents%><%#first%> {
-<%/first%>
-  <%name%>
-<%#last%>
-}<%/last%><%/InnerTokenComponents%><%={{ }}=%>
+{{#isAbstract}}abstract {{/isAbstract}}class "{{{Name}}}"{{#backgroundColor}} #{{{backgroundColor}}}{{/backgroundColor}}{{#InnerTokenComponents}}{{#first}} {
+{{/first}}
+  {{{type}}} {{{name}}}
+{{#last}}
+}{{/last}}{{/InnerTokenComponents}}
+{{#hasSuperClass}}{{#SuperClass}}"{{{Name}}}"{{/SuperClass}} <|-- "{{{Name}}}"{{/hasSuperClass}}
diff --git a/grammar2uml/src/main/resources/grammar2uml.mustache b/grammar2uml/src/main/resources/grammar2uml.mustache
index 6fe27d0..e2b13e3 100644
--- a/grammar2uml/src/main/resources/grammar2uml.mustache
+++ b/grammar2uml/src/main/resources/grammar2uml.mustache
@@ -6,6 +6,5 @@ hide methods
 {{#OtherTypes}}{{> TypeDecl}}{{/OtherTypes}}
 {{#Containments}}{{> Containment}}{{/Containments}}
 {{#Relations}}{{> Relation}}{{/Relations}}
-{{#Inheritances}}{{> Inheritance}}{{/Inheritances}}
 @enduml
 ```
-- 
GitLab