From 8d3e2019366aa6df2edf4c3c60126f920bc8e651 Mon Sep 17 00:00:00 2001
From: Johannes Mey <johannes.mey@tu-dresden.de>
Date: Fri, 3 Jan 2020 19:31:35 +0100
Subject: [PATCH] initial version of modelica scope tree generation.
 inheritance not treated properly yet.

---
 scope4m/src/main/jastadd/Helpers.jadd         | 22 ++++---
 .../src/main/jastadd/ModelicaToScopeTree.jrag | 63 ++++++++++++++++++-
 .../main/jastadd/ModelicaToScopeTree.relast   | 18 ++++++
 .../java/org/jmodelica/ScopeAnalysis.java     |  9 ++-
 .../org/jmodelica/ComplicatedScopeTest.java   | 20 ++++++
 .../java/org/jmodelica/EncapsulationTest.java | 16 +++++
 .../java/org/jmodelica/ScopeAnalysisTest.java | 36 +++++++++++
 .../java/org/jmodelica/SimpleScopeTest.java   | 20 ++++++
 .../test/resources/complicated/complicated.mo |  0
 .../resources/encapsulated/encapsulation.mo   | 19 ++++++
 scope4m/src/test/resources/simple/simple.mo   | 17 +++++
 testprograms/modelica/simple/simple.mo        | 17 +++++
 12 files changed, 243 insertions(+), 14 deletions(-)
 create mode 100644 scope4m/src/test/java/org/jmodelica/ComplicatedScopeTest.java
 create mode 100644 scope4m/src/test/java/org/jmodelica/EncapsulationTest.java
 create mode 100644 scope4m/src/test/java/org/jmodelica/ScopeAnalysisTest.java
 create mode 100644 scope4m/src/test/java/org/jmodelica/SimpleScopeTest.java
 rename testprograms/modelica/ComplicatedNameLookup.mo => scope4m/src/test/resources/complicated/complicated.mo (100%)
 create mode 100644 scope4m/src/test/resources/encapsulated/encapsulation.mo
 create mode 100644 scope4m/src/test/resources/simple/simple.mo
 create mode 100644 testprograms/modelica/simple/simple.mo

diff --git a/scope4m/src/main/jastadd/Helpers.jadd b/scope4m/src/main/jastadd/Helpers.jadd
index 3e171a6..76a6d97 100644
--- a/scope4m/src/main/jastadd/Helpers.jadd
+++ b/scope4m/src/main/jastadd/Helpers.jadd
@@ -1,9 +1,7 @@
-/** copy of the extendj source location interface */
+/** helper methods to provide source locations */
 aspect SourceLocation {
 
-  protected String ASTNode.sourceFile() {
-    return retrieveFileName();
-  }
+  syn String ASTNode.sourceLocation() = sourceFile() + ":" + lineNumber();
 
   syn int ASTNode.lineNumber() {
     ASTNode n = this;
@@ -12,8 +10,18 @@ aspect SourceLocation {
     }
     return getLine(n.getStart());
   }
+  eq ClassDeclScope.lineNumber() = getClassDecl().lineNumber();
+  eq ForStmtScope.lineNumber() = getForStmt().lineNumber();
+  eq ComponentDeclaration.lineNumber() = getComponentDecl().lineNumber();
 
-  public String ASTNode.sourceLocation() {
-    return sourceFile() + ":" + lineNumber();
-  }
+  eq ClassDeclScope.toString() = "Class-" + getClassDecl().name() + "-Scope";
+  eq ForStmtScope.toString() = getForStmt().getClass().getSimpleName() + "-Scope";
+
+  eq ComponentDeclaration.toString() = getComponentDecl().getClass().getSimpleName() + ":" + super.toString();
+
+  syn String ASTNode.sourceFile() = "<unknown>";
+
+  eq ClassDeclScope.sourceFile() = getClassDecl().sourceFile();
+  eq ForStmtScope.sourceFile() = getForStmt().sourceFile();
+  eq ComponentDeclaration.sourceFile() =  getComponentDecl().sourceFile();
 }
