From 1265bfbb3866a3bd1c966be6b7b744f96e3344fd Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Wed, 22 Apr 2020 20:15:29 +0200
Subject: [PATCH] Base: Begin with parser for ros2rag specification.

- changed compiler to accept two additional required parameters "inputGrammar" and "inputRos2Rag"
- update ros2rag grammar according to latest draft
- begin with parser (missing resolving amongst others)
- parsing in test kind of fails because of leading comment
---
 ros2rag.base/build.gradle                     |  3 +-
 ros2rag.base/src/main/jastadd/Preamble.parser |  1 +
 .../jastadd/{RelAst.flex => Ros2Rag.flex}     | 10 +++
 ros2rag.base/src/main/jastadd/Ros2Rag.parser  | 69 +++++++++++++++++++
 ros2rag.base/src/main/jastadd/Ros2Rag.relast  | 21 +++---
 .../src/main/jastadd/backend/Aspect.jadd      |  4 +-
 .../jastadd/ros2rag/compiler/Compiler.java    | 64 +++++++++++------
 .../jastadd/ros2rag/compiler/SimpleMain.java  |  2 +-
 .../jastadd/ros2rag/tests/RosToRagTest.java   | 11 +--
 ros2rag.base/src/test/resources/Example.jadd  | 47 +++++++++++++
 .../src/test/resources/Example.relast         | 13 ++++
 .../src/test/resources/Example.ros2rag        | 20 ++++++
 .../src/test/resources/MinimalExample.relast  | 14 ----
 .../src/main/jastadd/Example.ros2rag          |  4 +-
 14 files changed, 230 insertions(+), 53 deletions(-)
 rename ros2rag.base/src/main/jastadd/{RelAst.flex => Ros2Rag.flex} (82%)
 create mode 100644 ros2rag.base/src/main/jastadd/Ros2Rag.parser
 create mode 100644 ros2rag.base/src/test/resources/Example.jadd
 create mode 100644 ros2rag.base/src/test/resources/Example.relast
 create mode 100644 ros2rag.base/src/test/resources/Example.ros2rag
 delete mode 100644 ros2rag.base/src/test/resources/MinimalExample.relast

diff --git a/ros2rag.base/build.gradle b/ros2rag.base/build.gradle
index f9afaaf..64d5ca7 100644
--- a/ros2rag.base/build.gradle
+++ b/ros2rag.base/build.gradle
@@ -109,12 +109,13 @@ jastadd {
             }
 
             scanner {
-                include "src/main/jastadd/RelAst.flex"
+                include "src/main/jastadd/Ros2Rag.flex"
             }
 
             parser {
                 include "src/main/jastadd/Preamble.parser"
                 include "src/main/jastadd/RelAst.parser"
+                include "src/main/jastadd/Ros2Rag.parser"
             }
         }
     }
diff --git a/ros2rag.base/src/main/jastadd/Preamble.parser b/ros2rag.base/src/main/jastadd/Preamble.parser
index 386489c..43fa51e 100644
--- a/ros2rag.base/src/main/jastadd/Preamble.parser
+++ b/ros2rag.base/src/main/jastadd/Preamble.parser
@@ -4,3 +4,4 @@ import org.jastadd.ros2rag.ast.*;
 :};
 
 %goal goal;
+%goal ros2rag;
diff --git a/ros2rag.base/src/main/jastadd/RelAst.flex b/ros2rag.base/src/main/jastadd/Ros2Rag.flex
similarity index 82%
rename from ros2rag.base/src/main/jastadd/RelAst.flex
rename to ros2rag.base/src/main/jastadd/Ros2Rag.flex
index b0a3fd2..9ef2f3e 100644
--- a/ros2rag.base/src/main/jastadd/RelAst.flex
+++ b/ros2rag.base/src/main/jastadd/Ros2Rag.flex
@@ -49,6 +49,14 @@ ID = [a-zA-Z$_][a-zA-Z0-9$_]*
 "abstract"   { return sym(Terminals.ABSTRACT); }
 "rel"        { return sym(Terminals.RELATION); }
 
