diff --git a/scope4m/src/main/jastadd/Helpers.jadd b/scope4m/src/main/jastadd/Helpers.jadd index 3e171a6a2a637d32b77284caafb5f17d8ede8ad2..76a6d97efa1c53ff341fcce7516959d8a061686b 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 b3c375dc727fbdddc15c810487d0fca7a35a6edd..d9a6b42e29d38bf56201f0a6ff451605197f1b6c 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 4c47dbc6b0ea111cf691919dba7bb0339e26dad7..082b97dcdbab81c65ce7836df9653a1821aa3537 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 f7ce25d93bcc26f660f89f0887e8e6a537f9e5d1..a7dd42db699179a4d5e981856d9b49ff96fcaa69 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 0000000000000000000000000000000000000000..77496a1ab2c59bbf3e0de6e84246fb1182326a96 --- /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 0000000000000000000000000000000000000000..065a38d3d2a4cbcfd584126d2712f65a41e6ea2c --- /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 0000000000000000000000000000000000000000..e097ec489e6d873de38a6ecc77158752b6f62ca9 --- /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 0000000000000000000000000000000000000000..538bd3cdabf0098fb7e674d4f8ecb3430089bf6a --- /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 0000000000000000000000000000000000000000..d9877672c16dde105d17749aa34120e21f87985d --- /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 0000000000000000000000000000000000000000..75b8e8a381d9290172f94b051b9e99a0d1e35f77 --- /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 0000000000000000000000000000000000000000..75b8e8a381d9290172f94b051b9e99a0d1e35f77 --- /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;