diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md index 138ad9b2fae4140e8d172635202a089d12d7e4b6..1fcc26bc7052a700a13b934b0e4c2ee32bb2b6d9 100644 --- a/pages/docs/changelog.md +++ b/pages/docs/changelog.md @@ -7,6 +7,7 @@ ### Development Changes - Bugfix: "error: variable handler is already defined" when using multiple protocols [#58](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/58) +- Bugfix: Inherited components of a type can not be chosen as port targets [#59](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/59) ## 1.0.0 diff --git a/ragconnect.base/src/main/jastadd/NameResolution.jrag b/ragconnect.base/src/main/jastadd/NameResolution.jrag index e470430d8f5d355cd90d4e79503ee431de994a18..6e7c74576f1a6ef68d045733cc59a6b6368a2246 100644 --- a/ragconnect.base/src/main/jastadd/NameResolution.jrag +++ b/ragconnect.base/src/main/jastadd/NameResolution.jrag @@ -91,7 +91,7 @@ aspect RagConnectNameResolution { String childTypeName = id.substring(dotIndex + 1); TypeDecl type = program().resolveTypeDecl(parentTypeName); // iterate over components and find the matching typeComponent - for (Component comp : type.getComponentList()) { + for (Component comp : type.allComponentsAsOwnedByMe()) { if (comp.isTypeComponent() && comp.getName().equals(childTypeName)) { return comp.asTypeComponent(); } @@ -114,7 +114,7 @@ aspect RagConnectNameResolution { String childTypeName = id.substring(dotIndex + 1); TypeDecl type = program().resolveTypeDecl(parentTypeName); // iterate over components and find the matching typeComponent - for (Component comp : type.getComponentList()) { + for (Component comp : type.allComponentsAsOwnedByMe()) { if (comp.getName().equals(childTypeName)) { return comp; } @@ -138,7 +138,7 @@ aspect RagConnectNameResolution { String tokenName = id.substring(dotIndex + 1); TypeDecl type = program().resolveTypeDecl(typeName); // iterate over components and find the matching tokenComponent - for (Component comp : type.getComponentList()) { + for (Component comp : type.allComponentsAsOwnedByMe()) { if (comp.isTokenComponent() && comp.getName().equals(tokenName)) { return comp.asTokenComponent(); } @@ -175,10 +175,60 @@ aspect RagConnectNameResolution { } return null; } +} +aspect RelastNameResolution { syn boolean Role.matches(String typeName, String roleName) = false; eq NavigableRole.matches(String typeName, String roleName) { return getType().getName().equals(typeName) && getName().equals(roleName); } + /** Returns all components including inherited ones */ + syn List<Component> TypeDecl.allComponents() { + List<Component> result = new ArrayList<>(); + getComponentList().forEach(result::add); + if (hasSuperType()) { + mergeComponentLists(result, getSuperType().allComponents()); + } + return result; + } + + /** Returns same as allComponents() but for each component the containingTypeDecl will compute to this component */ + syn nta JastAddList<Component> TypeDecl.allComponentsAsOwnedByMe() { + JastAddList<Component> result = new JastAddList<>(); + for (Component comp : allComponents()) { + result.add(comp.treeCopy()); + } + return result; + } + + public static void TypeDecl.mergeComponentLists(List<Component> subTypeResult, List<Component> superTypeResult) { + for (int i = superTypeResult.size() - 1; i >= 0; i--) { + Component superTypeComponent = superTypeResult.get(i); + if (subTypeResult.stream().noneMatch(subTypeComponent -> subTypeComponent.matches(superTypeComponent))) { + subTypeResult.add(0, superTypeComponent); + } + } + } + + syn boolean Component.matches(Component other) = false; + eq TokenComponent.matches(Component other) { + if (!other.isTokenComponent()) { return false; } + return getName().equals(other.getName()) && getJavaTypeUse().prettyPrint().equals(other.asTokenComponent().getJavaTypeUse().prettyPrint()); + } + eq TypeComponent.matches(Component other) { + return getName().equals(other.getName()) && getTypeDecl().equals(other.asTypeComponent().getTypeDecl()); + } + eq NormalComponent.matches(Component other) { + if (!other.isTypeComponent() || !other.asTypeComponent().isNormalComponent()) { return false; } + return super.matches(other); + } + eq ListComponent.matches(Component other) { + if (!other.isTypeComponent() || !other.asTypeComponent().isListComponent()) { return false; } + return super.matches(other); + } + eq OptComponent.matches(Component other) { + if (!other.isTypeComponent() || !other.asTypeComponent().isOptComponent()) { return false; } + return super.matches(other); + } } diff --git a/ragconnect.tests/src/test/01-input/passing/README.md b/ragconnect.tests/src/test/01-input/passing/README.md new file mode 100644 index 0000000000000000000000000000000000000000..15afc42285d80e7e205777af6333162100cb36dd --- /dev/null +++ b/ragconnect.tests/src/test/01-input/passing/README.md @@ -0,0 +1,4 @@ +# Passing + +This directory contains use case for RagConnect, that just need to pass compiling (no dedicated Java test). + diff --git a/ragconnect.tests/src/test/01-input/passing/inheritance/Test.connect b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..3b4690cd05838159de6e5c9be0e47808ca25f9ab --- /dev/null +++ b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.connect @@ -0,0 +1,18 @@ +// both directions for SpecialA1 +send SpecialA1.Input ; +receive SpecialA1.Input ; + +send SpecialA1.Many ; +receive SpecialA1.Many ; + +send SpecialA1.Maybe ; +receive SpecialA1.Maybe ; + +send SpecialA1.B ; +receive SpecialA1.B ; + +// only one direction for SpecialA2 +send SpecialA2.Input ; +send SpecialA2.Many ; +send SpecialA2.Maybe ; +send SpecialA2.B ; diff --git a/ragconnect.tests/src/test/01-input/passing/inheritance/Test.relast b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..7de55c8642cbb307c615e7abe78833f7e7ff1a6c --- /dev/null +++ b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.relast @@ -0,0 +1,5 @@ +Root ::= A ; +A ::= <Input:String> Many:B* [Maybe:B] B ; +SpecialA1 : A ; +SpecialA2 : A ; +B ::= ; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractCompilerTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractCompilerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..002f9815f9eb09b8b41d533c9f01d6dcc5f0e98f --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractCompilerTest.java @@ -0,0 +1,61 @@ +package org.jastadd.ragconnect.tests; + +import org.jastadd.ragconnect.tests.utils.TestUtils; +import org.junit.jupiter.api.BeforeEach; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.jastadd.ragconnect.tests.utils.TestUtils.readFile; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * TODO: Add description. + * + * @author rschoene - Initial contribution + */ +public abstract class AbstractCompilerTest extends RagConnectTest { + + protected abstract String getDirectoryName(); + + protected String getOutputDirectory() { + return TestUtils.OUTPUT_DIRECTORY_PREFIX + getDirectoryName(); + } + + protected abstract String getDefaultGrammarName(); + + @BeforeEach + public void ensureOutputDirectory() { + File outputDirectory = new File(getOutputDirectory()); + assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir()); + } + + protected Path test(String inputDirectoryName, int expectedReturnValue, String rootNode, String... connectNames) throws IOException { + String grammarFile = Paths.get(inputDirectoryName, getDefaultGrammarName() + ".relast").toString(); + List<String> connectFiles = Arrays.stream(connectNames) + .map(connectName -> Paths.get(inputDirectoryName,connectName + ".connect").toString()) + .collect(Collectors.toList()); + return TestUtils.runCompiler(grammarFile, connectFiles, rootNode, + getDirectoryName(), expectedReturnValue); + } + + protected void testAndCompare(String inputDirectoryName, String expectedName, String rootNode, String... connectNames) throws IOException { + Path outPath = test(inputDirectoryName, 1, rootNode, connectNames); + + final String startOfErrorsPattern = "Errors:\n"; + String out = readFile(outPath, Charset.defaultCharset()); + assertThat(out).contains(startOfErrorsPattern); + out = out.substring(out.indexOf(startOfErrorsPattern) + startOfErrorsPattern.length()); + + TestUtils.assertLinesMatch(getDirectoryName(), expectedName, out); + + logger.info("ragconnect for " + expectedName + " returned:\n{}", out); + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java index 4853b6a7e62f6e71878b37700a72fe2515655200..7f7e0cb2fbbaba2f6ce2995d9866ba10142e2039 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java @@ -1,65 +1,33 @@ package org.jastadd.ragconnect.tests; -import org.jastadd.ragconnect.tests.utils.TestUtils; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.io.File; import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.jastadd.ragconnect.tests.utils.TestUtils.readFile; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test error messages. * * @author rschoene - Initial contribution */ -public class ErrorsTest extends RagConnectTest { - - private static final String ERROR_DIRECTORY = "errors/"; - private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY; +public class ErrorsTest extends AbstractCompilerTest { - private static final String DEFAULT_GRAMMAR_NAME = "Errors"; + @Override + protected String getDirectoryName() { + return "errors"; + } - @BeforeAll - public static void createOutputDirectory() { - File outputDirectory = new File(OUTPUT_DIRECTORY); - assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir()); + @Override + protected String getDefaultGrammarName() { + return "Errors"; } @Test void testStandardErrors() throws IOException { - test("Standard", "A", "Standard"); + testAndCompare(getDirectoryName(), "Standard", "A", "Standard"); } @Test void testTwoPartsErrors() throws IOException { - test("Part", "A", "Part1", "Part2"); + testAndCompare(getDirectoryName(), "Part", "A", "Part1", "Part2"); } - - @SuppressWarnings("SameParameterValue") - private void test(String expectedName, String rootNode, String... connectNames) throws IOException { - String grammarFile = ERROR_DIRECTORY + DEFAULT_GRAMMAR_NAME + ".relast"; - List<String> connectFiles = Arrays.stream(connectNames) - .map(connectName -> ERROR_DIRECTORY + connectName + ".connect") - .collect(Collectors.toList()); - Path outPath = TestUtils.runCompiler(grammarFile, connectFiles, rootNode, ERROR_DIRECTORY, 1); - - final String startOfErrorsPattern = "Errors:\n"; - String out = readFile(outPath, Charset.defaultCharset()); - assertThat(out).contains(startOfErrorsPattern); - out = out.substring(out.indexOf(startOfErrorsPattern) + startOfErrorsPattern.length()); - - TestUtils.assertLinesMatch(ERROR_DIRECTORY, expectedName, out); - - logger.info("ragconnect for " + expectedName + " returned:\n{}", out); - } - } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/PassingTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/PassingTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5829c21d41f8ee0601a5218c33bb3d7165ae457b --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/PassingTest.java @@ -0,0 +1,33 @@ +package org.jastadd.ragconnect.tests; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Paths; + +/** + * Use cases that just need to compile. + * + * @author rschoene - Initial contribution + */ +public class PassingTest extends AbstractCompilerTest { + @Override + protected String getDirectoryName() { + return "passing"; + } + + @Override + protected String getDefaultGrammarName() { + return "Test"; + } + + protected void run(String inputDirectoryName, String rootNode) throws IOException { + super.test(Paths.get("passing", inputDirectoryName).toString(), + 0, rootNode, getDefaultGrammarName()); + } + + @Test + public void testInheritance() throws IOException { + run("inheritance", "Root"); + } +}