+"read"       { return sym(Terminals.READ); }
+"write"      { return sym(Terminals.WRITE); }
+"using"      { return sym(Terminals.USING); }
+"canDependOn" { return sym(Terminals.CAN_DEPEND_ON); }
+"maps"       { return sym(Terminals.MAPS); }
+"to"         { return sym(Terminals.TO); }
+"as"         { return sym(Terminals.AS); }
+
 ";"          { return sym(Terminals.SCOL); }
 ":"          { return sym(Terminals.COL); }
 "::="        { return sym(Terminals.ASSIGN); }
@@ -59,6 +67,8 @@ ID = [a-zA-Z$_][a-zA-Z0-9$_]*
 ">"          { return sym(Terminals.GT); }
 "["          { return sym(Terminals.LBRACKET); }
 "]"          { return sym(Terminals.RBRACKET); }
+"{"          { return sym(Terminals.LB_CURLY); }
+"}"          { return sym(Terminals.RB_CURLY); }
 "/"          { return sym(Terminals.SLASH); }
 "?"          { return sym(Terminals.QUESTION_MARK); }
 "->"         { return sym(Terminals.RIGHT); }
diff --git a/ros2rag.base/src/main/jastadd/Ros2Rag.parser b/ros2rag.base/src/main/jastadd/Ros2Rag.parser
new file mode 100644
index 0000000..44f2761
--- /dev/null
+++ b/ros2rag.base/src/main/jastadd/Ros2Rag.parser
@@ -0,0 +1,69 @@
+Ros2Rag ros2rag
+  = update_definition.d ros2rag.r       {: r.getUpdateDefinitionList().insertChild(d, 0); return r; :}
+  | dependency_definition.d ros2rag.r   {: r.getDependencyDefinitionList().insertChild(d, 0); return r; :}
+  | mapping_definition.d ros2rag.r      {: r.getMappingDefinitionList().insertChild(d, 0); return r; :}
+  |                                     {: return new Ros2Rag(); :}
+;
+
+// read Joint.CurrentPosition using LinkStateToIntPosition ;
+// write RobotArm._AppropriateSpeed using CreateSpeedMessage ;
+UpdateDefinition update_definition
+  = READ ID.type_name DOT ID.token_name SCOL
+    {:
+      ReadFromMqttDefinition result = new ReadFromMqttDefinition();
+      result.setToken(TokenComponent.createRef(type_name + "." + token_name));
+      return result;
+    :}
+  | READ ID.type_name DOT ID.token_name USING ID.mapping_def SCOL
+    {:
+      ReadFromMqttDefinition result = new ReadFromMqttDefinition();
+      result.setToken(TokenComponent.createRef(type_name + "." + token_name));
+      result.setMapping(MappingDefinition.createRef(mapping_def));
+      return result;
+    :}
+  | WRITE ID.type_name DOT ID.token_name SCOL
+    {:
+      WriteToMqttDefinition result = new WriteToMqttDefinition();
+      result.setToken(TokenComponent.createRef(type_name + "." + token_name));
+      return result;
+    :}
+  | WRITE ID.type_name DOT ID.token_name USING ID.mapping_def SCOL
+    {:
+      WriteToMqttDefinition result = new WriteToMqttDefinition();
+      result.setToken(TokenComponent.createRef(type_name + "." + token_name));
+      result.setMapping(MappingDefinition.createRef(mapping_def));
+      return result;
+    :}
+;
+
+// RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1 ;
+DependencyDefinition dependency_definition
+  = ID.target_type DOT ID.target_token CAN_DEPEND_ON ID.source_type DOT ID.source_token AS ID.id SCOL
+    {:
+      DependencyDefinition result = new DependencyDefinition();
+      result.setSource(TokenComponent.createRef(source_type + "." + source_token));
+      result.setTarget(TokenComponent.createRef(target_type + "." + target_token));
+      result.setID(id);
+      return result;
+    :}
+;
+
+//LinkStateToIntPosition maps protobuf panda.Linkstate.PandaLinkState x to IntPosition y {
+//  panda.Linkstate.PandaLinkState.Position p = x.getPos();
+//  y = IntPosition.of((int) p.getPositionX(), (int) p.getPositionY(), (int) p.getPositionZ());
+//}
+MappingDefinition mapping_definition
+  = ID.id MAPS java_type_use.from_type ID.from_variable TO java_type_use.to_type ID.to_variable LB_CURLY mapping_def_content.content RB_CURLY
+    {:
+      MappingDefinition result = new MappingDefinition();
+      result.setID(id);
+      result.setFrom(from_type);
+      result.setFromVariable(from_variable);
+      result.setTo(to_type);
+      result.setToVariable(to_variable);
+      result.setContent(content);
+      return result;
+    :}
+;
+
+String mapping_def_content = ID ;
diff --git a/ros2rag.base/src/main/jastadd/Ros2Rag.relast b/ros2rag.base/src/main/jastadd/Ros2Rag.relast
index 75c8df8..6d13bd1 100644
--- a/ros2rag.base/src/main/jastadd/Ros2Rag.relast
+++ b/ros2rag.base/src/main/jastadd/Ros2Rag.relast
@@ -1,13 +1,18 @@
-Ros2Rag ::= MappingDefinition* SyncDefinition* Program;
+Ros2Rag ::= UpdateDefinition* DependencyDefinition* MappingDefinition* Program;
 