diff --git a/scope4m/src/main/jastadd/ModelicaToScopeTree.jrag b/scope4m/src/main/jastadd/ModelicaToScopeTree.jrag
index b3c375d..d9a6b42 100644
--- a/scope4m/src/main/jastadd/ModelicaToScopeTree.jrag
+++ b/scope4m/src/main/jastadd/ModelicaToScopeTree.jrag
@@ -1,12 +1,71 @@
 aspect ModelicaToScopeTree {
-  syn lazy ScopeTree SourceRoot.scopeTree() {
+  /** a relational nta collection attribute to compute the scope tree */
+  syn lazy ScopeTree SourceRoot.scopeTree() = (ScopeTree) scope();
+
+  /** a relational nta collection attribute to compute scopes */
+  coll Scope ASTNode.scope() [asScope()] with addElement root SourceRoot;
+
+  // collect all scopes
+  SrcClassDecl contributes scope() when !isEncapsulated() to ASTNode.scope() for containingScope();
+  // if a scope is encapsulated, it is added to the top-level, because the inner 
+  SrcClassDecl contributes scope() when  isEncapsulated() to ASTNode.scope() for srcRoot();
+
+  SrcForStmt contributes scope() to ASTNode.scope() for containingScope();
+
+  // collect all elements
+  SrcComponentDecl contributes asDeclaration() to ASTNode.scope() for containingScope();
+}
+
+/**
+ * ascpect containing helper methods to construct (mostly empty) AST nodes of the scope tree
+ * If it was not for the single line in asProtectedScope(), the rest of this aspect could have been generated
+ *  automatically, which would have been much nicer!
+ */
+aspect ScopeTreeConstructors {
+
+  syn lazy ScopeTree SourceRoot.asScope() {
     ScopeTree tree = new ScopeTree();
     tree.setSourceRoot(this);
-
     return tree;
   }
+
+  /** fallback attribute to ensure every AST element could pontentially be a scope */
+  syn Scope ASTNode.asScope() {
+    throw new RuntimeException("unable to create a scope for type " + getClass().getSimpleName());
+  }
+
+  syn lazy ClassDeclScope SrcClassDecl.asScope() {
+    ClassDeclScope scope = new ClassDeclScope();
+    scope.setClassDecl(this);
+    return scope;
+  }
+
+  syn lazy ForStmtScope SrcForStmt.asScope() {
+    ForStmtScope scope = new ForStmtScope();
+    scope.setForStmt(this);
+    return scope;
+  }
+
+  syn lazy ComponentDeclaration SrcComponentDecl.asDeclaration() {
+    ComponentDeclaration decl = new ComponentDeclaration(getName().getID());
+    decl.setComponentDecl(this);
+    return decl;
+  }
+
 }
 
 aspect ScopeGenerationAttributes {
 
+  /** determine the scope an AST element is contained in or belongs to.*/
+  inh lazy ASTNode SrcClassDecl.containingScope();
+  inh lazy ASTNode SrcForStmt.containingScope();
+  inh lazy ASTNode SrcComponentDecl.containingScope();
+  // contained in scope:
+  eq SourceRoot.getChild().containingScope() = this;
+  eq SrcClassDecl.getChild().containingScope() = this;
+  eq SrcForStmt.getChild().containingScope() = this;
+
+  inh SourceRoot SrcBaseNode.srcRoot();
+  eq SourceRoot.getChild().srcRoot() = this;
+
 }
diff --git a/scope4m/src/main/jastadd/ModelicaToScopeTree.relast b/scope4m/src/main/jastadd/ModelicaToScopeTree.relast
index 4c47dbc..082b97d 100644
--- a/scope4m/src/main/jastadd/ModelicaToScopeTree.relast
+++ b/scope4m/src/main/jastadd/ModelicaToScopeTree.relast
@@ -1,2 +1,20 @@
 // glue relation for the Java-based variable shadowing analysis
 rel ScopeTree.SourceRoot -> SourceRoot;
+
+// scopes
+
+ClassDeclScope : Scope;
+rel ClassDeclScope.classDecl -> SrcClassDecl;
+
+ForStmtScope : Scope;
+rel ForStmtScope.forStmt -> SrcForStmt;
+
+// we do not need the for iter expression, because it cannot contains declarations
+// IterExpScope : Scope;
+// rel IterExpScope.iterExp -> SrcIterExp;
+
+// declarations
+
+ComponentDeclaration : Declaration;
+rel ComponentDeclaration.componentDecl -> SrcComponentDecl;
+
diff --git a/scope4m/src/main/java/org/jmodelica/ScopeAnalysis.java b/scope4m/src/main/java/org/jmodelica/ScopeAnalysis.java
index f7ce25d..a7dd42d 100644
--- a/scope4m/src/main/java/org/jmodelica/ScopeAnalysis.java
+++ b/scope4m/src/main/java/org/jmodelica/ScopeAnalysis.java
@@ -37,10 +37,10 @@ public class ScopeAnalysis {
     boolean warnings = arguments.remove("--warnings");
 
     if (arguments.size() > 1) {
-      System.out.println("usage: ScopeAnalysis [--debug] [--tree] [--warnings] <directory with java files>");
+      System.out.println("usage: ScopeAnalysis [--debug] [--tree] [--warnings] <directory with modelica files>");
       System.exit(-1);
     }
-    String path = arguments.isEmpty() ? "../testprograms/modelica" : arguments.get(arguments.size() - 1);
+    String path = arguments.isEmpty() ? "../testprograms/modelica/simple" : arguments.get(arguments.size() - 1);
 
     if (debug) {
       new ScopeAnalysis().analyze(path, tree, warnings);
@@ -117,6 +117,7 @@ public class ScopeAnalysis {
 
       if (warnings) {
         // TODO find out if there are compiler warnings in JModelica
+        System.out.println("Currently, compiler warnings are not supported for jModelica.");
       }
 
       Set<AbstractFinding> findings = scopeTree.variableShadowings();
@@ -129,15 +130,13 @@ public class ScopeAnalysis {
 
       return findings;
     } catch (IOException | Parser.Exception | CompilerException e) {
+      System.out.println("Current relative path is: " + Paths.get("").toAbsolutePath());
       throw new RuntimeException(e);
     }
   }
 
   private static SourceRoot readProgram(Collection<String> files) throws IOException, beaver.Parser.Exception, CompilerException {
-
-
     ModelicaCompiler mc = new ModelicaCompiler(ModelicaCompiler.createOptions());
     return mc.getParserHandler().parseModel(UtilInterface.create(mc), (files.toArray(new String[]{})));
-
   }
 }
diff --git a/scope4m/src/test/java/org/jmodelica/ComplicatedScopeTest.java b/scope4m/src/test/java/org/jmodelica/ComplicatedScopeTest.java
new file mode 100644
index 0000000..77496a1
--- /dev/null
+++ b/scope4m/src/test/java/org/jmodelica/ComplicatedScopeTest.java
@@ -0,0 +1,20 @@
+package org.jmodelica;
+
+import org.jmodelica.compiler.AbstractFinding;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+public class ComplicatedScopeTest extends ScopeAnalysisTest {
+
+  @Test
+  void test() {
+
+    ScopeAnalysis scopeAnalysis = new ScopeAnalysis();
+    Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/complicated/", true, false);
+
+
+  }
+
+}
diff --git a/scope4m/src/test/java/org/jmodelica/EncapsulationTest.java b/scope4m/src/test/java/org/jmodelica/EncapsulationTest.java
new file mode 100644
index 0000000..065a38d
--- /dev/null
+++ b/scope4m/src/test/java/org/jmodelica/EncapsulationTest.java
@@ -0,0 +1,16 @@
+package org.jmodelica;
+
+import org.jmodelica.compiler.AbstractFinding;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+public class EncapsulationTest extends ScopeAnalysisTest {
+
+  @Test
+  void test() {
+    ScopeAnalysis scopeAnalysis = new ScopeAnalysis();
+    Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/encapsulated", true, false);
+  }
+
+}
diff --git a/scope4m/src/test/java/org/jmodelica/ScopeAnalysisTest.java b/scope4m/src/test/java/org/jmodelica/ScopeAnalysisTest.java
new file mode 100644
index 0000000..e097ec4
--- /dev/null
+++ b/scope4m/src/test/java/org/jmodelica/ScopeAnalysisTest.java
@@ -0,0 +1,36 @@
+package org.jmodelica;
+
+import org.jmodelica.compiler.AbstractFinding;
+import org.jmodelica.compiler.Declaration;
+import org.jmodelica.compiler.MultipleDeclarationFinding;
+import org.jmodelica.compiler.VariableShadowFinding;
+import org.junit.jupiter.api.Assertions;
+
+import java.util.Set;
+
+public abstract class ScopeAnalysisTest {
+  static void assertShadow(Set<AbstractFinding> findings, String name, int shadowerLine, int shadowedLine) {
+    for (AbstractFinding finding : findings) {
+      if (finding instanceof VariableShadowFinding) {
+        Declaration shadower = ((VariableShadowFinding)finding).getShadower();
+        Declaration shadowed = ((VariableShadowFinding)finding).getShadowed();
+        if (shadowed.getName().equals(name) && shadowed.lineNumber() == shadowedLine && shadower.lineNumber() == shadowerLine) {
+          return;
+        }
+      }
+    }
+    Assertions.fail("No shadow finding found for name '" + name + "' in lines " + shadowerLine + " > " + shadowedLine);
+  }
+
+  static void assertRedefinition(Set<AbstractFinding> findings, String name, int declLine) {
+    for (AbstractFinding finding : findings) {
+      if (finding instanceof MultipleDeclarationFinding) {
+        Declaration declaration = ((MultipleDeclarationFinding)finding).getDeclaration();
+        if (declaration.getName().equals(name) && declaration.lineNumber() == declLine) {
+          return;
+        }
+      }
+    }
+    Assertions.fail("No multi-decl finding found for name '" + name + "' in line " + declLine);
+  }
+}
diff --git a/scope4m/src/test/java/org/jmodelica/SimpleScopeTest.java b/scope4m/src/test/java/org/jmodelica/SimpleScopeTest.java
new file mode 100644
index 0000000..538bd3c
--- /dev/null
+++ b/scope4m/src/test/java/org/jmodelica/SimpleScopeTest.java
@@ -0,0 +1,20 @@
+package org.jmodelica;
+
+import org.jmodelica.compiler.AbstractFinding;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+public class SimpleScopeTest extends ScopeAnalysisTest {
+
+  @Test
+  void test() {
+
+    ScopeAnalysis scopeAnalysis = new ScopeAnalysis();
+    Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/simple", true, false);
+
+
+  }
+
+}
diff --git a/testprograms/modelica/ComplicatedNameLookup.mo b/scope4m/src/test/resources/complicated/complicated.mo
similarity index 100%
rename from testprograms/modelica/ComplicatedNameLookup.mo
rename to scope4m/src/test/resources/complicated/complicated.mo
diff --git a/scope4m/src/test/resources/encapsulated/encapsulation.mo b/scope4m/src/test/resources/encapsulated/encapsulation.mo
new file mode 100644
index 0000000..d987767
--- /dev/null
+++ b/scope4m/src/test/resources/encapsulated/encapsulation.mo
@@ -0,0 +1,19 @@
+within ModelicaCompliance.Scoping.NameLookup.Simple;
+
+model Encapsulation
+  extends Icons.TestCase;
+
+  encapsulated model A
+    constant Integer x = abs(-4);
+  equation
+    assert(x == 4, "x was not set correctly!");
+  end A;
+
+  A a;
+
+  annotation (
+    __ModelicaAssociation(TestCase(shouldPass = true, section = {"5.3.1"})),
+    experiment(StopTime = 0.01),
+    Documentation(
+    info = "<html>Tests that builtin functions can be found even if the scope is encapsulated.</html>"));
+end Encapsulation;
diff --git a/scope4m/src/test/resources/simple/simple.mo b/scope4m/src/test/resources/simple/simple.mo
new file mode 100644
index 0000000..75b8e8a
--- /dev/null
+++ b/scope4m/src/test/resources/simple/simple.mo
@@ -0,0 +1,17 @@
+within ModelicaCompliance.Scoping.InnerOuter;
+
+model SimpleNameLookup
+  extends Icons.TestCase;
+
+  class A
+    outer Integer T0;
+  end A;
+
+  class B
+    inner Integer T0 = 10;
+    A a1, a2; // B.T0, B.a1.T0 and B.a2.T0 is the same variable
+  end B;
+
+  B b;
+equation
+end SimpleNameLookup;
diff --git a/testprograms/modelica/simple/simple.mo b/testprograms/modelica/simple/simple.mo
new file mode 100644
index 0000000..75b8e8a
--- /dev/null
+++ b/testprograms/modelica/simple/simple.mo
@@ -0,0 +1,17 @@
+within ModelicaCompliance.Scoping.InnerOuter;
+
+model SimpleNameLookup
+  extends Icons.TestCase;
+
+  class A
+    outer Integer T0;
+  end A;
+
+  class B
+    inner Integer T0 = 10;
+    A a1, a2; // B.T0, B.a1.T0 and B.a2.T0 is the same variable
+  end B;
+
+  B b;
+equation
+end SimpleNameLookup;
-- 
GitLab