diff --git a/ragconnect.base/src/main/jastadd/Errors.jrag b/ragconnect.base/src/main/jastadd/Errors.jrag index 51f8b03b18e9d1eb18079fbaff0ae27632d17267..922fbf3dda85efa883706c684fe63a559bc65684 100644 --- a/ragconnect.base/src/main/jastadd/Errors.jrag +++ b/ragconnect.base/src/main/jastadd/Errors.jrag @@ -1,6 +1,6 @@ aspect Errors { - coll Set<ErrorMessage> RagConnect.errors() - [new TreeSet<ErrorMessage>()] + coll Set<CompilerMessage> RagConnect.errors() + [new TreeSet<CompilerMessage>()] root RagConnect; EndpointDefinition contributes error("Endpoint definition already defined for " + parentTypeName() + "." + entityName()) @@ -74,66 +74,8 @@ aspect ErrorHelpers { } return null; } -} - -aspect ErrorMessage { - public class ErrorMessage implements Comparable<ErrorMessage> { - private final ASTNode node; - private final String filename; - private final int line; - private final int col; - private final String message; - - public ErrorMessage(ASTNode node, String message) { - this.node = node; - this.filename = node.containedFileName(); - this.line = node.getStartLine(); - this.col = node.getStartColumn(); - this.message = message; - } - - public ASTNode getNode() { - return node; - } - - public int getLine() { - return line; - } - - public int getCol() { - return col; - } - - public String getMessage() { - return message; - } - - public String toString() { - return filename + " Line " + line + ", column " + col + ": " + message; - } - - @Override - public int compareTo(ErrorMessage err) { - int n = filename.compareTo(err.filename); - if (n != 0) { - return n; - } - - n = line - err.line; - if (n != 0) { - return n; - } - - n = col - err.col; - if (n != 0) { - return n; - } - - return message.compareTo(err.message); - } - } - protected ErrorMessage ASTNode.error(String message) { - return new ErrorMessage(this, message); + protected CompilerMessage ASTNode.error(String message) { + return new CompilerMessage(this, message); } } diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast index 5037cff1ffa0a7c803ce0d74207637417b1afbda..cf12d73202b144240ed929ba499d99a8e797aa89 100644 --- a/ragconnect.base/src/main/jastadd/RagConnect.relast +++ b/ragconnect.base/src/main/jastadd/RagConnect.relast @@ -39,5 +39,6 @@ Configuration ::= <JastAddList:String> <JastAddOpt:String> <IncrementalOptionActive:boolean> +<CacheAllOptionActive:boolean> <ExperimentalJastAdd329:boolean>; rel Configuration.RootNode -> TypeDecl ; diff --git a/ragconnect.base/src/main/jastadd/Util.jadd b/ragconnect.base/src/main/jastadd/Util.jadd index 31b15ba693b320a4cb502ef3034ea036979bf05b..174f8cdb9e40db6f3b1edef519b31daade2c1bba 100644 --- a/ragconnect.base/src/main/jastadd/Util.jadd +++ b/ragconnect.base/src/main/jastadd/Util.jadd @@ -6,4 +6,60 @@ aspect Util { } protected T JastAddList.firstChild() { return getChild(0); } protected T JastAddList.lastChild() { return getChild(getNumChild() - 1); } + + public class CompilerMessage implements Comparable<CompilerMessage> { + private final ASTNode node; + private final String filename; + private final int line; + private final int col; + private final String message; + + public CompilerMessage(ASTNode node, String message) { + this.node = node; + this.filename = node.containedFileName(); + this.line = node.getStartLine(); + this.col = node.getStartColumn(); + this.message = message; + } + + public ASTNode getNode() { + return node; + } + + public int getLine() { + return line; + } + + public int getCol() { + return col; + } + + public String getMessage() { + return message; + } + + public String toString() { + return filename + " Line " + line + ", column " + col + ": " + message; + } + + @Override + public int compareTo(CompilerMessage other) { + int n = filename.compareTo(other.filename); + if (n != 0) { + return n; + } + + n = line - other.line; + if (n != 0) { + return n; + } + + n = col - other.col; + if (n != 0) { + return n; + } + + return message.compareTo(other.message); + } + } } diff --git a/ragconnect.base/src/main/jastadd/Warnings.jrag b/ragconnect.base/src/main/jastadd/Warnings.jrag new file mode 100644 index 0000000000000000000000000000000000000000..351fb19eac5a41930c7e8ba64aa6b151bdcf7d78 --- /dev/null +++ b/ragconnect.base/src/main/jastadd/Warnings.jrag @@ -0,0 +1,23 @@ +aspect Warnings { + coll Set<CompilerMessage> RagConnect.warnings() + [new TreeSet<CompilerMessage>()] + root RagConnect; + + DependencyDefinition contributes warning("Dependency definition should not be used if incremental evaluation is enabled!") + when ragconnect().configIncrementalOptionActive() + to RagConnect.warnings(); + + EndpointDefinition contributes warning("No dependency definitions are given, and incremental evaluation is disabled. No messages will be sent for this!") + when getSend() && ragconnect().allDependencyDefinitionList().isEmpty() && !ragconnect().configIncrementalOptionActive() + to RagConnect.warnings(); + + ConnectSpecificationFile contributes warning("Incremental evaluation is disabled, but cache=all is set. This might lead to no messages sent!") + when !ragconnect().configIncrementalOptionActive() && ragconnect().getConfiguration().getCacheAllOptionActive() + to RagConnect.warnings(); +} + +aspect WarningHelpers { + protected CompilerMessage ASTNode.warning(String message) { + return new CompilerMessage(this, message); + } +} diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java index f671349843dddf5e81099660365668fbf5ae4c66..b70f3a715c1fc2f6fa11fbc568027bcaf9df042b 100644 --- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java +++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java @@ -83,10 +83,18 @@ public class Compiler extends AbstractCompiler { throw new CompilerException("Failed to parse all files", re); } + if (!ragConnect.warnings().isEmpty()) { + StringBuilder sb = new StringBuilder("Warnings:\n"); + for (CompilerMessage message : ragConnect.warnings()) { + sb.append(message).append("\n"); + } + System.err.println(sb); + } + if (!ragConnect.errors().isEmpty()) { StringBuilder sb = new StringBuilder("Errors:\n"); - for (ErrorMessage e : ragConnect.errors()) { - sb.append(e).append("\n"); + for (CompilerMessage message : ragConnect.errors()) { + sb.append(message).append("\n"); } System.err.println(sb); System.exit(1); @@ -306,6 +314,9 @@ public class Compiler extends AbstractCompiler { System.out.println("ragConnect.getConfiguration().IncrementalOptionActive = " + incrementalOptionActive); } + // reuse "--cache=all" option of JastAdd + ragConnect.getConfiguration().setCacheAllOptionActive(this.getConfiguration().cacheAll()); + // reuse "--List" and "--Opt" options of JastAdd ragConnect.getConfiguration().setJastAddList(this.getConfiguration().listType()); ragConnect.getConfiguration().setJastAddOpt(this.getConfiguration().optType()); diff --git a/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoInc.expected b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoInc.expected new file mode 100644 index 0000000000000000000000000000000000000000..9ed8fcfcf83026f52c4824941f369c05b677af39 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoInc.expected @@ -0,0 +1 @@ +TestNoDependencies.connect Line 1, column 1: No dependency definitions are given, and incremental evaluation is disabled. No messages will be sent for this! diff --git a/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoIncAndCacheAll.expected b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoIncAndCacheAll.expected new file mode 100644 index 0000000000000000000000000000000000000000..e2743af9f787c4f047407b0f47814bcc752fa856 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoIncAndCacheAll.expected @@ -0,0 +1,2 @@ +TestNoDependencies.connect Line 1, column 1: Incremental evaluation is disabled, but cache=all is set. This might lead to no messages sent! +TestNoDependencies.connect Line 1, column 1: No dependency definitions are given, and incremental evaluation is disabled. No messages will be sent for this! diff --git a/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndInc.expected b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndInc.expected new file mode 100644 index 0000000000000000000000000000000000000000..15fab0001ee36aef1e9ba34db195f0e83db9066e --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndInc.expected @@ -0,0 +1 @@ +TestSomeDependencies.connect Line 3, column 1: Dependency definition should not be used if incremental evaluation is enabled! diff --git a/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndNoIncAndCacheAll.expected b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndNoIncAndCacheAll.expected new file mode 100644 index 0000000000000000000000000000000000000000..732463caf9bfabae3cefe5d2555cb8cc6b85870b --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndNoIncAndCacheAll.expected @@ -0,0 +1 @@ +TestSomeDependencies.connect Line 1, column 1: Incremental evaluation is disabled, but cache=all is set. This might lead to no messages sent! diff --git a/ragconnect.tests/src/test/01-input/warnings/Test.jrag b/ragconnect.tests/src/test/01-input/warnings/Test.jrag new file mode 100644 index 0000000000000000000000000000000000000000..7a1b4984f520b6ca5d39317f258c2b83a0ba72e0 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/Test.jrag @@ -0,0 +1,3 @@ +aspect Computation { + syn String Root.getOutput() = getInput() + "out"; +} diff --git a/ragconnect.tests/src/test/01-input/warnings/Test.relast b/ragconnect.tests/src/test/01-input/warnings/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..5acb9e5493771e0115fcc5d22e0e84ea9d7d4da7 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/Test.relast @@ -0,0 +1 @@ +Root ::= <Input> /<Output>/ ; diff --git a/ragconnect.tests/src/test/01-input/warnings/TestNoDependencies.connect b/ragconnect.tests/src/test/01-input/warnings/TestNoDependencies.connect new file mode 100644 index 0000000000000000000000000000000000000000..e2efbebcc85c375c23feba62cab48617a77b8a83 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/TestNoDependencies.connect @@ -0,0 +1 @@ +send Root.Output ; diff --git a/ragconnect.tests/src/test/01-input/warnings/TestSomeDependencies.connect b/ragconnect.tests/src/test/01-input/warnings/TestSomeDependencies.connect new file mode 100644 index 0000000000000000000000000000000000000000..30409ea90623fd59ce1efacf4b64a1f04aab37e0 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/warnings/TestSomeDependencies.connect @@ -0,0 +1,3 @@ +send Root.Output ; + +Root.Output canDependOn Root.Input as AttributeDependency; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java index 63ee6de3ef515e46768a9dc9bfc75477a451bfd5..d774d91d91fefab6b86d0b0bdf2f2c8e4e72d729 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java @@ -1,6 +1,5 @@ package org.jastadd.ragconnect.tests; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -10,9 +9,7 @@ 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.Collections; import java.util.List; import java.util.stream.Collectors; @@ -20,10 +17,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.jastadd.ragconnect.tests.TestUtils.readFile; import static org.junit.jupiter.api.Assertions.assertTrue; +/** + * Test error messages. + * + * @author rschoene - Initial contribution + */ public class Errors { private static final Logger logger = LoggerFactory.getLogger(Errors.class); - private static final String FILENAME_PATTERN = "$FILENAME"; private static final String ERROR_DIRECTORY = "errors/"; private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY; @@ -37,12 +38,12 @@ public class Errors { @Test void testStandardErrors() throws IOException { - test("Standard", "A","Standard"); + test("Standard", "A", "Standard"); } @Test void testTwoPartsErrors() throws IOException { - test("Part", "A","Part1", "Part2"); + test("Part", "A", "Part1", "Part2"); } @SuppressWarnings("SameParameterValue") @@ -58,18 +59,7 @@ public class Errors { assertThat(out).contains(startOfErrorsPattern); out = out.substring(out.indexOf(startOfErrorsPattern) + startOfErrorsPattern.length()); - Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX) - .resolve(ERROR_DIRECTORY) - .resolve(expectedName + ".expected"); - String expected = readFile(expectedPath, Charset.defaultCharset()); - List<String> outList = Arrays.asList(out.split("\n")); - Collections.sort(outList); - List<String> expectedList = Arrays.stream(expected.split("\n")) - .sorted() - .filter(s -> !s.isEmpty() && !s.startsWith("//")) - .collect(Collectors.toList()); - - Assertions.assertLinesMatch(expectedList, outList); + 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/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java index 655d61e1710014d37c7aeaa3fdcc140995568f9d..3457ce0c69123ebbc31853e69af5e84b5ddf827b 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java @@ -66,7 +66,7 @@ public class TestUtils { return 1883; } - public static Path runCompiler(String grammarFile, Iterable<String> connectFiles, String rootNode, String outputDirectory, int expectedReturnValue) { + public static Path runCompiler(String grammarFile, Iterable<String> connectFiles, String rootNode, String outputDirectory, int expectedReturnValue, String... additionalArguments) { assertThat(connectFiles).isNotEmpty(); @@ -84,6 +84,7 @@ public class TestUtils { add(INPUT_DIRECTORY_PREFIX + grammarFile); }}; connectFiles.forEach(connectFile -> args.add(INPUT_DIRECTORY_PREFIX + connectFile)); + args.addAll(Arrays.asList(additionalArguments)); int returnValue = exec(Compiler.class, args.toArray(new String[0]), outPath.toFile()); Assertions.assertEquals(expectedReturnValue, returnValue, "RagConnect did not return with value " + expectedReturnValue); @@ -93,6 +94,21 @@ public class TestUtils { return outPath; } + public static void assertLinesMatch(String directory, String expectedName, String out) throws IOException { + Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX) + .resolve(directory) + .resolve(expectedName + ".expected"); + String expected = readFile(expectedPath, Charset.defaultCharset()); + List<String> outList = Arrays.asList(out.split("\n")); + Collections.sort(outList); + List<String> expectedList = Arrays.stream(expected.split("\n")) + .sorted() + .filter(s -> !s.isEmpty() && !s.startsWith("//")) + .collect(Collectors.toList()); + + Assertions.assertLinesMatch(expectedList, outList); + } + private static void ensureCreated(Path directory) { File directoryFile = directory.toFile(); if (directoryFile.exists() && directoryFile.isDirectory()) { diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java new file mode 100644 index 0000000000000000000000000000000000000000..b0840d6d2d6d35ed50f759d61198b2d25586d800 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java @@ -0,0 +1,94 @@ +package org.jastadd.ragconnect.tests; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.jastadd.ragconnect.tests.TestUtils.readFile; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test warning messages. + * + * @author rschoene - Initial contribution + */ +public class Warnings { + + private static final Logger logger = LoggerFactory.getLogger(Warnings.class); + private static final String WARNING_DIRECTORY = "warnings/"; + private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + WARNING_DIRECTORY; + + private static final String DEFAULT_GRAMMAR_NAME = "Test"; + + private static final String OPTION_INCREMENTAL_PARAM = "--incremental=param"; + private static final String OPTION_TRACE_FLUSH = "--tracing=flush"; + private static final String OPTION_CACHE_ALL = "--cache=all"; + + @BeforeAll + public static void createOutputDirectory() { + File outputDirectory = new File(OUTPUT_DIRECTORY); + assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir()); + } + + @Test + public void testNoDependenciesAndInc() throws IOException { + // pass "null" as expectedName means that no warnings are expected + test(null, "TestNoDependencies", OPTION_INCREMENTAL_PARAM, OPTION_TRACE_FLUSH); + } + + @Test + public void testSomeDependenciesAndInc() throws IOException { + test("SomeDependenciesAndInc", "TestSomeDependencies", + OPTION_INCREMENTAL_PARAM, OPTION_TRACE_FLUSH); + } + + @Test + public void testNoDependenciesAndNoInc() throws IOException { + test("NoDependenciesAndNoInc", "TestNoDependencies"); + } + + @Test + public void testNoIncAndCacheAll() throws IOException { + test("NoDependenciesAndNoIncAndCacheAll", "TestNoDependencies", OPTION_CACHE_ALL); + test("SomeDependenciesAndNoIncAndCacheAll", "TestSomeDependencies", OPTION_CACHE_ALL); + } + + private void test(String expectedName, String connectName, String... additionalArguments) throws IOException { + String grammarFile = WARNING_DIRECTORY + DEFAULT_GRAMMAR_NAME + ".relast"; + List<String> connectFiles = Collections.singletonList(WARNING_DIRECTORY + connectName + ".connect"); + Path outPath = TestUtils.runCompiler( + grammarFile, connectFiles, "Root", WARNING_DIRECTORY, 0, additionalArguments + ); + + String out = readFile(outPath, Charset.defaultCharset()); + final boolean expectWarnings = expectedName != null; + final String startOfWarningsPattern = "Warnings:\n"; + if (expectWarnings) { + assertThat(out).contains(startOfWarningsPattern); + } else { + assertThat(out).doesNotContain(startOfWarningsPattern); + } + + final String startOfErrorsPattern = "Errors:\n"; + assertThat(out).doesNotContain(startOfErrorsPattern); + + if (!expectWarnings) { + return; + } + + out = out.substring(out.indexOf(startOfWarningsPattern) + startOfWarningsPattern.length()); + + TestUtils.assertLinesMatch(WARNING_DIRECTORY, expectedName, out); + + logger.info("ragconnect for " + expectedName + " returned:\n{}", out); + } +}