-abstract SyncDefinition ::= <AlwaysApply:Boolean> ;
+abstract UpdateDefinition ::= <AlwaysApply:Boolean> ;
 
-rel SyncDefinition.Mapping? -> MappingDefinition;
+rel UpdateDefinition.Mapping? -> MappingDefinition;
 
-abstract TokenSyncDefinition : SyncDefinition;
-rel TokenSyncDefinition.token -> TokenComponent;
+abstract TokenUpdateDefinition : UpdateDefinition;
+rel TokenUpdateDefinition.Token -> TokenComponent;
 
-ReadFromMqttDefinition : TokenSyncDefinition;
-WriteToMqttDefinition  : TokenSyncDefinition;
+ReadFromMqttDefinition : TokenUpdateDefinition;
+WriteToMqttDefinition  : TokenUpdateDefinition;
 
-MappingDefinition ::= <ID> From:JavaTypeUse To:JavaTypeUse <Content> ;
+// example: RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1
+DependencyDefinition ::= <ID> ;
+rel DependencyDefinition.Source -> TokenComponent ;
+rel DependencyDefinition.Target -> TokenComponent ;
+
+MappingDefinition ::= <ID> From:JavaTypeUse <FromVariable> To:JavaTypeUse <ToVariable> <Content> ;
diff --git a/ros2rag.base/src/main/jastadd/backend/Aspect.jadd b/ros2rag.base/src/main/jastadd/backend/Aspect.jadd
index c63ec13..d403bbc 100644
--- a/ros2rag.base/src/main/jastadd/backend/Aspect.jadd
+++ b/ros2rag.base/src/main/jastadd/backend/Aspect.jadd
@@ -31,14 +31,14 @@ aspect Aspect {
   public void Ros2Rag.generateAspect(StringBuilder sb) {
     sb.append("aspect ROS2RAG {\n");
 
-    for (SyncDefinition def : getSyncDefinitionList()) {
+    for (UpdateDefinition def : getUpdateDefinitionList()) {
       def.generateAspect(sb);
     }
 
     sb.append("}\n");
   }
 
-  abstract void SyncDefinition.generateAspect(StringBuilder sb);
+  abstract void UpdateDefinition.generateAspect(StringBuilder sb);
 //  @Override
 //  void UpdateDefinition.generateAspect(StringBuilder sb) {
 //    // TODO
diff --git a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
index 1ba4fbb..fac820c 100644
--- a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
+++ b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
@@ -10,12 +10,16 @@ import org.jastadd.ros2rag.parser.RelAstParser;
 import org.jastadd.ros2rag.scanner.RelAstScanner;
 
 import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 
 public class Compiler {
 
   private StringOption optionOutputDir;
+  private StringOption optionInputGrammar;
+  private StringOption optionInputRos2Rag;
 
   private ArrayList<Option<?>> options;
   private CommandLine commandLine;
@@ -31,21 +35,28 @@ public class Compiler {
     if (optionOutputDir.isSet()) {
       outputDir = optionOutputDir.getValue();
     } else {
-      System.out.println("No output grammar name is set. Assuming output dir '" + outputDir + "'.");
+      System.out.println("No output output dir is set. Assuming '" + outputDir + "'.");
     }
 
     printMessage("Running ROS2RAG Preprocessor");
 
-    if (commandLine.getArguments().size() < 1) {
-      error("specify at least one input file");
+    if (!optionInputGrammar.isSet()) {
+      error("specify an input grammar");
     }
 
-    List<String> filenames = commandLine.getArguments();
-    Program p = parseProgram(filenames);
+    if (!optionInputRos2Rag.isSet()) {
+      error("specify the ros2rag definition file");
+    }
 
-    writeToFile(outputDir + "/Grammar.ast", p.generateAbstractGrammar());
-    writeToFile(outputDir + "/ROS2RAG.jadd", p.generateAspect());
+    List<String> otherArgs = commandLine.getArguments();
+    if (!otherArgs.isEmpty()) {
+      printMessage("Superfluous arguments will be ignored: " + otherArgs);
+    }
+    Ros2Rag ros2Rag = parseProgram(optionInputGrammar.getValue(), optionInputRos2Rag.getValue());
 
+    printMessage("Writing output files");
+    writeToFile(outputDir + "/Grammar.relast", ros2Rag.getProgram().generateAbstractGrammar());
+    writeToFile(outputDir + "/ROS2RAG.jadd", ros2Rag.generateAspect());
   }
 
 
@@ -75,6 +86,8 @@ public class Compiler {
 
   private void addOptions() {
     optionOutputDir = addOption(new StringOption("outputDir", "target directory for the generated files."));
+    optionInputGrammar = addOption(new StringOption("inputGrammar", "base grammar."));
+    optionInputRos2Rag = addOption(new StringOption("inputRos2Rag", "ros2rag definition file."));
   }
 
   private <OptionType extends Option<?>> OptionType addOption(OptionType option) {
@@ -82,23 +95,32 @@ public class Compiler {
     return option;
   }
 
-  private Program parseProgram(List<String> fileNames) {
-
+  private Ros2Rag parseProgram(String inputGrammarFileName, String inputRos2RagFileName) {
     Program program = new Program();
+    Ros2Rag ros2Rag;
+
+    try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputGrammarFileName))) {
+      RelAstScanner scanner = new RelAstScanner(reader);
+      RelAstParser parser = new RelAstParser();
+      GrammarFile inputGrammar = (GrammarFile) parser.parse(scanner);
+      program.addGrammarFile(inputGrammar);
+    } catch (IOException | Parser.Exception e) {
+      System.err.println(e.getMessage());
+      System.exit(1);
+    }
 
-    for (String fileName : fileNames) {
-      FileReader reader = null;
-      try {
-        reader = new FileReader(fileName);
-      } catch (FileNotFoundException e) {
-        System.err.println(e.getMessage());
-        System.exit(1);
-      }
-
-      parse(program, reader, fileName);
+    try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputRos2RagFileName))) {
+      RelAstScanner scanner = new RelAstScanner(reader);
+      RelAstParser parser = new RelAstParser();
+      ros2Rag = (Ros2Rag) parser.parse(scanner, RelAstParser.AltGoals.ros2rag);
+      ros2Rag.setProgram(program);
+    } catch (IOException | Parser.Exception e) {
+      System.err.println(e.getMessage());
+      System.exit(2);
+      return null;  // poor compiler does not know System.exit()
     }
-    program.treeResolveAll();
-    return program;
+
+    return ros2Rag;
   }
 
   private void parse(Program program, Reader reader, String file) {
diff --git a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/SimpleMain.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/SimpleMain.java
index e27a97e..7d528ac 100644
--- a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/SimpleMain.java
+++ b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/SimpleMain.java
@@ -64,7 +64,7 @@ public class SimpleMain {
     readFromMqttDefinition.setAlwaysApply(false);
     readFromMqttDefinition.setToken(TokenComponent.createRef("Joint.CurrentPosition"));
     readFromMqttDefinition.setMapping(mappingDefinition);
-    model.addSyncDefinition(readFromMqttDefinition);
+    model.addUpdateDefinition(readFromMqttDefinition);
 
     model.treeResolveAll();
 
diff --git a/ros2rag.base/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java b/ros2rag.base/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
index 52e1a4b..19ad7e5 100644
--- a/ros2rag.base/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
+++ b/ros2rag.base/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
@@ -11,10 +11,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class RosToRagTest {
 
-  void transform(String inputFile, String outputDir) throws CommandLine.CommandLineException {
+  void transform(String inputGrammar, String inputRos2Rag, String outputDir) throws CommandLine.CommandLineException {
 
     System.out.println(Paths.get(".").toAbsolutePath());
-    assertTrue(Paths.get(inputFile).toFile().exists(), "input file does not exist");
+    assertTrue(Paths.get(inputGrammar).toFile().exists(), "input grammar does not exist");
 
     File outputDirFile = Paths.get(outputDir).toFile();
     if (outputDirFile.exists()) {
@@ -28,7 +28,8 @@ public class RosToRagTest {
 
     String[] args = {
         "--outputDir=" + outputDir,
-        inputFile
+        "--inputGrammar=" + inputGrammar,
+        "--inputRos2Rag=" + inputRos2Rag
     };
 
     new Compiler(args);
@@ -36,6 +37,8 @@ public class RosToRagTest {
 
   @Test
   void transformMinimalExample() throws CommandLine.CommandLineException {
-    transform("src/test/resources/MinimalExample.relast", "src/test/resources/out");
+    transform("src/test/resources/Example.relast",
+        "src/test/resources/Example.ros2rag",
+        "src/test/resources/out");
   }
 }
diff --git a/ros2rag.base/src/test/resources/Example.jadd b/ros2rag.base/src/test/resources/Example.jadd
new file mode 100644
index 0000000..1fd2549
--- /dev/null
+++ b/ros2rag.base/src/test/resources/Example.jadd
@@ -0,0 +1,47 @@
+aspect GrammarTypes {
+  public class IntPosition {
+    private final int x, y, z;
+
+    private IntPosition(int x, int y, int z) {
+      this.x = x;
+      this.y = y;
+      this.z = z;
+    }
+
+    public static IntPosition of(int x, int y, int z) {
+      return new IntPosition(x, y, z);
+    }
+
+    public int getX() {
+      return x;
+    }
+
+    public int getY() {
+      return y;
+    }
+
+    public int getZ() {
+      return z;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      IntPosition that = (IntPosition) o;
+      return x == that.x &&
+          y == that.y &&
+          z == that.z;
+    }
+
+    @Override
+    public int hashCode() {
+      return java.util.Objects.hash(x, y, z);
+    }
+
+    @Override
+    public String toString() {
+      return "(" + x + ", " + y + ", " + z + ")";
+    }
+  }
+}
diff --git a/ros2rag.base/src/test/resources/Example.relast b/ros2rag.base/src/test/resources/Example.relast
new file mode 100644
index 0000000..f4b2aa0
--- /dev/null
+++ b/ros2rag.base/src/test/resources/Example.relast
@@ -0,0 +1,13 @@
+Model ::= RobotArm ZoneModel ;
+
+ZoneModel ::= <Size:IntPosition> SafetyZone:Zone* ;
+
+Zone ::= Coordinate* ;
+
+RobotArm ::= Joint* EndEffector <_AttributeTestSource:int> /<_AppropriateSpeed:double>/ ; // normally this would be: <AttributeTestSource:int> ;
+
+Joint ::= <Name> <_CurrentPosition:IntPosition> ;  // normally this would be: <CurrentPosition:IntPosition>
+
+EndEffector : Joint;
+
+Coordinate ::= <Position:IntPosition> ;
diff --git a/ros2rag.base/src/test/resources/Example.ros2rag b/ros2rag.base/src/test/resources/Example.ros2rag
new file mode 100644
index 0000000..26702ca
--- /dev/null
+++ b/ros2rag.base/src/test/resources/Example.ros2rag
@@ -0,0 +1,20 @@
+/* Version 2020-04-17 */
+// --- update definitions ---
+read Joint.CurrentPosition using LinkStateToIntPosition ;
+write RobotArm._AppropriateSpeed using CreateSpeedMessage ;
+
+// --- dependency definitions ---
+RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1 ;
+RobotArm._AppropriateSpeed canDependOn RobotArm._AttributeTestSource as dependency2 ;
+
+// --- mapping definitions ---
+LinkStateToIntPosition: map protobuf panda.Linkstate.PandaLinkState x to IntPosition y {
+  panda.Linkstate.PandaLinkState.Position p = x.getPos();
+  y = IntPosition.of((int) p.getPositionX(), (int) p.getPositionY(), (int) p.getPositionZ());
+}
+
+CreateSpeedMessage: map double x to protobuf config.Robotconfig.RobotConfig y {
+  y = config.Robotconfig.RobotConfig.newBuilder()
+    .setSpeed(x)
+    .build();
+}
diff --git a/ros2rag.base/src/test/resources/MinimalExample.relast b/ros2rag.base/src/test/resources/MinimalExample.relast
deleted file mode 100644
index 7152bd1..0000000
--- a/ros2rag.base/src/test/resources/MinimalExample.relast
+++ /dev/null
@@ -1,14 +0,0 @@
-Model ::= RobotArm ZoneModel ;
-
-ZoneModel ::= Size:Position SafetyZone:Zone*;
-
-Zone ::= Position*;
-
-RobotArm ::= Joint* EndEffector /<ShouldUseLowSpeed:Boolean>/ ;
-
-Joint ::= <Name> ;
-rel Joint.CurrentPosition -> Position ;
-
-EndEffector : Joint;
-
-Position ::= <x:int> <y:int> <z:int> ;
diff --git a/ros2rag.example/src/main/jastadd/Example.ros2rag b/ros2rag.example/src/main/jastadd/Example.ros2rag
index f6b49f2..e65c535 100644
--- a/ros2rag.example/src/main/jastadd/Example.ros2rag
+++ b/ros2rag.example/src/main/jastadd/Example.ros2rag
@@ -10,12 +10,12 @@ RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1 ;
 RobotArm._AppropriateSpeed canDependOn RobotArm._AttributeTestSource as dependency2 ;
 
 // --- mapping definitions ---
-LinkStateToIntPosition: map protobuf panda.Linkstate.PandaLinkState x to IntPosition y {
+LinkStateToIntPosition maps protobuf panda.Linkstate.PandaLinkState x to IntPosition y {
   panda.Linkstate.PandaLinkState.Position p = x.getPos();
   y = IntPosition.of((int) p.getPositionX(), (int) p.getPositionY(), (int) p.getPositionZ());
 }
 
-CreateSpeedMessage: map double x to protobuf config.Robotconfig.RobotConfig y {
+CreateSpeedMessage maps double x to protobuf config.Robotconfig.RobotConfig y {
   y = config.Robotconfig.RobotConfig.newBuilder()
     .setSpeed(x)
     .build();
-- 
GitLab