diff --git a/cli/base/src/main/java/org/lflang/cli/CliBase.java b/cli/base/src/main/java/org/lflang/cli/CliBase.java index 0d098ec758..0b61fa767b 100644 --- a/cli/base/src/main/java/org/lflang/cli/CliBase.java +++ b/cli/base/src/main/java/org/lflang/cli/CliBase.java @@ -27,9 +27,9 @@ import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; -import org.lflang.ErrorReporter; import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; +import org.lflang.MessageReporter; import org.lflang.util.FileUtil; import picocli.CommandLine; import picocli.CommandLine.ArgGroup; @@ -88,7 +88,7 @@ static class MutuallyExclusive { @Inject protected ReportingBackend reporter; /** Used to report error messages at the end. */ - @Inject protected ErrorReporter errorReporter; + @Inject protected MessageReporter messageReporter; /** IO context of this run. */ @Inject protected Io io; diff --git a/cli/base/src/main/java/org/lflang/cli/LFStandaloneModule.java b/cli/base/src/main/java/org/lflang/cli/LFStandaloneModule.java index abf01bfcd3..99dfd9a0b8 100644 --- a/cli/base/src/main/java/org/lflang/cli/LFStandaloneModule.java +++ b/cli/base/src/main/java/org/lflang/cli/LFStandaloneModule.java @@ -34,8 +34,8 @@ import org.eclipse.emf.ecore.EValidator; import org.eclipse.emf.ecore.impl.EValidatorRegistryImpl; import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.lflang.ErrorReporter; import org.lflang.LFRuntimeModule; +import org.lflang.MessageReporter; /** * Module that is only available when running LFC as a standalone program. @@ -56,7 +56,7 @@ public LFStandaloneModule(ReportingBackend helper, Io io) { @Override public void configure(Binder binder) { - binder.bind(ErrorReporter.class).to(StandaloneErrorReporter.class); + binder.bind(MessageReporter.class).to(StandaloneMessageReporter.class); binder.bind(ReportingBackend.class).toInstance(helper); binder.bind(Io.class).toInstance(io); binder.bind(ValidationMessageAcceptor.class).to(StandaloneIssueAcceptor.class); diff --git a/cli/base/src/main/java/org/lflang/cli/StandaloneErrorReporter.java b/cli/base/src/main/java/org/lflang/cli/StandaloneMessageReporter.java similarity index 52% rename from cli/base/src/main/java/org/lflang/cli/StandaloneErrorReporter.java rename to cli/base/src/main/java/org/lflang/cli/StandaloneMessageReporter.java index 83e58eb15c..1ecffbdedb 100644 --- a/cli/base/src/main/java/org/lflang/cli/StandaloneErrorReporter.java +++ b/cli/base/src/main/java/org/lflang/cli/StandaloneMessageReporter.java @@ -30,72 +30,42 @@ import com.google.inject.Inject; import java.nio.file.Path; import org.eclipse.emf.ecore.EObject; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.xtext.diagnostics.Severity; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporterBase; +import org.lflang.generator.Range; /** * An error reporter that forwards all messages to an {@link IssueCollector}. They'll be sorted out * later. */ -public class StandaloneErrorReporter implements ErrorReporter { +public class StandaloneMessageReporter extends MessageReporterBase { @Inject private StandaloneIssueAcceptor issueAcceptor; - private String reportWithNode(String message, Severity severity, EObject obj) { - issueAcceptor.accept(severity, message, obj, null, 0, null); - return message; - } - - private String reportSimpleFileCtx(String message, Severity severity, Integer line, Path path) { - LfIssue issue = new LfIssue(message, severity, line, 1, line, 1, 0, path); - issueAcceptor.accept(issue); - // Return a string that can be inserted into the generated code. - return message; - } - - @Override - public String reportError(String message) { - return reportSimpleFileCtx(message, Severity.ERROR, null, null); - } - - @Override - public String reportWarning(String message) { - return reportSimpleFileCtx(message, Severity.WARNING, null, null); - } - - @Override - public String reportInfo(String message) { - return reportSimpleFileCtx(message, Severity.INFO, null, null); + static Severity convertSeverity(DiagnosticSeverity severity) { + return switch (severity) { + case Error -> Severity.ERROR; + case Warning -> Severity.WARNING; + case Information, Hint -> Severity.INFO; + }; } @Override - public String reportError(EObject obj, String message) { - return reportWithNode(message, Severity.ERROR, obj); + protected void reportOnNode(EObject node, DiagnosticSeverity severity, String message) { + issueAcceptor.accept(convertSeverity(severity), message, node, null, 0, null); } @Override - public String reportWarning(EObject obj, String message) { - return reportWithNode(message, Severity.WARNING, obj); - } - - @Override - public String reportInfo(EObject obj, String message) { - return reportWithNode(message, Severity.INFO, obj); - } - - @Override - public String reportError(Path file, Integer line, String message) { - return reportSimpleFileCtx(message, Severity.ERROR, line, file); - } - - @Override - public String reportWarning(Path file, Integer line, String message) { - return reportSimpleFileCtx(message, Severity.WARNING, line, file); + protected void report(Path path, Range range, DiagnosticSeverity severity, String message) { + LfIssue issue = new LfIssue(message, convertSeverity(severity), path, range); + issueAcceptor.accept(issue); } @Override - public String reportInfo(Path file, Integer line, String message) { - return reportSimpleFileCtx(message, Severity.INFO, line, file); + protected void reportWithoutPosition(DiagnosticSeverity severity, String message) { + LfIssue issue = new LfIssue(message, convertSeverity(severity), null, null); + issueAcceptor.accept(issue); } @Override diff --git a/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt b/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt index efdd0e731f..b8e94083cf 100644 --- a/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt +++ b/cli/base/src/main/kotlin/org/lflang/cli/ReportingUtil.kt @@ -27,6 +27,7 @@ package org.lflang.cli import com.google.inject.Inject import com.google.inject.Singleton import org.eclipse.xtext.diagnostics.Severity +import org.lflang.generator.Range import java.io.IOException import java.io.PrintStream import java.lang.Error @@ -135,6 +136,21 @@ data class LfIssue( val file: Path? ) : Comparable { + constructor( + message: String, + severity: Severity, + path: Path?, + range: Range? + ) : this( + message, severity, + line = range?.startInclusive?.oneBasedLine, + column = range?.startInclusive?.oneBasedColumn, + endLine = range?.endExclusive?.oneBasedLine, + endColumn = range?.endExclusive?.oneBasedColumn, + length = null, + file = path + ) + override operator fun compareTo(other: LfIssue): Int = issueComparator.compare(this, other) diff --git a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java index 19869f9394..021fb1c9fa 100644 --- a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java +++ b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java @@ -181,7 +181,7 @@ private void invokeGenerator(List files, Path root, Properties properties) properties, resource, this.fileAccess, - fileConfig -> errorReporter); + fileConfig -> messageReporter); try { this.generator.generate(resource, this.fileAccess, context); diff --git a/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java b/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java index 4d6911f6c7..9270d9ea82 100644 --- a/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java +++ b/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java @@ -16,7 +16,7 @@ import org.junit.jupiter.api.Test; import org.lflang.Target; import org.lflang.generator.IntegratedBuilder; -import org.lflang.generator.LanguageServerErrorReporter; +import org.lflang.generator.LanguageServerMessageReporter; import org.lflang.tests.LFTest; import org.lflang.tests.LfInjectedTestBase; import org.lflang.tests.TestBase; @@ -150,7 +150,7 @@ private void checkDiagnostics( Random random) throws IOException { MockLanguageClient client = new MockLanguageClient(); - LanguageServerErrorReporter.setClient(client); + LanguageServerMessageReporter.setClient(client); for (LFTest test : selectTests(target, random)) { client.clearDiagnostics(); if (alterer != null) { diff --git a/core/src/main/java/org/lflang/DefaultErrorReporter.java b/core/src/main/java/org/lflang/DefaultErrorReporter.java deleted file mode 100644 index 2698d2b582..0000000000 --- a/core/src/main/java/org/lflang/DefaultErrorReporter.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.lflang; - -import java.nio.file.Path; -import org.eclipse.emf.ecore.EObject; - -/** Simple implementation of the ErrorReport interface that simply prints to standard out. */ -public class DefaultErrorReporter implements ErrorReporter { - - private boolean errorsOccurred = false; - - private String println(String s) { - System.out.println(s); - return s; - } - - @Override - public String reportError(String message) { - errorsOccurred = true; - return println("ERROR: " + message); - } - - @Override - public String reportError(EObject object, String message) { - errorsOccurred = true; - return println("ERROR: " + message); - } - - @Override - public String reportError(Path file, Integer line, String message) { - errorsOccurred = true; - return println("ERROR: " + message); - } - - @Override - public String reportWarning(String message) { - return println("WARNING: " + message); - } - - @Override - public String reportInfo(String message) { - return println("INFO: " + message); - } - - @Override - public String reportWarning(EObject object, String message) { - return println("WARNING: " + message); - } - - @Override - public String reportInfo(EObject object, String message) { - return println("INFO: " + message); - } - - @Override - public String reportWarning(Path file, Integer line, String message) { - return println("WARNING: " + message); - } - - @Override - public String reportInfo(Path file, Integer line, String message) { - return println("INFO: " + message); - } - - @Override - public boolean getErrorsOccurred() { - return errorsOccurred; - } -} diff --git a/core/src/main/java/org/lflang/DefaultMessageReporter.java b/core/src/main/java/org/lflang/DefaultMessageReporter.java new file mode 100644 index 0000000000..9ea3ede28a --- /dev/null +++ b/core/src/main/java/org/lflang/DefaultMessageReporter.java @@ -0,0 +1,33 @@ +package org.lflang; + +import java.nio.file.Path; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.lflang.generator.Range; + +/** Simple implementation of the ErrorReport interface that simply prints to standard out. */ +public class DefaultMessageReporter extends MessageReporterBase implements MessageReporter { + + private void println(String s) { + System.out.println(s); + } + + @Override + protected void report(Path path, Range range, DiagnosticSeverity severity, String message) { + reportWithoutPosition(severity, message); + } + + @Override + protected void reportOnNode(EObject node, DiagnosticSeverity severity, String message) { + reportWithoutPosition(severity, message); + } + + @Override + protected void reportWithoutPosition(DiagnosticSeverity severity, String message) { + switch (severity) { + case Error -> println("ERROR: " + message); + case Warning -> println("WARNING: " + message); + case Information, Hint -> println("INFO: " + message); + } + } +} diff --git a/core/src/main/java/org/lflang/ErrorReporter.java b/core/src/main/java/org/lflang/ErrorReporter.java deleted file mode 100644 index f7fe6dcb31..0000000000 --- a/core/src/main/java/org/lflang/ErrorReporter.java +++ /dev/null @@ -1,170 +0,0 @@ -package org.lflang; - -import java.nio.file.Path; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.lsp4j.DiagnosticSeverity; -import org.lflang.generator.Position; - -/** - * Interface for reporting errors. - * - * @author Edward A. Lee - * @author Marten Lohstroh - * @author Christian Menard - */ -public interface ErrorReporter { - - /** - * Report an error. - * - * @param message The error message. - * @return a string that describes the error. - */ - String reportError(String message); - - /** - * Report a warning. - * - * @param message The warning message. - * @return a string that describes the warning. - */ - String reportWarning(String message); - - /** - * Report an informational message. - * - * @param message The message to report - * @return a string that describes the error - */ - String reportInfo(String message); - - /** - * Report an error on the specified parse tree object. - * - * @param object The parse tree object. - * @param message The error message. - * @return a string that describes the error. - */ - String reportError(EObject object, String message); - - /** - * Report a warning on the specified parse tree object. - * - * @param object The parse tree object. - * @param message The error message. - * @return a string that describes the warning. - */ - String reportWarning(EObject object, String message); - - /** - * Report an informational message on the specified parse tree object. - * - * @param object The parse tree object. - * @param message The informational message - * @return a string that describes the info - */ - String reportInfo(EObject object, String message); - - /** - * Report an error at the specified line within a file. - * - * @param message The error message. - * @param line The one-based line number to report at. - * @param file The file to report at. - * @return a string that describes the error. - */ - String reportError(Path file, Integer line, String message); - - /** - * Report a warning at the specified line within a file. - * - * @param message The error message. - * @param line The one-based line number to report at. - * @param file The file to report at. - * @return a string that describes the warning. - */ - String reportWarning(Path file, Integer line, String message); - - /** - * Report an informational message at the specified line within a file. - * - * @param file The file to report at. - * @param line The one-based line number to report at. - * @param message The error message. - * @return - */ - String reportInfo(Path file, Integer line, String message); - - /** - * Report a message of severity {@code severity}. - * - * @param file The file to which the message pertains, or {@code null} if the file is unknown. - * @param severity the severity of the message - * @param message the message to send to the IDE - * @return a string that describes the diagnostic - */ - default String report(Path file, DiagnosticSeverity severity, String message) { - switch (severity) { - case Error: - return reportError(message); - case Warning: - case Hint: - case Information: - return reportInfo(message); - default: - return reportWarning(message); - } - } - - /** - * Report a message of severity {@code severity} that pertains to line {@code line} of an LF - * source file. - * - * @param file The file to which the message pertains, or {@code null} if the file is unknown. - * @param severity the severity of the message - * @param message the message to send to the IDE - * @param line the one-based line number associated with the message - * @return a string that describes the diagnostic - */ - default String report(Path file, DiagnosticSeverity severity, String message, int line) { - switch (severity) { - case Error: - return reportError(file, line, message); - case Warning: - case Hint: - case Information: - return reportInfo(file, line, message); - default: - return reportWarning(file, line, message); - } - } - - /** - * Report a message of severity {@code severity} that pertains to the range [{@code startPos}, - * {@code endPos}) of an LF source file. - * - * @param file The file to which the message pertains, or {@code null} if the file is unknown. - * @param severity the severity of the message - * @param message the message to send to the IDE - * @param startPos the position of the first character of the range of interest - * @param endPos the position immediately AFTER the final character of the range of interest - * @return a string that describes the diagnostic - */ - default String report( - Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { - return report(file, severity, message, startPos.getOneBasedLine()); - } - - /** - * Check if errors where reported. - * - * @return true if errors where reported - */ - boolean getErrorsOccurred(); - - /** - * Clear error history, if exists. This is usually only the case for error markers in Epoch - * (Eclipse). - */ - default void clearHistory() {} -} diff --git a/core/src/main/java/org/lflang/LFRuntimeModule.java b/core/src/main/java/org/lflang/LFRuntimeModule.java index a01986dfb3..e69b413081 100644 --- a/core/src/main/java/org/lflang/LFRuntimeModule.java +++ b/core/src/main/java/org/lflang/LFRuntimeModule.java @@ -46,8 +46,8 @@ public Class bindISyntaxErrorMessageProvi } /** The error reporter. {@code org.lflang.lfc.LFStandaloneModule} overrides this binding. */ - public Class bindErrorReporter() { - return DefaultErrorReporter.class; + public Class bindErrorReporter() { + return DefaultMessageReporter.class; } @Override diff --git a/core/src/main/java/org/lflang/MessageReporter.java b/core/src/main/java/org/lflang/MessageReporter.java new file mode 100644 index 0000000000..949c708592 --- /dev/null +++ b/core/src/main/java/org/lflang/MessageReporter.java @@ -0,0 +1,125 @@ +package org.lflang; + +import java.nio.file.Path; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.lflang.generator.Position; +import org.lflang.generator.Range; + +/** + * Interface for reporting messages like errors or info. This interface is a staged builder: first + * call one of the {@code at} methods to specify the position of the message, then use one of the + * report methods on the returned {@link Stage2} instance. + * + *

Examples: + * + *

{@code
+ * errorReporter.at(file, line).error("an error")
+ * errorReporter.at(node).warning("a warning reported on a node")
+ * errorReporter.nowhere().error("Some error that has no specific position")
+ * }
+ * + * @see MessageReporterBase + * @author Edward A. Lee + * @author Marten Lohstroh + * @author Christian Menard + * @author Clément Fournier + */ +public interface MessageReporter { + + /** Position the message on the given range in a given file (both must be non-null). */ + Stage2 at(Path file, Range range); + + /** Position the message on the given node (must be non-null). */ + Stage2 at(EObject object); + + /** + * Position the message in the file (non-null), at an unknown line. Implementations usually will + * report on the first line of the file. + */ + default Stage2 at(Path file) { + return at(file, 1); + } + + /** Position the message in the file (non-null), on the given line. */ + default Stage2 at(Path file, int line) { + return at(file, Position.fromOneBased(line, 1)); + } + + /** Position the message in the file, using a position object. */ + default Stage2 at(Path file, Position pos) { + return at(file, Range.degenerateRange(pos)); + } + + /** + * Specify that the message has no relevant position, ie it does not belong to a particular file. + */ + Stage2 nowhere(); + + /** + * Position the message in the given file. The line may be null. This is a convenience wrapper + * that calls either {@link #at(Path, int)} or {@link #at(Path)}. + */ + default Stage2 atNullableLine(Path file, Integer line) { + if (line != null) { + return at(file, line); + } + return at(file); + } + + /** + * Interface to report a message with a specific severity. This is returned by one of the + * positioning functions like {@link #at(Path)}. This instance holds an implicit position. + */ + interface Stage2 { + + /** + * Report an error. + * + * @param message The error message. + */ + default void error(String message) { + report(DiagnosticSeverity.Error, message); + } + + /** + * Report a warning. + * + * @param message The warning message. + */ + default void warning(String message) { + report(DiagnosticSeverity.Warning, message); + } + + /** + * Report an informational message. + * + * @param message The message to report + */ + default void info(String message) { + report(DiagnosticSeverity.Information, message); + } + + /** + * Report a message with the given severity. This is the only member that needs to be + * implemented. + * + * @param severity The severity + * @param message The message to report + */ + void report(DiagnosticSeverity severity, String message); + } + + /** + * Check if errors where reported. + * + * @return true if errors where reported + */ + boolean getErrorsOccurred(); + + /** + * Clear error history, if exists. This is usually only the case for error markers in Epoch + * (Eclipse). + */ + default void clearHistory() {} +} diff --git a/core/src/main/java/org/lflang/MessageReporterBase.java b/core/src/main/java/org/lflang/MessageReporterBase.java new file mode 100644 index 0000000000..0ee7b0286e --- /dev/null +++ b/core/src/main/java/org/lflang/MessageReporterBase.java @@ -0,0 +1,62 @@ +package org.lflang; + +import java.nio.file.Path; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.lflang.generator.Range; + +/** Base implementation of the {@link MessageReporter} interface. */ +public abstract class MessageReporterBase implements MessageReporter { + + private boolean errorsOccurred = false; + + protected MessageReporterBase() {} + + @Override + public boolean getErrorsOccurred() { + return errorsOccurred; + } + + @Override + public void clearHistory() { + errorsOccurred = false; + } + + /** A wrapper that takes care of setting the error flag if needed. */ + protected Stage2 wrap(Stage2 e) { + return (severity, message) -> { + if (severity == DiagnosticSeverity.Error) { + errorsOccurred = true; + } + e.report(severity, message); + }; + } + + @Override + public Stage2 at(Path file, Range range) { + return wrap((severity, message) -> report(file, range, severity, message)); + } + + @Override + public Stage2 at(EObject node) { + return wrap((severity, message) -> reportOnNode(node, severity, message)); + } + + @Override + public Stage2 nowhere() { + return wrap(this::reportWithoutPosition); + } + + // These methods are the terminal ones that are called when a call to + // Stage2#report is issued by a caller. + + /** Implementation of the reporting methods that use a path and range as position. */ + protected abstract void report( + Path path, Range range, DiagnosticSeverity severity, String message); + + /** Implementation of the reporting methods that use a node as position. */ + protected abstract void reportOnNode(EObject node, DiagnosticSeverity severity, String message); + + /** Implementation of the reporting methods for {@link #nowhere()}. */ + protected abstract void reportWithoutPosition(DiagnosticSeverity severity, String message); +} diff --git a/core/src/main/java/org/lflang/ModelInfo.java b/core/src/main/java/org/lflang/ModelInfo.java index ef7081878d..765cebb916 100644 --- a/core/src/main/java/org/lflang/ModelInfo.java +++ b/core/src/main/java/org/lflang/ModelInfo.java @@ -97,7 +97,7 @@ public class ModelInfo { * * @param model the model to analyze. */ - public void update(Model model, ErrorReporter reporter) { + public void update(Model model, MessageReporter reporter) { this.updated = true; this.model = model; this.instantiationGraph = new InstantiationGraph(model, true); @@ -131,7 +131,7 @@ public void update(Model model, ErrorReporter reporter) { checkCaseInsensitiveNameCollisions(model, reporter); } - public void checkCaseInsensitiveNameCollisions(Model model, ErrorReporter reporter) { + public void checkCaseInsensitiveNameCollisions(Model model, MessageReporter reporter) { var reactorNames = new HashSet<>(); var bad = new ArrayList<>(); for (var reactor : model.getReactors()) { @@ -144,8 +144,9 @@ public void checkCaseInsensitiveNameCollisions(Model model, ErrorReporter report .filter(it -> getName(it).toLowerCase().equals(badName)) .forEach( it -> - reporter.reportError( - it, "Multiple reactors have the same name up to case differences.")); + reporter + .at(it) + .error("Multiple reactors have the same name up to case differences.")); } } diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index fcb8c88a62..a61df711e9 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -71,13 +71,13 @@ public TargetConfig(TargetDecl target) { // FIXME: eliminate this constructor if * * @param cliArgs Arguments passed on the commandline. * @param target AST node of a target declaration. - * @param errorReporter An error reporter to report problems. + * @param messageReporter An error reporter to report problems. */ - public TargetConfig(Properties cliArgs, TargetDecl target, ErrorReporter errorReporter) { + public TargetConfig(Properties cliArgs, TargetDecl target, MessageReporter messageReporter) { this(target); if (target.getConfig() != null) { List pairs = target.getConfig().getPairs(); - TargetProperty.set(this, pairs != null ? pairs : List.of(), errorReporter); + TargetProperty.set(this, pairs != null ? pairs : List.of(), messageReporter); } if (cliArgs.containsKey("no-compile")) { this.noCompile = true; diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 923891ce66..f86f87ea2b 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -34,7 +34,6 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; -import org.eclipse.xtext.util.RuntimeIOException; import org.lflang.TargetConfig.DockerOptions; import org.lflang.TargetConfig.PlatformOptions; import org.lflang.TargetConfig.TracingOptions; @@ -471,7 +470,7 @@ public enum TargetProperty { String s = "Unidentified Platform Type, LF supports the following platform types: " + Arrays.asList(Platform.values()).toString(); - err.reportError(s); + err.at(entry).error(s); throw new AssertionError(s); } config.platformOptions.platform = p; @@ -717,8 +716,8 @@ else if (paths.size() == 1) { try { referencePath = FileUtil.toPath(value.eResource().getURI()).toAbsolutePath(); } catch (IllegalArgumentException e) { - err.reportError(value, "Invalid path? " + e.getMessage()); - throw new RuntimeIOException(e); + err.at(value).error("Invalid path? " + e.getMessage()); + throw e; } // we'll resolve relative paths to check that the files @@ -862,7 +861,7 @@ private interface PropertyParser { * Parse the given element into the given target config. May use the error reporter to report * format errors. */ - void parseIntoTargetConfig(TargetConfig config, Element element, ErrorReporter err); + void parseIntoTargetConfig(TargetConfig config, Element element, MessageReporter err); } public final PropertyGetter getter; @@ -940,7 +939,7 @@ public String getDisplayName() { * @param properties AST node that holds all the target properties. * @param err Error reporter on which property format errors will be reported */ - public static void set(TargetConfig config, List properties, ErrorReporter err) { + public static void set(TargetConfig config, List properties, MessageReporter err) { properties.forEach( property -> { TargetProperty p = forName(property.getName()); @@ -950,7 +949,7 @@ public static void set(TargetConfig config, List properties, Error try { p.setter.parseIntoTargetConfig(config, property.getValue(), err); } catch (InvalidLfSourceException e) { - err.reportError(e.getNode(), e.getProblem()); + err.at(e.getNode()).error(e.getProblem()); } } }); @@ -1001,7 +1000,7 @@ public static TargetDecl extractTargetDecl(Target target, TargetConfig config) { * properties originate. */ public static void update( - TargetConfig config, List properties, Path relativePath, ErrorReporter err) { + TargetConfig config, List properties, Path relativePath, MessageReporter err) { properties.forEach( property -> { TargetProperty p = forName(property.getName()); @@ -1044,7 +1043,7 @@ public static void updateOne( TargetConfig config, TargetProperty property, List properties, - ErrorReporter err) { + MessageReporter err) { properties.stream() .filter(p -> p.getName().equals(property.getDisplayName())) .findFirst() diff --git a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index b82b49461b..272b483607 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -115,7 +115,7 @@ import org.lflang.diagram.synthesis.util.ModeDiagrams; import org.lflang.diagram.synthesis.util.NamedInstanceUtil; import org.lflang.diagram.synthesis.util.ReactorIcons; -import org.lflang.diagram.synthesis.util.SynthesisErrorReporter; +import org.lflang.diagram.synthesis.util.SynthesisMessageReporter; import org.lflang.diagram.synthesis.util.UtilityExtensions; import org.lflang.generator.ActionInstance; import org.lflang.generator.ParameterInstance; @@ -312,7 +312,7 @@ public KNode transform(final Model model) { Reactor main = IterableExtensions.findFirst(model.getReactors(), _utilityExtensions::isMainOrFederated); if (main != null) { - ReactorInstance reactorInstance = new ReactorInstance(main, new SynthesisErrorReporter()); + ReactorInstance reactorInstance = new ReactorInstance(main, new SynthesisMessageReporter()); rootNode .getChildren() .addAll(createReactorNode(reactorInstance, true, null, null, new HashMap<>())); @@ -328,7 +328,7 @@ public KNode transform(final Model model) { for (Reactor reactor : model.getReactors()) { if (reactor == main) continue; ReactorInstance reactorInstance = - new ReactorInstance(reactor, new SynthesisErrorReporter()); + new ReactorInstance(reactor, new SynthesisMessageReporter()); reactorNodes.addAll( createReactorNode( reactorInstance, diff --git a/core/src/main/java/org/lflang/diagram/synthesis/util/SynthesisErrorReporter.java b/core/src/main/java/org/lflang/diagram/synthesis/util/SynthesisMessageReporter.java similarity index 55% rename from core/src/main/java/org/lflang/diagram/synthesis/util/SynthesisErrorReporter.java rename to core/src/main/java/org/lflang/diagram/synthesis/util/SynthesisMessageReporter.java index 5d229e015b..dea894d833 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/util/SynthesisErrorReporter.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/util/SynthesisMessageReporter.java @@ -29,66 +29,33 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.emf.ecore.EObject; -import org.lflang.ErrorReporter; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.lflang.MessageReporterBase; +import org.lflang.generator.Range; /** * @author Alexander Schulz-Rosengarten */ -public class SynthesisErrorReporter implements ErrorReporter { - - private boolean errorsOccurred = false; - - @Override - public String reportError(String message) { - errorsOccurred = true; - Klighd.log(new Status(IStatus.ERROR, SynthesisErrorReporter.class, message)); - return message; - } - - @Override - public String reportError(EObject object, String message) { - return reportError(message); - } - - @Override - public String reportError(Path file, Integer line, String message) { - return reportError(message); - } - - @Override - public String reportWarning(String message) { - Klighd.log(new Status(IStatus.WARNING, SynthesisErrorReporter.class, message)); - return message; - } - - @Override - public String reportWarning(EObject object, String message) { - return reportWarning(message); - } - - @Override - public String reportWarning(Path file, Integer line, String message) { - return reportWarning(message); - } - - @Override - public String reportInfo(String message) { - Klighd.log(new Status(IStatus.INFO, SynthesisErrorReporter.class, message)); - return message; - } +public class SynthesisMessageReporter extends MessageReporterBase { @Override - public String reportInfo(EObject object, String message) { - return reportInfo(message); + protected void reportWithoutPosition(DiagnosticSeverity severity, String message) { + var status = + switch (severity) { + case Error -> IStatus.ERROR; + case Warning -> IStatus.WARNING; + case Information, Hint -> IStatus.INFO; + }; + Klighd.log(new Status(status, SynthesisMessageReporter.class, message)); } @Override - public String reportInfo(Path file, Integer line, String message) { - return reportInfo(message); + protected void report(Path path, Range range, DiagnosticSeverity severity, String message) { + reportWithoutPosition(severity, message); } @Override - public boolean getErrorsOccurred() { - return errorsOccurred; + protected void reportOnNode(EObject node, DiagnosticSeverity severity, String message) { + reportWithoutPosition(severity, message); } } diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 301fbfe9a9..0641d03191 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -33,8 +33,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TargetProperty.CoordinationType; @@ -79,11 +79,11 @@ public void initializeTargetConfig( int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, - ErrorReporter errorReporter, + MessageReporter messageReporter, RtiConfig rtiConfig) throws IOException { - CExtensionUtils.handleCompileDefinitions(federate, numOfFederates, rtiConfig); + CExtensionUtils.handleCompileDefinitions(federate, numOfFederates, rtiConfig, messageReporter); generateCMakeInclude(federate, fileConfig); @@ -119,7 +119,7 @@ protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fil * @param connection FIXME * @param type FIXME * @param coordinationType The coordination type - * @param errorReporter + * @param messageReporter */ public String generateNetworkReceiverBody( Action action, @@ -128,7 +128,7 @@ public String generateNetworkReceiverBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var receiveRef = CUtil.portRefInReaction(receivingPort, connection.getDstBank(), connection.getDstChannel()); var result = new CodeBuilder(); @@ -148,7 +148,7 @@ public String generateNetworkReceiverBody( receiveRef + "->intended_tag = self->_lf__" + action.getName() + ".intended_tag;\n"); } - deserialize(action, receivingPort, connection, type, receiveRef, result, errorReporter); + deserialize(action, receivingPort, connection, type, receiveRef, result, messageReporter); return result.toString(); } @@ -161,7 +161,7 @@ public String generateNetworkReceiverBody( * @param type Type for the port * @param receiveRef A target language reference to the receiving port * @param result Used to put generated code in - * @param errorReporter Used to report errors, if any + * @param messageReporter Used to report errors, if any */ protected void deserialize( Action action, @@ -170,7 +170,7 @@ protected void deserialize( InferredType type, String receiveRef, CodeBuilder result, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { CTypes types = new CTypes(); // Adjust the type of the action and the receivingPort. // If it is "string", then change it to "char*". @@ -242,7 +242,7 @@ protected void deserialize( * @param connection * @param type * @param coordinationType - * @param errorReporter FIXME + * @param messageReporter FIXME */ public String generateNetworkSenderBody( VarRef sendingPort, @@ -250,7 +250,7 @@ public String generateNetworkSenderBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var sendRef = CUtil.portRefInReaction(sendingPort, connection.getSrcBank(), connection.getSrcChannel()); var receiveRef = @@ -331,7 +331,8 @@ public String generateNetworkSenderBody( + ", message_length"; } - serializeAndSend(connection, type, sendRef, result, sendingFunction, commonArgs, errorReporter); + serializeAndSend( + connection, type, sendRef, result, sendingFunction, commonArgs, messageReporter); return result.toString(); } @@ -344,7 +345,7 @@ public String generateNetworkSenderBody( * @param result * @param sendingFunction * @param commonArgs - * @param errorReporter + * @param messageReporter */ protected void serializeAndSend( FedConnectionInstance connection, @@ -353,7 +354,7 @@ protected void serializeAndSend( CodeBuilder result, String sendingFunction, String commonArgs, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { CTypes types = new CTypes(); var lengthExpression = ""; var pointerExpression = ""; @@ -510,10 +511,10 @@ public String generatePreamble( FederateInstance federate, FedFileConfig fileConfig, RtiConfig rtiConfig, - ErrorReporter errorReporter) + MessageReporter messageReporter) throws IOException { // Put the C preamble in a {@code include/_federate.name + _preamble.h} file - String cPreamble = makePreamble(federate, fileConfig, rtiConfig, errorReporter); + String cPreamble = makePreamble(federate, fileConfig, rtiConfig, messageReporter); String relPath = getPreamblePath(federate); Path fedPreamblePath = fileConfig.getSrcPath().resolve(relPath); Files.createDirectories(fedPreamblePath.getParent()); @@ -542,7 +543,7 @@ protected String makePreamble( FederateInstance federate, FedFileConfig fileConfig, RtiConfig rtiConfig, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var code = new CodeBuilder(); @@ -564,9 +565,9 @@ protected String makePreamble( """ .formatted(numOfNetworkActions)); - code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); + code.pr(generateExecutablePreamble(federate, rtiConfig, messageReporter)); - code.pr(generateInitializeTriggers(federate, errorReporter)); + code.pr(generateInitializeTriggers(federate, messageReporter)); code.pr(CExtensionUtils.generateFederateNeighborStructure(federate)); @@ -585,18 +586,18 @@ protected String generateSerializationIncludes( * receiving network messages). * * @param federate The federate to initialize triggers for. - * @param errorReporter Used to report errors. + * @param messageReporter Used to report errors. * @return The generated code for the macro. */ private String generateInitializeTriggers( - FederateInstance federate, ErrorReporter errorReporter) { + FederateInstance federate, MessageReporter messageReporter) { CodeBuilder code = new CodeBuilder(); // Temporarily change the original federate reactor's name in the AST to // the federate's name so that trigger references are accurate. var federatedReactor = FedASTUtils.findFederatedReactor(federate.instantiation.eResource()); var oldFederatedReactorName = federatedReactor.getName(); federatedReactor.setName(federate.name); - var main = new ReactorInstance(federatedReactor, errorReporter, 1); + var main = new ReactorInstance(federatedReactor, messageReporter, 1); code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main)); code.pr(CExtensionUtils.initializeTriggerForControlReactions(main, main, federate)); federatedReactor.setName(oldFederatedReactorName); @@ -613,10 +614,10 @@ private String generateInitializeTriggers( /** Generate code for an executed preamble. */ private String generateExecutablePreamble( - FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { + FederateInstance federate, RtiConfig rtiConfig, MessageReporter messageReporter) { CodeBuilder code = new CodeBuilder(); - code.pr(generateCodeForPhysicalActions(federate, errorReporter)); + code.pr(generateCodeForPhysicalActions(federate, messageReporter)); code.pr(generateCodeToInitializeFederate(federate, rtiConfig)); @@ -761,11 +762,11 @@ private String generateCodeToInitializeFederate(FederateInstance federate, RtiCo /** * Generate code to handle physical actions in the {@code federate}. * - * @param errorReporter Used to report errors. + * @param messageReporter Used to report errors. * @return Generated code. */ private String generateCodeForPhysicalActions( - FederateInstance federate, ErrorReporter errorReporter) { + FederateInstance federate, MessageReporter messageReporter) { CodeBuilder code = new CodeBuilder(); if (federate.targetConfig.coordination.equals(CoordinationType.CENTRALIZED)) { // If this program uses centralized coordination then check @@ -775,9 +776,9 @@ private String generateCodeForPhysicalActions( var main = new ReactorInstance( FedASTUtils.findFederatedReactor(federate.instantiation.eResource()), - errorReporter, + messageReporter, 1); - var instance = new ReactorInstance(federateClass, main, errorReporter); + var instance = new ReactorInstance(federateClass, main, messageReporter); var outputDelayMap = federate.findOutputsConnectedToPhysicalActions(instance); var minDelay = TimeValue.MAX_VALUE; Output outputFound = null; @@ -791,8 +792,7 @@ private String generateCodeForPhysicalActions( if (minDelay != TimeValue.MAX_VALUE) { // Unless silenced, issue a warning. if (federate.targetConfig.coordinationOptions.advance_message_interval == null) { - errorReporter.reportWarning( - outputFound, + String message = String.join( "\n", "Found a path from a physical action to output for reactor " @@ -806,7 +806,8 @@ private String generateCodeForPhysicalActions( "or consider using decentralized coordination. To silence this warning, set the" + " target", "parameter coordination-options with a value like {advance-message-interval: 10" - + " msec}")); + + " msec}"); + messageReporter.at(outputFound).warning(message); } code.pr( "_fed.min_delay_from_physical_action_to_federate_output = " diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index 391204fdb5..bacd99d56c 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.regex.Pattern; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.TargetConfig.ClockSyncOptions; import org.lflang.TargetProperty; import org.lflang.TargetProperty.ClockSyncMode; @@ -239,7 +240,10 @@ static boolean isSharedPtrType(InferredType type, CTypes types) { } public static void handleCompileDefinitions( - FederateInstance federate, int numOfFederates, RtiConfig rtiConfig) { + FederateInstance federate, + int numOfFederates, + RtiConfig rtiConfig, + MessageReporter messageReporter) { federate.targetConfig.setByUser.add(TargetProperty.COMPILE_DEFINITIONS); federate.targetConfig.compileDefinitions.put("FEDERATED", ""); federate.targetConfig.compileDefinitions.put( @@ -255,7 +259,7 @@ public static void handleCompileDefinitions( handleAdvanceMessageInterval(federate); - initializeClockSynchronization(federate, rtiConfig); + initializeClockSynchronization(federate, rtiConfig, messageReporter); } /** @@ -295,13 +299,17 @@ static boolean clockSyncIsOn(FederateInstance federate, RtiConfig rtiConfig) { * href="https://github.com/icyphy/lingua-franca/wiki/Distributed-Execution#clock-synchronization">Documentation */ public static void initializeClockSynchronization( - FederateInstance federate, RtiConfig rtiConfig) { + FederateInstance federate, RtiConfig rtiConfig, MessageReporter messageReporter) { // Check if clock synchronization should be enabled for this federate in the first place if (clockSyncIsOn(federate, rtiConfig)) { - System.out.println("Initial clock synchronization is enabled for federate " + federate.id); + messageReporter + .nowhere() + .info("Initial clock synchronization is enabled for federate " + federate.id); if (federate.targetConfig.clockSync == ClockSyncMode.ON) { if (federate.targetConfig.clockSyncOptions.collectStats) { - System.out.println("Will collect clock sync statistics for federate " + federate.id); + messageReporter + .nowhere() + .info("Will collect clock sync statistics for federate " + federate.id); // Add libm to the compiler flags // FIXME: This is a linker flag not compile flag but we don't have a way to add linker // flags @@ -310,7 +318,9 @@ public static void initializeClockSynchronization( federate.targetConfig.compilerFlags.add("-lm"); federate.targetConfig.setByUser.add(TargetProperty.FLAGS); } - System.out.println("Runtime clock synchronization is enabled for federate " + federate.id); + messageReporter + .nowhere() + .info("Runtime clock synchronization is enabled for federate " + federate.id); } addClockSyncCompileDefinitions(federate); diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index 17ac198781..616cfa8fce 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -1,8 +1,8 @@ package org.lflang.federated.extensions; import java.io.IOException; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.federated.generator.FedConnectionInstance; @@ -23,14 +23,14 @@ public interface FedTargetExtension { * @param numOfFederates * @param federate The federate instance. * @param fileConfig An instance of {@code FedFileConfig}. - * @param errorReporter Used to report errors. + * @param messageReporter Used to report errors. */ void initializeTargetConfig( LFGeneratorContext context, int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, - ErrorReporter errorReporter, + MessageReporter messageReporter, RtiConfig rtiConfig) throws IOException; @@ -44,7 +44,7 @@ void initializeTargetConfig( * @param connection FIXME * @param type FIXME * @param coordinationType The coordination type - * @param errorReporter + * @param messageReporter */ String generateNetworkReceiverBody( Action action, @@ -53,7 +53,7 @@ String generateNetworkReceiverBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter); + MessageReporter messageReporter); /** * Generate code for the body of a reaction that handles an output that is to be sent over the @@ -64,7 +64,7 @@ String generateNetworkReceiverBody( * @param connection * @param type * @param coordinationType - * @param errorReporter FIXME + * @param messageReporter FIXME */ String generateNetworkSenderBody( VarRef sendingPort, @@ -72,7 +72,7 @@ String generateNetworkSenderBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter); + MessageReporter messageReporter); /** * Generate code for the body of a reaction that decides whether the trigger for the given port is @@ -113,13 +113,13 @@ default void annotateReaction(Reaction reaction) {} * * @param federate * @param rtiConfig - * @param errorReporter + * @param messageReporter * @return */ String generatePreamble( FederateInstance federate, FedFileConfig fileConfig, RtiConfig rtiConfig, - ErrorReporter errorReporter) + MessageReporter messageReporter) throws IOException; } diff --git a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java index 6fd1cb3dc9..d467e981e0 100644 --- a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java @@ -27,8 +27,8 @@ package org.lflang.federated.extensions; import java.io.IOException; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedConnectionInstance; @@ -86,7 +86,7 @@ public String generateNetworkSenderBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var result = new CodeBuilder(); // We currently have no way to mark a reaction "unordered" @@ -95,7 +95,7 @@ public String generateNetworkSenderBody( result.pr(PyUtil.generateGILAcquireCode() + "\n"); result.pr( super.generateNetworkSenderBody( - sendingPort, receivingPort, connection, type, coordinationType, errorReporter)); + sendingPort, receivingPort, connection, type, coordinationType, messageReporter)); result.pr(PyUtil.generateGILReleaseCode() + "\n"); return result.getCode(); } @@ -108,7 +108,7 @@ public String generateNetworkReceiverBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var result = new CodeBuilder(); // We currently have no way to mark a reaction "unordered" @@ -117,7 +117,13 @@ public String generateNetworkReceiverBody( result.pr(PyUtil.generateGILAcquireCode() + "\n"); result.pr( super.generateNetworkReceiverBody( - action, sendingPort, receivingPort, connection, type, coordinationType, errorReporter)); + action, + sendingPort, + receivingPort, + connection, + type, + coordinationType, + messageReporter)); result.pr(PyUtil.generateGILReleaseCode() + "\n"); return result.getCode(); } @@ -130,7 +136,7 @@ protected void deserialize( InferredType type, String receiveRef, CodeBuilder result, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { String value = ""; switch (connection.getSerializer()) { case NATIVE: @@ -160,7 +166,7 @@ protected void serializeAndSend( CodeBuilder result, String sendingFunction, String commonArgs, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { String lengthExpression = ""; String pointerExpression = ""; switch (connection.getSerializer()) { diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 14df96afb5..884b40fbb4 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -6,8 +6,8 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -32,7 +32,7 @@ public void initializeTargetConfig( int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, - ErrorReporter errorReporter, + MessageReporter messageReporter, RtiConfig rtiConfig) throws IOException {} @@ -44,7 +44,7 @@ public String generateNetworkReceiverBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { return """ // generateNetworkReceiverBody if (%1$s !== undefined) { @@ -64,7 +64,7 @@ public String generateNetworkSenderBody( FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { return """ if (%1$s.%2$s !== undefined) { this.util.sendRTITimedMessage(%1$s.%2$s, %3$s, %4$s, %5$s); @@ -110,8 +110,8 @@ public String generatePreamble( FederateInstance federate, FedFileConfig fileConfig, RtiConfig rtiConfig, - ErrorReporter errorReporter) { - var minOutputDelay = getMinOutputDelay(federate, fileConfig, errorReporter); + MessageReporter messageReporter) { + var minOutputDelay = getMinOutputDelay(federate, fileConfig, messageReporter); var upstreamConnectionDelays = getUpstreamConnectionDelays(federate); return """ const defaultFederateConfig: __FederateConfig = { @@ -149,7 +149,7 @@ public String generatePreamble( } private TimeValue getMinOutputDelay( - FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + FederateInstance federate, FedFileConfig fileConfig, MessageReporter messageReporter) { if (federate.targetConfig.coordination.equals(CoordinationType.CENTRALIZED)) { // If this program uses centralized coordination then check // for outputs that depend on physical actions so that null messages can be @@ -158,9 +158,9 @@ private TimeValue getMinOutputDelay( var main = new ReactorInstance( FedASTUtils.findFederatedReactor(federate.instantiation.eResource()), - errorReporter, + messageReporter, 1); - var instance = new ReactorInstance(federateClass, main, errorReporter); + var instance = new ReactorInstance(federateClass, main, messageReporter); var outputDelayMap = federate.findOutputsConnectedToPhysicalActions(instance); var minOutputDelay = TimeValue.MAX_VALUE; Output outputFound = null; @@ -174,8 +174,7 @@ private TimeValue getMinOutputDelay( if (minOutputDelay != TimeValue.MAX_VALUE) { // Unless silenced, issue a warning. if (federate.targetConfig.coordinationOptions.advance_message_interval == null) { - errorReporter.reportWarning( - outputFound, + String message = String.join( "\n", "Found a path from a physical action to output for reactor " @@ -189,7 +188,8 @@ private TimeValue getMinOutputDelay( "or consider using decentralized coordination. To silence this warning, set the" + " target", "parameter coordination-options with a value like {advance-message-interval: 10" - + " msec}")); + + " msec}"); + messageReporter.at(outputFound).warning(message); } return minOutputDelay; } diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 338fbff242..71b809baf6 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -42,8 +42,8 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.xbase.lib.IteratorExtensions; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.ModelInfo; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; @@ -125,15 +125,15 @@ public static Reactor findFederatedReactor(Resource resource) { * * @param connection Network connection between two federates. * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. - * @param errorReporter Used to report errors encountered. + * @param messageReporter Used to report errors encountered. */ public static void makeCommunication( FedConnectionInstance connection, CoordinationType coordination, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { // Add the sender reaction. - addNetworkSenderReaction(connection, coordination, errorReporter); + addNetworkSenderReaction(connection, coordination, messageReporter); // Next, generate control reactions if (!connection.getDefinition().isPhysical() @@ -146,7 +146,7 @@ public static void makeCommunication( FedASTUtils.addNetworkOutputControlReaction(connection); // Add the network input control reaction to the parent - FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); + FedASTUtils.addNetworkInputControlReaction(connection, coordination, messageReporter); } // Create the network action (@see createNetworkAction) @@ -159,7 +159,7 @@ public static void makeCommunication( ((Reactor) connection.getDefinition().eContainer()).getActions().add(networkAction); // Add the network receiver reaction in the destinationFederate - addNetworkReceiverReaction(networkAction, connection, coordination, errorReporter); + addNetworkReceiverReaction(networkAction, connection, coordination, messageReporter); } /** @@ -223,7 +223,7 @@ private static void addNetworkReceiverReaction( Action networkAction, FedConnectionInstance connection, CoordinationType coordination, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { LfFactory factory = LfFactory.eINSTANCE; VarRef sourceRef = factory.createVarRef(); VarRef destRef = factory.createVarRef(); @@ -278,7 +278,7 @@ private static void addNetworkReceiverReaction( connection, ASTUtils.getInferredType(networkAction), coordination, - errorReporter)); + messageReporter)); ASTUtils.addReactionAttribute(networkReceiverReaction, "_unordered"); @@ -297,7 +297,7 @@ private static void addNetworkReceiverReaction( ) { // Add necessary dependencies to reaction to ensure that it executes correctly // relative to other network input control reactions in the federate. - addRelativeDependency(connection, networkReceiverReaction, errorReporter); + addRelativeDependency(connection, networkReceiverReaction, messageReporter); } } @@ -308,13 +308,13 @@ private static void addNetworkReceiverReaction( * * @param connection FIXME * @param coordination FIXME - * @param errorReporter + * @param messageReporter * @note Used in federated execution */ private static void addNetworkInputControlReaction( FedConnectionInstance connection, CoordinationType coordination, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { LfFactory factory = LfFactory.eINSTANCE; Reaction reaction = factory.createReaction(); @@ -383,7 +383,7 @@ private static void addNetworkInputControlReaction( // Add necessary dependencies to reaction to ensure that it executes correctly // relative to other network input control reactions in the federate. - addRelativeDependency(connection, reaction, errorReporter); + addRelativeDependency(connection, reaction, messageReporter); } /** @@ -399,7 +399,7 @@ private static void addNetworkInputControlReaction( private static void addRelativeDependency( FedConnectionInstance connection, Reaction networkInputReaction, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var upstreamOutputPortsInFederate = findUpstreamPortsInFederate( connection.dstFederate, @@ -416,7 +416,7 @@ private static void addRelativeDependency( networkInputReaction.getSources().add(sourceRef); // Remove the port if it introduces cycles - info.update((Model) networkInputReaction.eContainer().eContainer(), errorReporter); + info.update((Model) networkInputReaction.eContainer().eContainer(), messageReporter); if (!info.topologyCycles().isEmpty()) { networkInputReaction.getSources().remove(sourceRef); } @@ -646,13 +646,13 @@ public static List safe(List list) { * * @param connection Network connection between two federates. * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. - * @param errorReporter FIXME + * @param messageReporter FIXME * @note Used in federated execution */ private static void addNetworkSenderReaction( FedConnectionInstance connection, CoordinationType coordination, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { LfFactory factory = LfFactory.eINSTANCE; // Assume all the types are the same, so just use the first on the right. Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); @@ -700,7 +700,7 @@ private static void addNetworkSenderReaction( connection, InferredType.fromAST(type), coordination, - errorReporter)); + messageReporter)); ASTUtils.addReactionAttribute(networkSenderReaction, "_unordered"); diff --git a/core/src/main/java/org/lflang/federated/generator/FedEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedEmitter.java index bdf912d0c8..c6e515854d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedEmitter.java @@ -5,7 +5,7 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.Map; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.CodeMap; import org.lflang.generator.LFGeneratorContext; @@ -16,17 +16,17 @@ public class FedEmitter { private final FedFileConfig fileConfig; private final Reactor originalMainReactor; - private final ErrorReporter errorReporter; + private final MessageReporter messageReporter; private final RtiConfig rtiConfig; public FedEmitter( FedFileConfig fileConfig, Reactor originalMainReactor, - ErrorReporter errorReporter, + MessageReporter messageReporter, RtiConfig rtiConfig) { this.fileConfig = fileConfig; this.originalMainReactor = originalMainReactor; - this.errorReporter = errorReporter; + this.messageReporter = messageReporter; this.rtiConfig = rtiConfig; } @@ -40,23 +40,26 @@ Map generateFederate( throws IOException { String fedName = federate.name; Files.createDirectories(fileConfig.getSrcPath()); - System.out.println( - "##### Generating code for federate " - + fedName - + " in directory " - + fileConfig.getSrcPath()); + messageReporter + .nowhere() + .info( + "##### Generating code for federate " + + fedName + + " in directory " + + fileConfig.getSrcPath()); String federateCode = String.join( "\n", new FedTargetEmitter() .generateTarget( - context, numOfFederates, federate, fileConfig, errorReporter, rtiConfig), + context, numOfFederates, federate, fileConfig, messageReporter, rtiConfig), new FedImportEmitter().generateImports(federate, fileConfig), new FedPreambleEmitter() - .generatePreamble(federate, fileConfig, rtiConfig, errorReporter), + .generatePreamble(federate, fileConfig, rtiConfig, messageReporter), new FedReactorEmitter().generateReactorDefinitions(federate), - new FedMainEmitter().generateMainReactor(federate, originalMainReactor, errorReporter)); + new FedMainEmitter() + .generateMainReactor(federate, originalMainReactor, messageReporter)); Map codeMapMap = new HashMap<>(); var lfFilePath = lfFilePath(fileConfig, federate); try (var srcWriter = Files.newBufferedWriter(lfFilePath)) { diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 6656a489f8..b3cd74fe48 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -26,9 +26,9 @@ import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.util.RuntimeIOException; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.LFStandaloneSetup; +import org.lflang.MessageReporter; import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TargetProperty.CoordinationType; @@ -54,12 +54,13 @@ import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Reactor; +import org.lflang.lf.TargetDecl; import org.lflang.util.Averager; public class FedGenerator { /** */ - private final ErrorReporter errorReporter; + private final MessageReporter messageReporter; /** A list of federate instances. */ private final List federates = new ArrayList<>(); @@ -100,7 +101,7 @@ public class FedGenerator { public FedGenerator(LFGeneratorContext context) { this.fileConfig = (FedFileConfig) context.getFileConfig(); this.targetConfig = context.getTargetConfig(); - this.errorReporter = context.getErrorReporter(); + this.messageReporter = context.getErrorReporter(); } /** @@ -139,7 +140,10 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws FedEmitter fedEmitter = new FedEmitter( - fileConfig, ASTUtils.toDefinition(mainDef.getReactorClass()), errorReporter, rtiConfig); + fileConfig, + ASTUtils.toDefinition(mainDef.getReactorClass()), + messageReporter, + rtiConfig); // Generate LF code for each federate. Map lf2lfCodeMapMap = new HashMap<>(); @@ -166,8 +170,8 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws if (c.getErrorReporter().getErrorsOccurred()) { context .getErrorReporter() - .reportError( - "Failure during code generation of " + c.getFileConfig().srcFile); + .at(c.getFileConfig().srcFile) + .error("Failure during code generation of " + c.getFileConfig().srcFile); } }); }); @@ -177,7 +181,7 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws } private void generateLaunchScript() { - new FedLauncherGenerator(this.targetConfig, this.fileConfig, this.errorReporter) + new FedLauncherGenerator(this.targetConfig, this.fileConfig, this.messageReporter) .doGenerate(federates, rtiConfig); } @@ -227,15 +231,19 @@ private void cleanIfNeeded(LFGeneratorContext context) { /** Return whether federated execution is supported for {@code resource}. */ private boolean federatedExecutionIsSupported(Resource resource) { - var target = Target.fromDecl(GeneratorUtils.findTargetDecl(resource)); + TargetDecl targetDecl = GeneratorUtils.findTargetDecl(resource); + var target = Target.fromDecl(targetDecl); var targetOK = List.of(Target.C, Target.Python, Target.TS, Target.CPP, Target.CCPP).contains(target); if (!targetOK) { - errorReporter.reportError("Federated execution is not supported with target " + target + "."); + messageReporter + .at(targetDecl) + .error("Federated execution is not supported with target " + target + "."); } if (target.equals(Target.C) && GeneratorUtils.isHostWindows()) { - errorReporter.reportError( - "Federated LF programs with a C target are currently not supported on Windows."); + messageReporter + .at(targetDecl) + .error("Federated LF programs with a C target are currently not supported on Windows."); targetOK = false; } @@ -259,12 +267,13 @@ private Map compileFederates( Math.min( 6, Math.min(Math.max(federates.size(), 1), Runtime.getRuntime().availableProcessors())); var compileThreadPool = Executors.newFixedThreadPool(numOfCompileThreads); - System.out.println( - "******** Using " + numOfCompileThreads + " threads to compile the program."); + messageReporter + .nowhere() + .info("******** Using " + numOfCompileThreads + " threads to compile the program."); Map codeMapMap = new ConcurrentHashMap<>(); List subContexts = Collections.synchronizedList(new ArrayList<>()); Averager averager = new Averager(federates.size()); - final var threadSafeErrorReporter = new SynchronizedErrorReporter(errorReporter); + final var threadSafeErrorReporter = new SynchronizedMessageReporter(messageReporter); for (int i = 0; i < federates.size(); i++) { FederateInstance fed = federates.get(i); final int id = i; @@ -277,8 +286,8 @@ private Map compileFederates( true); FileConfig subFileConfig = LFGenerator.createFileConfig(res, fileConfig.getSrcGenPath(), true); - ErrorReporter subContextErrorReporter = - new LineAdjustingErrorReporter(threadSafeErrorReporter, lf2lfCodeMapMap); + MessageReporter subContextMessageReporter = + new LineAdjustingMessageReporter(threadSafeErrorReporter, lf2lfCodeMapMap); var props = new Properties(); if (targetConfig.dockerOptions != null && targetConfig.target.buildsUsingDocker()) { @@ -290,12 +299,12 @@ private Map compileFederates( new TargetConfig( props, GeneratorUtils.findTargetDecl(subFileConfig.resource), - subContextErrorReporter); + subContextMessageReporter); SubContext subContext = new SubContext(context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, 100) { @Override - public ErrorReporter getErrorReporter() { - return subContextErrorReporter; + public MessageReporter getErrorReporter() { + return subContextMessageReporter; } @Override @@ -330,7 +339,10 @@ public TargetConfig getTargetConfig() { try { compileThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (Exception e) { - context.getErrorReporter().reportError("Failure during code generation: " + e.getMessage()); + context + .getErrorReporter() + .nowhere() + .error("Failure during code generation: " + e.getMessage()); e.printStackTrace(); } finally { finalizer.accept(subContexts); @@ -412,8 +424,9 @@ private void analyzeFederates(Reactor federation, LFGeneratorContext context) { for (Instantiation instantiation : ASTUtils.allInstantiations(federation)) { int bankWidth = ASTUtils.width(instantiation.getWidthSpec(), mainReactorContext); if (bankWidth < 0) { - errorReporter.reportError( - instantiation, "Cannot determine bank width! Assuming width of 1."); + messageReporter + .at(instantiation) + .error("Cannot determine bank width! Assuming width of 1."); // Continue with a bank width of 1. bankWidth = 1; } @@ -445,7 +458,7 @@ private List getFederateInstances( var resource = instantiation.getReactorClass().eResource(); var federateTargetConfig = new FedTargetConfig(context, resource); FederateInstance federateInstance = - new FederateInstance(instantiation, federateID, i, federateTargetConfig, errorReporter); + new FederateInstance(instantiation, federateID, i, federateTargetConfig, messageReporter); federates.add(federateInstance); federateInstances.add(federateInstance); @@ -481,7 +494,7 @@ private void replaceFederateConnectionsWithProxies(Reactor federation) { // to duplicate the rather complicated logic in that class. We specify a depth of 1, // so it only creates the reactors immediately within the top level, not reactors // that those contain. - ReactorInstance mainInstance = new ReactorInstance(federation, errorReporter); + ReactorInstance mainInstance = new ReactorInstance(federation, messageReporter); for (ReactorInstance child : mainInstance.children) { for (PortInstance output : child.outputs) { @@ -507,8 +520,7 @@ private void replaceConnectionFromOutputPort(PortInstance output) { for (SendRange srcRange : output.getDependentPorts()) { if (srcRange.connection == null) { // This should not happen. - errorReporter.reportError( - output.getDefinition(), "Unexpected error. Cannot find output connection for port"); + messageReporter.at(output.getDefinition()).error("Cannot find output connection for port"); continue; } // Iterate through destinations @@ -600,6 +612,6 @@ private void replaceFedConnection(FedConnectionInstance connection) { } } - FedASTUtils.makeCommunication(connection, targetConfig.coordination, errorReporter); + FedASTUtils.makeCommunication(connection, targetConfig.coordination, messageReporter); } } diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 31cb435e31..4d262d1bb4 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -3,7 +3,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.emf.ecore.EObject; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.ast.FormattingUtil; import org.lflang.lf.Reactor; @@ -17,16 +17,16 @@ public class FedMainEmitter { * * @param federate * @param originalMainReactor The original main reactor. - * @param errorReporter Used to report errors. + * @param messageReporter Used to report errors. * @return The main reactor. */ String generateMainReactor( - FederateInstance federate, Reactor originalMainReactor, ErrorReporter errorReporter) { + FederateInstance federate, Reactor originalMainReactor, MessageReporter messageReporter) { // FIXME: Handle modes at the top-level if (!ASTUtils.allModes(originalMainReactor).isEmpty()) { - errorReporter.reportError( - ASTUtils.allModes(originalMainReactor).stream().findFirst().get(), - "Modes at the top level are not supported under federated execution."); + messageReporter + .at(ASTUtils.allModes(originalMainReactor).stream().findFirst().get()) + .error("Modes at the top level are not supported under federated execution."); } var renderer = FormattingUtil.renderer(federate.targetConfig.target); diff --git a/core/src/main/java/org/lflang/federated/generator/FedPreambleEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedPreambleEmitter.java index 9cb35b2949..7966bc73dd 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedPreambleEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedPreambleEmitter.java @@ -3,7 +3,7 @@ import static org.lflang.ast.ASTUtils.toText; import java.io.IOException; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.federated.extensions.FedTargetExtensionFactory; import org.lflang.federated.launcher.RtiConfig; @@ -23,7 +23,7 @@ String generatePreamble( FederateInstance federate, FedFileConfig fileConfig, RtiConfig rtiConfig, - ErrorReporter errorReporter) + MessageReporter messageReporter) throws IOException { CodeBuilder preambleCode = new CodeBuilder(); @@ -48,7 +48,7 @@ String generatePreamble( =}""" .formatted( FedTargetExtensionFactory.getExtension(federate.targetConfig.target) - .generatePreamble(federate, fileConfig, rtiConfig, errorReporter))); + .generatePreamble(federate, fileConfig, rtiConfig, messageReporter))); return preambleCode.getCode(); } diff --git a/core/src/main/java/org/lflang/federated/generator/FedTargetConfig.java b/core/src/main/java/org/lflang/federated/generator/FedTargetConfig.java index 1c9a53da4a..4eef59c7e9 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedTargetConfig.java +++ b/core/src/main/java/org/lflang/federated/generator/FedTargetConfig.java @@ -4,7 +4,7 @@ import java.nio.file.Path; import org.eclipse.emf.ecore.resource.Resource; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.TargetProperty; import org.lflang.generator.GeneratorUtils; @@ -47,10 +47,10 @@ public FedTargetConfig(LFGeneratorContext context, Resource federateResource) { * * @param federateResource The resource where the class of the federate is specified. * @param mainResource The resource in which the federation (i.e., main reactor) is specified. - * @param errorReporter An error reporter to use when problems are encountered. + * @param messageReporter An error reporter to use when problems are encountered. */ private void mergeImportedConfig( - Resource federateResource, Resource mainResource, ErrorReporter errorReporter) { + Resource federateResource, Resource mainResource, MessageReporter messageReporter) { // If the federate is imported, then update the configuration based on target properties // in the imported file. if (!federateResource.equals(mainResource)) { @@ -62,7 +62,7 @@ private void mergeImportedConfig( this, convertToEmptyListIfNull(targetProperties.getPairs()), getRelativePath(mainResource, federateResource), - errorReporter); + messageReporter); } } } diff --git a/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java index b411ff0a44..2e9b26a7ff 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java @@ -1,7 +1,7 @@ package org.lflang.federated.generator; import java.io.IOException; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.TargetProperty; import org.lflang.ast.FormattingUtil; import org.lflang.federated.extensions.FedTargetExtensionFactory; @@ -15,7 +15,7 @@ String generateTarget( int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, - ErrorReporter errorReporter, + MessageReporter messageReporter, RtiConfig rtiConfig) throws IOException { @@ -25,7 +25,7 @@ String generateTarget( // See https://issues.lf-lang.org/1667 FedTargetExtensionFactory.getExtension(federate.targetConfig.target) .initializeTargetConfig( - context, numOfFederates, federate, fileConfig, errorReporter, rtiConfig); + context, numOfFederates, federate, fileConfig, messageReporter, rtiConfig); return FormattingUtil.renderer(federate.targetConfig.target) .apply( diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index ee3c0a46f0..22e70a6c73 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -36,7 +36,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.emf.ecore.EObject; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -83,18 +83,18 @@ public class FederateInstance { // why does this not extend ReactorInstance? * been defined. * @param id The federate ID. * @param bankIndex If instantiation.widthSpec !== null, this gives the bank position. - * @param errorReporter The error reporter + * @param messageReporter The error reporter */ public FederateInstance( Instantiation instantiation, int id, int bankIndex, TargetConfig targetConfig, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { this.instantiation = instantiation; this.id = id; this.bankIndex = bankIndex; - this.errorReporter = errorReporter; + this.messageReporter = messageReporter; this.targetConfig = targetConfig; if (instantiation != null) { @@ -510,9 +510,9 @@ private boolean containsAllVarRefs(Iterable varRefs) { referencesFederate = true; } else { if (referencesFederate) { - errorReporter.reportError( - varRef, - "Mixed triggers and effects from" + " different federates. This is not permitted"); + messageReporter + .at(varRef) + .error("Mixed triggers and effects from different federates. This is not permitted"); } inFederate = false; } @@ -568,7 +568,7 @@ public String toString() { private Set excludeReactions = null; /** An error reporter */ - private final ErrorReporter errorReporter; + private final MessageReporter messageReporter; /** * Find the nearest (shortest) path to a physical action trigger from this 'reaction' in terms of diff --git a/core/src/main/java/org/lflang/federated/generator/LineAdjustingErrorReporter.java b/core/src/main/java/org/lflang/federated/generator/LineAdjustingErrorReporter.java deleted file mode 100644 index aa2f49b6da..0000000000 --- a/core/src/main/java/org/lflang/federated/generator/LineAdjustingErrorReporter.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.lflang.federated.generator; - -import java.nio.file.Path; -import java.util.Map; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.lsp4j.DiagnosticSeverity; -import org.lflang.ErrorReporter; -import org.lflang.generator.CodeMap; -import org.lflang.generator.Position; -import org.lflang.generator.Range; - -public class LineAdjustingErrorReporter implements ErrorReporter { - - private final ErrorReporter parent; - private final Map codeMapMap; - - public LineAdjustingErrorReporter(ErrorReporter parent, Map codeMapMap) { - this.parent = parent; - this.codeMapMap = codeMapMap; - } - - @Override - public String reportError(String message) { - return parent.reportError(message); - } - - @Override - public String reportWarning(String message) { - return parent.reportWarning(message); - } - - @Override - public String reportInfo(String message) { - return parent.reportInfo(message); - } - - @Override - public String reportError(EObject object, String message) { - return parent.reportError(object, message); - } - - @Override - public String reportWarning(EObject object, String message) { - return parent.reportWarning(object, message); - } - - @Override - public String reportInfo(EObject object, String message) { - return parent.reportInfo(object, message); - } - - @Override - public String reportError(Path file, Integer line, String message) { - return report(file, line, message, DiagnosticSeverity.Error); - } - - @Override - public String reportWarning(Path file, Integer line, String message) { - return report(file, line, message, DiagnosticSeverity.Warning); - } - - @Override - public String reportInfo(Path file, Integer line, String message) { - return report(file, line, message, DiagnosticSeverity.Information); - } - - private String report(Path file, Integer line, String message, DiagnosticSeverity severity) { - if (line == null) return report(file, severity, message); - var position = Position.fromOneBased(line, Integer.MAX_VALUE); - return report( - file, severity, message, Position.fromZeroBased(position.getZeroBasedLine(), 0), position); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message) { - return ErrorReporter.super.report(file, severity, message); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message, int line) { - return ErrorReporter.super.report(file, severity, message, line); - } - - @Override - public String report( - Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { - String ret = null; - if (codeMapMap.containsKey(file)) { - var relevantMap = codeMapMap.get(file); - for (Path lfSource : relevantMap.lfSourcePaths()) { - var adjustedRange = relevantMap.adjusted(lfSource, new Range(startPos, endPos)); - ret = - parent.report( - lfSource, - severity, - message, - adjustedRange.getStartInclusive().equals(Position.ORIGIN) - ? Position.fromZeroBased(adjustedRange.getEndExclusive().getZeroBasedLine(), 0) - : adjustedRange.getStartInclusive(), - adjustedRange.getEndExclusive()); - } - } - if (ret == null) - return severity == DiagnosticSeverity.Error ? reportError(message) : reportWarning(message); - return ret; - } - - @Override - public boolean getErrorsOccurred() { - return parent.getErrorsOccurred(); - } -} diff --git a/core/src/main/java/org/lflang/federated/generator/LineAdjustingMessageReporter.java b/core/src/main/java/org/lflang/federated/generator/LineAdjustingMessageReporter.java new file mode 100644 index 0000000000..9a08f007cb --- /dev/null +++ b/core/src/main/java/org/lflang/federated/generator/LineAdjustingMessageReporter.java @@ -0,0 +1,61 @@ +package org.lflang.federated.generator; + +import java.nio.file.Path; +import java.util.Map; +import org.eclipse.emf.ecore.EObject; +import org.lflang.MessageReporter; +import org.lflang.generator.CodeMap; +import org.lflang.generator.Position; +import org.lflang.generator.Range; + +public class LineAdjustingMessageReporter implements MessageReporter { + + private final MessageReporter parent; + private final Map codeMapMap; + + public LineAdjustingMessageReporter(MessageReporter parent, Map codeMapMap) { + this.parent = parent; + this.codeMapMap = codeMapMap; + } + + @Override + public Stage2 at(EObject object) { + return parent.at(object); + } + + @Override + public Stage2 nowhere() { + return parent.nowhere(); + } + + @Override + public Stage2 at(Path file, int line) { + // encompass the whole line + var endOfLine = Position.fromOneBased(line, Integer.MAX_VALUE); + var startOfLine = Position.fromOneBased(line, 1); + return at(file, new Range(startOfLine, endOfLine)); + } + + @Override + public Stage2 at(Path file, Range range) { + if (codeMapMap.containsKey(file)) { + var relevantMap = codeMapMap.get(file); + for (Path lfSource : relevantMap.lfSourcePaths()) { + var adjustedRange = relevantMap.adjusted(lfSource, range); + adjustedRange = + new Range( + adjustedRange.getStartInclusive().equals(Position.ORIGIN) + ? Position.fromZeroBased(adjustedRange.getEndExclusive().getZeroBasedLine(), 0) + : adjustedRange.getStartInclusive(), + adjustedRange.getEndExclusive()); + return parent.at(lfSource, adjustedRange); + } + } + return nowhere(); + } + + @Override + public boolean getErrorsOccurred() { + return parent.getErrorsOccurred(); + } +} diff --git a/core/src/main/java/org/lflang/federated/generator/SynchronizedErrorReporter.java b/core/src/main/java/org/lflang/federated/generator/SynchronizedErrorReporter.java deleted file mode 100644 index 08c47c4cd3..0000000000 --- a/core/src/main/java/org/lflang/federated/generator/SynchronizedErrorReporter.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.lflang.federated.generator; - -import java.nio.file.Path; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.lsp4j.DiagnosticSeverity; -import org.lflang.ErrorReporter; -import org.lflang.generator.Position; - -public class SynchronizedErrorReporter implements ErrorReporter { - - private final ErrorReporter parent; - - public SynchronizedErrorReporter(ErrorReporter parent) { - this.parent = parent; - } - - @Override - public synchronized String reportError(String message) { - return parent.reportError(message); - } - - @Override - public synchronized String reportWarning(String message) { - return parent.reportWarning(message); - } - - @Override - public synchronized String reportInfo(String message) { - return parent.reportInfo(message); - } - - @Override - public synchronized String reportError(EObject object, String message) { - return parent.reportError(object, message); - } - - @Override - public synchronized String reportWarning(EObject object, String message) { - return parent.reportWarning(object, message); - } - - @Override - public synchronized String reportInfo(EObject object, String message) { - return parent.reportInfo(object, message); - } - - @Override - public synchronized String reportError(Path file, Integer line, String message) { - return parent.reportError(file, line, message); - } - - @Override - public synchronized String reportWarning(Path file, Integer line, String message) { - return parent.reportWarning(file, line, message); - } - - @Override - public synchronized String reportInfo(Path file, Integer line, String message) { - return parent.reportInfo(file, line, message); - } - - @Override - public synchronized String report(Path file, DiagnosticSeverity severity, String message) { - return parent.report(file, severity, message); - } - - @Override - public synchronized String report( - Path file, DiagnosticSeverity severity, String message, int line) { - return parent.report(file, severity, message, line); - } - - @Override - public synchronized String report( - Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { - return parent.report(file, severity, message, startPos, endPos); - } - - @Override - public synchronized boolean getErrorsOccurred() { - return parent.getErrorsOccurred(); - } -} diff --git a/core/src/main/java/org/lflang/federated/generator/SynchronizedMessageReporter.java b/core/src/main/java/org/lflang/federated/generator/SynchronizedMessageReporter.java new file mode 100644 index 0000000000..e51a118d70 --- /dev/null +++ b/core/src/main/java/org/lflang/federated/generator/SynchronizedMessageReporter.java @@ -0,0 +1,39 @@ +package org.lflang.federated.generator; + +import java.nio.file.Path; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.lflang.MessageReporter; +import org.lflang.MessageReporterBase; +import org.lflang.generator.Range; + +public class SynchronizedMessageReporter extends MessageReporterBase { + + private final MessageReporter parent; + + public SynchronizedMessageReporter(MessageReporter parent) { + this.parent = parent; + } + + @Override + protected synchronized void reportOnNode( + EObject node, DiagnosticSeverity severity, String message) { + parent.at(node).report(severity, message); + } + + @Override + protected synchronized void report( + Path path, Range range, DiagnosticSeverity severity, String message) { + parent.at(path, range).report(severity, message); + } + + @Override + protected synchronized void reportWithoutPosition(DiagnosticSeverity severity, String message) { + parent.nowhere().report(severity, message); + } + + @Override + public synchronized boolean getErrorsOccurred() { + return parent.getErrorsOccurred(); + } +} diff --git a/core/src/main/java/org/lflang/federated/launcher/BuildConfig.java b/core/src/main/java/org/lflang/federated/launcher/BuildConfig.java index 47345af147..48b297da01 100644 --- a/core/src/main/java/org/lflang/federated/launcher/BuildConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/BuildConfig.java @@ -1,6 +1,6 @@ package org.lflang.federated.launcher; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; @@ -11,7 +11,7 @@ public abstract class BuildConfig { protected final FederateInstance federate; /** An error reporter to report problems. */ - protected final ErrorReporter errorReporter; + protected final MessageReporter messageReporter; /** The file configuration of the federation that the federate belongs to. */ protected final FedFileConfig fileConfig; @@ -21,11 +21,11 @@ public abstract class BuildConfig { * * @param federate The federate that this configuration applies to. * @param fileConfig The file configuration of the federation that the federate belongs to. - * @param errorReporter An error reporter to report problems. + * @param messageReporter An error reporter to report problems. */ public BuildConfig( - FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - this.errorReporter = errorReporter; + FederateInstance federate, FedFileConfig fileConfig, MessageReporter messageReporter) { + this.messageReporter = messageReporter; this.federate = federate; this.fileConfig = fileConfig; } diff --git a/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java b/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java index d77ea3988c..8a3609801c 100644 --- a/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java @@ -26,7 +26,7 @@ package org.lflang.federated.launcher; import java.io.File; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; import org.lflang.generator.c.CCompiler; @@ -41,8 +41,8 @@ public class CBuildConfig extends BuildConfig { public CBuildConfig( - FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - super(federate, fileConfig, errorReporter); + FederateInstance federate, FedFileConfig fileConfig, MessageReporter messageReporter) { + super(federate, fileConfig, messageReporter); } @Override @@ -56,7 +56,7 @@ public String compileCommand() { if (!federate.targetConfig.compileAdditionalSources.contains(linuxPlatformSupport)) { federate.targetConfig.compileAdditionalSources.add(linuxPlatformSupport); } - CCompiler cCompiler = new CCompiler(federate.targetConfig, fileConfig, errorReporter, false); + CCompiler cCompiler = new CCompiler(federate.targetConfig, fileConfig, messageReporter, false); commandToReturn = String.join( " ", diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java index b3be6acad0..53b8ec6607 100644 --- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -33,7 +33,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.TargetProperty.ClockSyncMode; import org.lflang.federated.generator.FedFileConfig; @@ -48,19 +48,19 @@ public class FedLauncherGenerator { protected TargetConfig targetConfig; protected FedFileConfig fileConfig; - protected ErrorReporter errorReporter; + protected MessageReporter messageReporter; /** * @param targetConfig The current target configuration. * @param fileConfig The current file configuration. - * @param errorReporter A error reporter for reporting any errors or warnings during the code + * @param messageReporter A error reporter for reporting any errors or warnings during the code * generation */ public FedLauncherGenerator( - TargetConfig targetConfig, FedFileConfig fileConfig, ErrorReporter errorReporter) { + TargetConfig targetConfig, FedFileConfig fileConfig, MessageReporter messageReporter) { this.targetConfig = targetConfig; this.fileConfig = fileConfig; - this.errorReporter = errorReporter; + this.messageReporter = messageReporter; } /** @@ -156,7 +156,7 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // Index used for storing pids of federates int federateIndex = 0; for (FederateInstance federate : federates) { - var buildConfig = getBuildConfig(federate, fileConfig, errorReporter); + var buildConfig = getBuildConfig(federate, fileConfig, messageReporter); if (federate.isRemote) { Path fedRelSrcGenPath = fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath()).resolve(federate.name); @@ -214,12 +214,13 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { try { Files.createDirectories(fileConfig.binPath); } catch (IOException e) { - errorReporter.reportError("Unable to create directory: " + fileConfig.binPath); + messageReporter.nowhere().error("Unable to create directory: " + fileConfig.binPath); } } - System.out.println( - "##### Generating launcher for federation " + " in directory " + fileConfig.binPath); + messageReporter + .nowhere() + .info("##### Generating launcher for federation " + " in directory " + fileConfig.binPath); // Write the launcher file. // Delete file previously produced, if any. @@ -232,17 +233,17 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { try { fOut = new FileOutputStream(file); } catch (FileNotFoundException e) { - errorReporter.reportError("Unable to find file: " + file); + messageReporter.nowhere().error("Unable to find file: " + file); } try { fOut.write(shCode.toString().getBytes()); fOut.close(); } catch (IOException e) { - errorReporter.reportError("Unable to write to file: " + file); + messageReporter.nowhere().error("Unable to write to file: " + file); } if (!file.setExecutable(true, false)) { - errorReporter.reportWarning("Unable to make launcher script executable."); + messageReporter.nowhere().warning("Unable to make launcher script executable."); } // Write the distributor file. @@ -257,12 +258,12 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { fOut.write(distCode.toString().getBytes()); fOut.close(); if (!file.setExecutable(true, false)) { - errorReporter.reportWarning("Unable to make file executable: " + file); + messageReporter.nowhere().warning("Unable to make file executable: " + file); } } catch (FileNotFoundException e) { - errorReporter.reportError("Unable to find file: " + file); + messageReporter.nowhere().error("Unable to find file: " + file); } catch (IOException e) { - errorReporter.reportError("Unable to write to file " + file); + messageReporter.nowhere().error("Unable to write to file " + file); } } } @@ -498,15 +499,15 @@ private String getFedLocalLaunchCode( * * @param federate The federate to which the build configuration applies. * @param fileConfig The file configuration of the federation to which the federate belongs. - * @param errorReporter An error reporter to report problems. + * @param messageReporter An error reporter to report problems. * @return */ private BuildConfig getBuildConfig( - FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + FederateInstance federate, FedFileConfig fileConfig, MessageReporter messageReporter) { return switch (federate.targetConfig.target) { - case C, CCPP -> new CBuildConfig(federate, fileConfig, errorReporter); - case Python -> new PyBuildConfig(federate, fileConfig, errorReporter); - case TS -> new TsBuildConfig(federate, fileConfig, errorReporter); + case C, CCPP -> new CBuildConfig(federate, fileConfig, messageReporter); + case Python -> new PyBuildConfig(federate, fileConfig, messageReporter); + case TS -> new TsBuildConfig(federate, fileConfig, messageReporter); case CPP, Rust -> throw new UnsupportedOperationException(); }; } diff --git a/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java b/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java index 1a8a996349..09f7fac34f 100644 --- a/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java @@ -1,14 +1,14 @@ package org.lflang.federated.launcher; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; public class PyBuildConfig extends BuildConfig { public PyBuildConfig( - FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - super(federate, fileConfig, errorReporter); + FederateInstance federate, FedFileConfig fileConfig, MessageReporter messageReporter) { + super(federate, fileConfig, messageReporter); } @Override diff --git a/core/src/main/java/org/lflang/federated/launcher/TsBuildConfig.java b/core/src/main/java/org/lflang/federated/launcher/TsBuildConfig.java index 97f40c767a..82a0e2c7ae 100644 --- a/core/src/main/java/org/lflang/federated/launcher/TsBuildConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/TsBuildConfig.java @@ -25,7 +25,7 @@ package org.lflang.federated.launcher; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; @@ -40,8 +40,8 @@ public class TsBuildConfig extends BuildConfig { public TsBuildConfig( - FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - super(federate, fileConfig, errorReporter); + FederateInstance federate, FedFileConfig fileConfig, MessageReporter messageReporter) { + super(federate, fileConfig, messageReporter); } @Override diff --git a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java index 1384d11517..5f7364b3de 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java @@ -47,12 +47,16 @@ public class FedROS2CPPSerialization implements FedSerialization { @Override public boolean isCompatible(GeneratorBase generator) { if (generator.getTarget() != Target.C) { - generator.errorReporter.reportError( - "ROS serialization is currently only supported for the C target."); + generator + .messageReporter + .nowhere() + .error("ROS serialization is currently only supported for the C target."); return false; } else if (!generator.getTargetConfig().compiler.equalsIgnoreCase("g++")) { - generator.errorReporter.reportError( - "Please use the 'compiler: \"g++\"' target property \n" + "for ROS serialization"); + generator + .messageReporter + .nowhere() + .error("Please use the 'compiler: \"g++\"' target property \n" + "for ROS serialization"); return false; } return true; diff --git a/core/src/main/java/org/lflang/federated/validation/FedValidator.java b/core/src/main/java/org/lflang/federated/validation/FedValidator.java index 9aeb535d2f..fc82733e0d 100644 --- a/core/src/main/java/org/lflang/federated/validation/FedValidator.java +++ b/core/src/main/java/org/lflang/federated/validation/FedValidator.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; @@ -15,7 +15,7 @@ /** Helper class that is used to validate a federated reactor. */ public class FedValidator { - public static void validateFederatedReactor(Reactor reactor, ErrorReporter errorReporter) { + public static void validateFederatedReactor(Reactor reactor, MessageReporter messageReporter) { if (!reactor.isFederated()) return; // Construct the set of excluded reactions for this federate. @@ -41,7 +41,7 @@ public static void validateFederatedReactor(Reactor reactor, ErrorReporter error // Add all the effects that are inputs allVarRefsReferencingFederates.addAll( react.getEffects().stream().filter(it -> it.getVariable() instanceof Input).toList()); - containsAllVarRefs(allVarRefsReferencingFederates, errorReporter); + containsAllVarRefs(allVarRefsReferencingFederates, messageReporter); } } @@ -49,7 +49,7 @@ public static void validateFederatedReactor(Reactor reactor, ErrorReporter error * Check if this federate contains all the {@code varRefs}. If not, report an error using {@code * errorReporter}. */ - private static void containsAllVarRefs(List varRefs, ErrorReporter errorReporter) { + private static void containsAllVarRefs(List varRefs, MessageReporter messageReporter) { var referencesFederate = false; Instantiation instantiation = null; for (VarRef varRef : varRefs) { @@ -57,9 +57,10 @@ private static void containsAllVarRefs(List varRefs, ErrorReporter error instantiation = varRef.getContainer(); referencesFederate = true; } else if (!varRef.getContainer().equals(instantiation)) { - errorReporter.reportError( - varRef, - "Mixed triggers and effects from" + " different federates. This is not permitted"); + messageReporter + .at(varRef) + .error( + "Mixed triggers and effects from" + " different federates. This is not permitted"); } } } diff --git a/core/src/main/java/org/lflang/generator/DiagnosticReporting.java b/core/src/main/java/org/lflang/generator/DiagnosticReporting.java index 871f417a0d..0ebbeb31ac 100644 --- a/core/src/main/java/org/lflang/generator/DiagnosticReporting.java +++ b/core/src/main/java/org/lflang/generator/DiagnosticReporting.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.util.Map; import org.eclipse.lsp4j.DiagnosticSeverity; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; /** * {@code DiagnosticReporting} provides utilities for reporting validation output. @@ -23,10 +23,10 @@ public interface Strategy { * Parse the validation output and report any errors that it contains. * * @param validationOutput any validation output - * @param errorReporter any error reporter + * @param messageReporter any error reporter * @param map the map from generated files to CodeMaps */ - void report(String validationOutput, ErrorReporter errorReporter, Map map); + void report(String validationOutput, MessageReporter messageReporter, Map map); } /** diff --git a/core/src/main/java/org/lflang/generator/DockerComposeGenerator.java b/core/src/main/java/org/lflang/generator/DockerComposeGenerator.java index a3e5028317..1b2dab1c52 100644 --- a/core/src/main/java/org/lflang/generator/DockerComposeGenerator.java +++ b/core/src/main/java/org/lflang/generator/DockerComposeGenerator.java @@ -17,8 +17,11 @@ public class DockerComposeGenerator { /** Path to the docker-compose.yml file. */ protected final Path path; + private final LFGeneratorContext context; + public DockerComposeGenerator(LFGeneratorContext context) { this.path = context.getFileConfig().getSrcGenPath().resolve("docker-compose.yml"); + this.context = context; } /** @@ -112,6 +115,6 @@ public void writeDockerComposeFile(List services, String networkName String.join( "\n", this.generateDockerServices(services), this.generateDockerNetwork(networkName)); FileUtil.writeToFile(contents, path); - System.out.println(getUsageInstructions()); + context.getErrorReporter().nowhere().info(getUsageInstructions()); } } diff --git a/core/src/main/java/org/lflang/generator/DockerData.java b/core/src/main/java/org/lflang/generator/DockerData.java index af29e9e1d5..181c7fd745 100644 --- a/core/src/main/java/org/lflang/generator/DockerData.java +++ b/core/src/main/java/org/lflang/generator/DockerData.java @@ -1,6 +1,7 @@ package org.lflang.generator; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import org.lflang.util.FileUtil; @@ -19,7 +20,14 @@ public class DockerData { /** The name of the service. */ public final String serviceName; - public DockerData(String serviceName, Path dockerFilePath, String dockerFileContent) { + private final LFGeneratorContext context; + + public DockerData( + String serviceName, + Path dockerFilePath, + String dockerFileContent, + LFGeneratorContext context) { + this.context = context; if (!dockerFilePath.toFile().isAbsolute()) { throw new RuntimeException("Cannot use relative docker file path in DockerData instance"); @@ -31,10 +39,8 @@ public DockerData(String serviceName, Path dockerFilePath, String dockerFileCont /** Write a docker file based on this data. */ public void writeDockerFile() throws IOException { - if (dockerFilePath.toFile().exists()) { - dockerFilePath.toFile().delete(); - } + Files.deleteIfExists(dockerFilePath); FileUtil.writeToFile(dockerFileContent, dockerFilePath); - System.out.println("Dockerfile written to " + dockerFilePath); + context.getErrorReporter().nowhere().info("Dockerfile written to " + dockerFilePath); } } diff --git a/core/src/main/java/org/lflang/generator/DockerGenerator.java b/core/src/main/java/org/lflang/generator/DockerGenerator.java index cf6044f14f..d7d842d15e 100644 --- a/core/src/main/java/org/lflang/generator/DockerGenerator.java +++ b/core/src/main/java/org/lflang/generator/DockerGenerator.java @@ -38,7 +38,7 @@ public DockerData generateDockerData() { var dockerFilePath = context.getFileConfig().getSrcGenPath().resolve("Dockerfile"); var dockerFileContent = generateDockerFileContent(); - return new DockerData(name.replace("_", ""), dockerFilePath, dockerFileContent); + return new DockerData(name.replace("_", ""), dockerFilePath, dockerFileContent, context); } public static DockerGenerator dockerGeneratorFactory(LFGeneratorContext context) { diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java index c8ebbc38a2..76af5c5c25 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorBase.java +++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java @@ -39,11 +39,12 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.IteratorExtensions; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.MainConflictChecker; +import org.lflang.MessageReporter; import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.ast.ASTUtils; @@ -76,7 +77,7 @@ public abstract class GeneratorBase extends AbstractLFValidator { public ReactorInstance main; /** An error reporter for reporting any errors or warnings during the code generation */ - public ErrorReporter errorReporter; + public MessageReporter messageReporter; //////////////////////////////////////////// //// Protected fields. @@ -163,8 +164,8 @@ public Instantiation getMainDef() { public GeneratorBase(LFGeneratorContext context) { this.context = context; this.targetConfig = context.getTargetConfig(); - this.errorReporter = context.getErrorReporter(); - this.commandFactory = new GeneratorCommandFactory(errorReporter, context.getFileConfig()); + this.messageReporter = context.getErrorReporter(); + this.commandFactory = new GeneratorCommandFactory(messageReporter, context.getFileConfig()); } /** @@ -219,7 +220,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Clear any IDE markers that may have been created by a previous build. // Markers mark problems in the Eclipse IDE when running in integrated mode. - errorReporter.clearHistory(); + messageReporter.clearHistory(); ASTUtils.setMainName(context.getFileConfig().resource, context.getFileConfig().name); @@ -228,8 +229,8 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Check if there are any conflicting main reactors elsewhere in the package. if (Objects.equal(context.getMode(), LFGeneratorContext.Mode.STANDALONE) && mainDef != null) { for (String conflict : new MainConflictChecker(context.getFileConfig()).conflicts) { - errorReporter.reportError( - this.mainDef.getReactorClass(), "Conflicting main reactor in " + conflict); + EObject object = this.mainDef.getReactorClass(); + messageReporter.at(object).error("Conflicting main reactor in " + conflict); } } @@ -263,10 +264,13 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { .map( it -> GeneratorUtils.getLFResource( - it, context.getFileConfig().getSrcGenBasePath(), context, errorReporter)) + it, context.getFileConfig().getSrcGenBasePath(), context, messageReporter)) .toList()); GeneratorUtils.accommodatePhysicalActionsIfPresent( - allResources, getTarget().setsKeepAliveOptionAutomatically(), targetConfig, errorReporter); + allResources, + getTarget().setsKeepAliveOptionAutomatically(), + targetConfig, + messageReporter); // FIXME: Should the GeneratorBase pull in {@code files} from imported // resources? @@ -347,7 +351,7 @@ protected void setReactorsAndInstantiationGraph(LFGeneratorContext.Mode mode) { */ protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { var dst = this.context.getFileConfig().getSrcGenPath(); - FileUtil.copyFilesOrDirectories(targetConfig.files, dst, fileConfig, errorReporter, false); + FileUtil.copyFilesOrDirectories(targetConfig.files, dst, fileConfig, messageReporter, false); } /** @@ -357,7 +361,7 @@ protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { * @return True if errors occurred. */ public boolean errorsOccurred() { - return errorReporter.getErrorsOccurred(); + return messageReporter.getErrorsOccurred(); } /* @@ -425,9 +429,11 @@ public int getReactionBankIndex(Reaction reaction) { */ protected void checkModalReactorSupport(boolean isSupported) { if (hasModalReactors && !isSupported) { - errorReporter.reportError( - "The currently selected code generation or " - + "target configuration does not support modal reactors!"); + messageReporter + .nowhere() + .error( + "The currently selected code generation or " + + "target configuration does not support modal reactors!"); } } @@ -439,8 +445,9 @@ protected void checkModalReactorSupport(boolean isSupported) { */ protected void checkWatchdogSupport(boolean isSupported) { if (hasWatchdogs && !isSupported) { - errorReporter.reportError( - "Watchdogs are currently only supported for threaded programs in the C target."); + messageReporter + .nowhere() + .error("Watchdogs are currently only supported for threaded programs in the C target."); } } @@ -460,10 +467,11 @@ private void transformConflictingConnectionsInModalReactors() { || connection.isIterated() || connection.getLeftPorts().size() > 1 || connection.getRightPorts().size() > 1) { - errorReporter.reportError( - connection, - "Cannot transform connection in modal reactor. Connection uses currently not" - + " supported features."); + messageReporter + .at(connection) + .error( + "Cannot transform connection in modal reactor. Connection uses currently not" + + " supported features."); } else { var reaction = factory.createReaction(); ((Mode) connection.eContainer()).getReactions().add(reaction); @@ -498,10 +506,12 @@ private void transformConflictingConnectionsInModalReactors() { * reactors. */ protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - errorReporter.reportError( - "The currently selected code generation " - + "is missing an implementation for conflicting " - + "transforming connections in modal reactors."); + messageReporter + .nowhere() + .error( + "The currently selected code generation " + + "is missing an implementation for conflicting " + + "transforming connections in modal reactors."); return "MODAL MODELS NOT SUPPORTED"; } @@ -573,9 +583,7 @@ public void reportCommandErrors(String stderr) { // Found a new line number designator. // If there is a previously accumulated message, report it. if (message.length() > 0) { - if (severity == IMarker.SEVERITY_ERROR) - errorReporter.reportError(path, lineNumber, message.toString()); - else errorReporter.reportWarning(path, lineNumber, message.toString()); + reportIssue(message, lineNumber, path, severity); if (!Objects.equal(originalPath.toFile(), path.toFile())) { // Report an error also in the top-level resource. @@ -583,9 +591,9 @@ public void reportCommandErrors(String stderr) { // statements to find which one matches and mark all the // import statements down the chain. But what a pain! if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); + messageReporter.at(originalPath).error("Error in imported file: " + path); } else { - errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); + messageReporter.at(originalPath).warning("Warning in imported file: " + path); } } } @@ -623,11 +631,7 @@ public void reportCommandErrors(String stderr) { } } if (message.length() > 0) { - if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(path, lineNumber, message.toString()); - } else { - errorReporter.reportWarning(path, lineNumber, message.toString()); - } + reportIssue(message, lineNumber, path, severity); if (originalPath.toFile() != path.toFile()) { // Report an error also in the top-level resource. @@ -635,14 +639,20 @@ public void reportCommandErrors(String stderr) { // statements to find which one matches and mark all the // import statements down the chain. But what a pain! if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); + messageReporter.at(originalPath).error("Error in imported file: " + path); } else { - errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); + messageReporter.at(originalPath).warning("Warning in imported file: " + path); } } } } + private void reportIssue(StringBuilder message, Integer lineNumber, Path path, int severity) { + DiagnosticSeverity convertedSeverity = + severity == IMarker.SEVERITY_ERROR ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning; + messageReporter.atNullableLine(path, lineNumber).report(convertedSeverity, message.toString()); + } + // ////////////////////////////////////////////////// // // Private functions @@ -651,10 +661,13 @@ public void reportCommandErrors(String stderr) { * is in, and where the generated sources are to be put. */ public void printInfo(LFGeneratorContext.Mode mode) { - System.out.println( - "Generating code for: " + context.getFileConfig().resource.getURI().toString()); - System.out.println("******** mode: " + mode); - System.out.println("******** generated sources: " + context.getFileConfig().getSrcGenPath()); + messageReporter + .nowhere() + .info("Generating code for: " + context.getFileConfig().resource.getURI().toString()); + messageReporter.nowhere().info("******** mode: " + mode); + messageReporter + .nowhere() + .info("******** generated sources: " + context.getFileConfig().getSrcGenPath()); } /** Get the buffer type used for network messages */ diff --git a/core/src/main/java/org/lflang/generator/GeneratorCommandFactory.java b/core/src/main/java/org/lflang/generator/GeneratorCommandFactory.java index b1dc44e094..7e9575b219 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorCommandFactory.java +++ b/core/src/main/java/org/lflang/generator/GeneratorCommandFactory.java @@ -25,12 +25,16 @@ package org.lflang.generator; +import static org.eclipse.lsp4j.DiagnosticSeverity.Error; +import static org.eclipse.lsp4j.DiagnosticSeverity.Warning; + import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Objects; -import org.lflang.ErrorReporter; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.util.LFCommand; /** @@ -43,12 +47,12 @@ */ public class GeneratorCommandFactory { - protected final ErrorReporter errorReporter; + protected final MessageReporter messageReporter; protected final FileConfig fileConfig; protected boolean quiet = false; - public GeneratorCommandFactory(ErrorReporter errorReporter, FileConfig fileConfig) { - this.errorReporter = Objects.requireNonNull(errorReporter); + public GeneratorCommandFactory(MessageReporter messageReporter, FileConfig fileConfig) { + this.messageReporter = Objects.requireNonNull(messageReporter); this.fileConfig = Objects.requireNonNull(fileConfig); } @@ -144,11 +148,9 @@ public LFCommand createCommand(String cmd, List args, Path dir, boolean + cmd + " is installed. " + "You can set PATH in ~/.bash_profile on Linux or Mac."; - if (failOnError) { - errorReporter.reportError(message); - } else { - errorReporter.reportWarning(message); - } + + DiagnosticSeverity severity = failOnError ? Error : Warning; + messageReporter.nowhere().report(severity, message); } return command; diff --git a/core/src/main/java/org/lflang/generator/GeneratorUtils.java b/core/src/main/java/org/lflang/generator/GeneratorUtils.java index 04ae33ba0c..09cf940fb3 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorUtils.java +++ b/core/src/main/java/org/lflang/generator/GeneratorUtils.java @@ -10,8 +10,8 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.IteratorExtensions; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.TargetProperty; import org.lflang.ast.ASTUtils; @@ -48,7 +48,7 @@ public static void accommodatePhysicalActionsIfPresent( List resources, boolean setsKeepAliveOptionAutomatically, TargetConfig targetConfig, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { if (!setsKeepAliveOptionAutomatically) { return; } @@ -61,11 +61,11 @@ public static void accommodatePhysicalActionsIfPresent( && !targetConfig.keepalive) { // If not, set it to true targetConfig.keepalive = true; - errorReporter.reportWarning( - action, + String message = String.format( "Setting %s to true because of the physical action %s.", - TargetProperty.KEEPALIVE.getDisplayName(), action.getName())); + TargetProperty.KEEPALIVE.getDisplayName(), action.getName()); + messageReporter.at(action).warning(message); return; } } @@ -110,20 +110,20 @@ public static List getResources(Iterable reactors) { * @param srcGenBasePath The root directory for any generated sources associated with the * resource. * @param context The generator invocation context. - * @param errorReporter An error message acceptor. + * @param messageReporter An error message acceptor. * @return the {@code LFResource} representation of the given resource. */ public static LFResource getLFResource( Resource resource, Path srcGenBasePath, LFGeneratorContext context, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var target = ASTUtils.targetDecl(resource); KeyValuePairs config = target.getConfig(); var targetConfig = new TargetConfig(target); if (config != null) { List pairs = config.getPairs(); - TargetProperty.set(targetConfig, pairs != null ? pairs : List.of(), errorReporter); + TargetProperty.set(targetConfig, pairs != null ? pairs : List.of(), messageReporter); } FileConfig fc = LFGenerator.createFileConfig( @@ -166,7 +166,7 @@ public static boolean isHostWindows() { public static boolean canGenerate( Boolean errorsOccurred, Instantiation mainDef, - ErrorReporter errorReporter, + MessageReporter messageReporter, LFGeneratorContext context) { // stop if there are any errors found in the program by doGenerate() in GeneratorBase if (errorsOccurred) { @@ -175,9 +175,11 @@ public static boolean canGenerate( } // abort if there is no main reactor if (mainDef == null) { - errorReporter.reportInfo( - "INFO: The given Lingua Franca program does not define a main reactor. Therefore, no code" - + " was generated."); + messageReporter + .nowhere() + .info( + "The given Lingua Franca program does not define a main reactor. Therefore, no code" + + " was generated."); context.finish(GeneratorResult.NOTHING); return false; } diff --git a/core/src/main/java/org/lflang/generator/HumanReadableReportingStrategy.java b/core/src/main/java/org/lflang/generator/HumanReadableReportingStrategy.java index 8c1dd1e547..fedfcb6d36 100644 --- a/core/src/main/java/org/lflang/generator/HumanReadableReportingStrategy.java +++ b/core/src/main/java/org/lflang/generator/HumanReadableReportingStrategy.java @@ -7,9 +7,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.lsp4j.DiagnosticSeverity; -import org.eclipse.xtext.xbase.lib.Procedures.Procedure0; -import org.eclipse.xtext.xbase.lib.Procedures.Procedure2; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; /** * An error reporting strategy that parses human-readable output. @@ -68,14 +66,15 @@ public HumanReadableReportingStrategy( } @Override - public void report(String validationOutput, ErrorReporter errorReporter, Map map) { + public void report( + String validationOutput, MessageReporter messageReporter, Map map) { Iterator it = validationOutput.lines().iterator(); while (it.hasNext() || bufferedLine != null) { if (bufferedLine != null) { - reportErrorLine(bufferedLine, it, errorReporter, map); + reportErrorLine(bufferedLine, it, messageReporter, map); bufferedLine = null; } else { - reportErrorLine(it.next(), it, errorReporter, map); + reportErrorLine(it.next(), it, messageReporter, map); } } } @@ -85,75 +84,66 @@ public void report(String validationOutput, ErrorReporter errorReporter, Map it, ErrorReporter errorReporter, Map maps) { + String line, Iterator it, MessageReporter messageReporter, Map maps) { Matcher matcher = diagnosticMessagePattern.matcher(stripEscaped(line)); if (matcher.matches()) { final Path path = Paths.get(matcher.group("path")); + final DiagnosticSeverity severity = DiagnosticReporting.severityOf(matcher.group("severity")); + + String column = matcher.group("column"); final Position generatedFilePosition = Position.fromOneBased( Integer.parseInt(matcher.group("line")), - Integer.parseInt( - matcher.group("column") != null - ? matcher.group("column") - : "0") // FIXME: Unreliable heuristic - ); + column == null + ? 1 // FIXME: Unreliable heuristic + : Integer.parseInt(column)); final String message = DiagnosticReporting.messageOf(matcher.group("message"), path, generatedFilePosition); final CodeMap map = maps.get(relativeTo != null ? relativeTo.resolve(path) : path); - final DiagnosticSeverity severity = DiagnosticReporting.severityOf(matcher.group("severity")); if (map == null) { - errorReporter.report(null, severity, message); + messageReporter.nowhere().report(severity, message); return; } for (Path srcFile : map.lfSourcePaths()) { Position lfFilePosition = map.adjusted(srcFile, generatedFilePosition); - if (matcher.group("column") != null) { - reportAppropriateRange( - (p0, p1) -> errorReporter.report(srcFile, severity, message, p0, p1), - lfFilePosition, - it); + if (column != null) { + Range range = findAppropriateRange(lfFilePosition, it); + messageReporter.at(srcFile, range).report(severity, message); } else { - errorReporter.report(srcFile, severity, message, lfFilePosition.getOneBasedLine()); + messageReporter.at(srcFile, lfFilePosition.getOneBasedLine()).report(severity, message); } } } } /** - * Report the appropriate range to {@code report}. + * Find the appropriate range to {@code report}. * - * @param report A reporting method whose first and second parameters are the (included) start and - * (excluded) end of the relevant range. * @param lfFilePosition The point about which the relevant range is anchored. * @param it An iterator over the lines immediately following a diagnostic message. */ - private void reportAppropriateRange( - Procedure2 report, Position lfFilePosition, Iterator it) { - Procedure0 failGracefully = () -> report.apply(lfFilePosition, lfFilePosition.plus(" ")); - if (!it.hasNext()) { - failGracefully.apply(); - return; - } - String line = it.next(); - Matcher labelMatcher = labelPattern.matcher(line); - if (labelMatcher.find()) { - report.apply( - Position.fromZeroBased( - lfFilePosition.getZeroBasedLine(), - lfFilePosition.getZeroBasedColumn() - labelMatcher.group(1).length()), - lfFilePosition.plus(labelMatcher.group(2))); - return; - } - if (diagnosticMessagePattern.matcher(line).find()) { - failGracefully.apply(); - bufferedLine = line; - return; + private Range findAppropriateRange(Position lfFilePosition, Iterator it) { + while (it.hasNext()) { + String line = it.next(); + Matcher labelMatcher = labelPattern.matcher(line); + if (labelMatcher.find()) { + Position start = + Position.fromZeroBased( + lfFilePosition.getZeroBasedLine(), + lfFilePosition.getZeroBasedColumn() - labelMatcher.group(1).length()); + Position end = lfFilePosition.plus(labelMatcher.group(2)); + return new Range(start, end); + } else if (diagnosticMessagePattern.matcher(line).find()) { + bufferedLine = line; + break; + } } - reportAppropriateRange(report, lfFilePosition, it); + // fallback, we didn't find it. + return new Range(lfFilePosition, lfFilePosition.plus(" ")); } /** diff --git a/core/src/main/java/org/lflang/generator/IntegratedBuilder.java b/core/src/main/java/org/lflang/generator/IntegratedBuilder.java index f87c81728d..41ad7a4c3d 100644 --- a/core/src/main/java/org/lflang/generator/IntegratedBuilder.java +++ b/core/src/main/java/org/lflang/generator/IntegratedBuilder.java @@ -9,6 +9,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.xtext.diagnostics.Severity; import org.eclipse.xtext.generator.GeneratorDelegate; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; @@ -16,8 +17,8 @@ import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.generator.LFGeneratorContext.Mode; /** @@ -36,15 +37,6 @@ public interface ReportProgress { void apply(String message, Integer percentage); } - // Note: This class is not currently used in response to - // document edits, even though the validator and code - // generator are invoked by Xtext in response to - // document edits. - /** A {@code ReportMethod} is a way of reporting issues. */ - private interface ReportMethod { - void apply(Path file, Integer line, String message); - } - /* ---------------------- INJECTED DEPENDENCIES ---------------------- */ @Inject private IResourceValidator validator; @@ -72,12 +64,12 @@ public GeneratorResult run( .toString()); List parseRoots = getResource(uri).getContents(); if (parseRoots.isEmpty()) return GeneratorResult.NOTHING; - ErrorReporter errorReporter = new LanguageServerErrorReporter(parseRoots.get(0)); + MessageReporter messageReporter = new LanguageServerMessageReporter(parseRoots.get(0)); reportProgress.apply("Validating...", START_PERCENT_PROGRESS); - validate(uri, errorReporter); + validate(uri, messageReporter); reportProgress.apply("Code validation complete.", VALIDATED_PERCENT_PROGRESS); if (cancelIndicator.isCanceled()) return GeneratorResult.CANCELLED; - if (errorReporter.getErrorsOccurred()) return GeneratorResult.FAILED; + if (messageReporter.getErrorsOccurred()) return GeneratorResult.FAILED; reportProgress.apply("Generating code...", VALIDATED_PERCENT_PROGRESS); return doGenerate(uri, mustComplete, reportProgress, cancelIndicator); } @@ -88,13 +80,14 @@ public GeneratorResult run( * Validates the Lingua Franca file {@code f}. * * @param uri The URI of a Lingua Franca file. - * @param errorReporter The error reporter. + * @param messageReporter The error reporter. */ - private void validate(URI uri, ErrorReporter errorReporter) { + private void validate(URI uri, MessageReporter messageReporter) { for (Issue issue : validator.validate(getResource(uri), CheckMode.ALL, CancelIndicator.NullImpl)) { - getReportMethod(errorReporter, issue.getSeverity()) - .apply(Path.of(uri.path()), issue.getLineNumber(), issue.getMessage()); + messageReporter + .atNullableLine(Path.of(uri.path()), issue.getLineNumber()) + .report(convertSeverity(issue.getSeverity()), issue.getMessage()); } } @@ -120,7 +113,7 @@ private GeneratorResult doGenerate( new Properties(), resource, fileAccess, - fileConfig -> new LanguageServerErrorReporter(resource.getContents().get(0))); + fileConfig -> new LanguageServerMessageReporter(resource.getContents().get(0))); generator.generate(getResource(uri), fileAccess, context); return context.getResult(); } @@ -135,14 +128,12 @@ private Resource getResource(URI uri) { return resourceSetProvider.get().getResource(uri, true); } - /** - * Returns the appropriate reporting method for the given {@code Severity}. - * - * @param severity An arbitrary {@code Severity}. - * @return The appropriate reporting method for {@code severity}. - */ - private ReportMethod getReportMethod(ErrorReporter errorReporter, Severity severity) { - if (severity == Severity.ERROR) return errorReporter::reportError; - return errorReporter::reportWarning; + static DiagnosticSeverity convertSeverity(Severity severity) { + return switch (severity) { + case ERROR -> DiagnosticSeverity.Error; + case WARNING -> DiagnosticSeverity.Warning; + case INFO -> DiagnosticSeverity.Information; + case IGNORE -> DiagnosticSeverity.Hint; + }; } } diff --git a/core/src/main/java/org/lflang/generator/InvalidSourceException.java b/core/src/main/java/org/lflang/generator/InvalidSourceException.java index 88b65d0c24..f4dc12ae8a 100644 --- a/core/src/main/java/org/lflang/generator/InvalidSourceException.java +++ b/core/src/main/java/org/lflang/generator/InvalidSourceException.java @@ -27,7 +27,7 @@ /** * This exception is thrown when a program fails a validity check performed by a code generator (and * not the validator). This should be thrown only when local control flow cannot recover, otherwise - * using {@link GeneratorBase#errorReporter} should be preferred, in order to collect more errors + * using {@link GeneratorBase#messageReporter} should be preferred, in order to collect more errors * before failing. * * @author Clément Fournier diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index 8427f165f6..261a615b91 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -1,6 +1,7 @@ package org.lflang.generator; import com.google.inject.Inject; +import com.google.inject.Injector; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; @@ -9,8 +10,8 @@ import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.generator.IGeneratorContext; import org.eclipse.xtext.util.RuntimeIOException; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.Target; import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedASTUtils; @@ -32,6 +33,7 @@ public class LFGenerator extends AbstractGenerator { @Inject private LFGlobalScopeProvider scopeProvider; + @Inject private Injector injector; // Indicator of whether generator errors occurred. protected boolean generatorErrorsOccurred = false; @@ -85,6 +87,7 @@ private GeneratorBase createGenerator(LFGeneratorContext context) { @Override public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { + assert injector != null; final LFGeneratorContext lfContext; if (context instanceof LFGeneratorContext) { lfContext = (LFGeneratorContext) context; @@ -97,7 +100,9 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorCont if (FedASTUtils.findFederatedReactor(resource) != null) { try { - generatorErrorsOccurred = (new FedGenerator(lfContext)).doGenerate(resource, lfContext); + FedGenerator fedGenerator = new FedGenerator(lfContext); + injector.injectMembers(fedGenerator); + generatorErrorsOccurred = fedGenerator.doGenerate(resource, lfContext); } catch (IOException e) { throw new RuntimeIOException("Error during federated code generation", e); } @@ -111,9 +116,9 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorCont generatorErrorsOccurred = generator.errorsOccurred(); } } - final ErrorReporter errorReporter = lfContext.getErrorReporter(); - if (errorReporter instanceof LanguageServerErrorReporter) { - ((LanguageServerErrorReporter) errorReporter).publishDiagnostics(); + final MessageReporter messageReporter = lfContext.getErrorReporter(); + if (messageReporter instanceof LanguageServerMessageReporter) { + ((LanguageServerMessageReporter) messageReporter).publishDiagnostics(); } } diff --git a/core/src/main/java/org/lflang/generator/LFGeneratorContext.java b/core/src/main/java/org/lflang/generator/LFGeneratorContext.java index 727ce229b9..cc3b41c6c1 100644 --- a/core/src/main/java/org/lflang/generator/LFGeneratorContext.java +++ b/core/src/main/java/org/lflang/generator/LFGeneratorContext.java @@ -6,8 +6,8 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.generator.IGeneratorContext; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; /** @@ -79,7 +79,7 @@ enum Mode { Properties getArgs(); /** Get the error reporter for this context; construct one if it hasn't been constructed yet. */ - ErrorReporter getErrorReporter(); + MessageReporter getErrorReporter(); /** * Mark the code generation process performed in this context as finished with the result {@code diff --git a/core/src/main/java/org/lflang/generator/LanguageServerErrorReporter.java b/core/src/main/java/org/lflang/generator/LanguageServerMessageReporter.java similarity index 62% rename from core/src/main/java/org/lflang/generator/LanguageServerErrorReporter.java rename to core/src/main/java/org/lflang/generator/LanguageServerMessageReporter.java index bc5f04e0c0..0fd51333b1 100644 --- a/core/src/main/java/org/lflang/generator/LanguageServerErrorReporter.java +++ b/core/src/main/java/org/lflang/generator/LanguageServerMessageReporter.java @@ -14,7 +14,7 @@ import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporterBase; import org.lflang.util.FileUtil; /** @@ -22,7 +22,7 @@ * * @author Peter Donovan */ -public class LanguageServerErrorReporter implements ErrorReporter { +public class LanguageServerMessageReporter extends MessageReporterBase { /** * The language client to which errors should be reported, if such a client is available. FIXME: @@ -35,64 +35,56 @@ public class LanguageServerErrorReporter implements ErrorReporter { /** The list of all diagnostics since the last reset. */ private final Map> diagnostics; - /* ------------------------ CONSTRUCTORS -------------------------- */ - /** * Initialize a {@code DiagnosticAcceptor} for the document whose parse tree root node is {@code * parseRoot}. * * @param parseRoot the root of the AST of the document for which this is an error reporter */ - public LanguageServerErrorReporter(EObject parseRoot) { + public LanguageServerMessageReporter(EObject parseRoot) { this.parseRoot = parseRoot; this.diagnostics = new HashMap<>(); } - /* ----------------------- PUBLIC METHODS ------------------------- */ - - @Override - public String reportError(String message) { - return report(getMainFile(), DiagnosticSeverity.Error, message); - } - - @Override - public String reportWarning(String message) { - return report(getMainFile(), DiagnosticSeverity.Warning, message); - } - - @Override - public String reportInfo(String message) { - return report(getMainFile(), DiagnosticSeverity.Information, message); - } - - @Override - public String reportError(EObject object, String message) { - return reportError(message); - } - - @Override - public String reportWarning(EObject object, String message) { - return reportWarning(message); - } - @Override - public String reportInfo(EObject object, String message) { - return reportInfo(message); + protected void reportOnNode(EObject node, DiagnosticSeverity severity, String message) { + reportWithoutPosition(severity, message); } @Override - public String reportError(Path file, Integer line, String message) { - return report(file, DiagnosticSeverity.Error, message, line != null ? line : 1); + protected void reportWithoutPosition(DiagnosticSeverity severity, String message) { + report( + getMainFile(), + org.lflang.generator.Range.degenerateRange(Position.ORIGIN), + severity, + message); } @Override - public String reportWarning(Path file, Integer line, String message) { - return report(file, DiagnosticSeverity.Warning, message, line != null ? line : 1); + public Stage2 at(Path file, int line) { + // Create a range for the whole line + Optional text = getLine(line - 1); + org.lflang.generator.Range range = + new org.lflang.generator.Range( + Position.fromOneBased(line, 1), + Position.fromOneBased(line, 1 + text.map(String::length).orElse(0))); + return at(file, range); } @Override - public String reportInfo(Path file, Integer line, String message) { - return report(file, DiagnosticSeverity.Information, message, line != null ? line : 1); + protected void report( + Path path, org.lflang.generator.Range range, DiagnosticSeverity severity, String message) { + if (path == null) { + path = getMainFile(); + } + diagnostics + .computeIfAbsent(path, p -> new ArrayList<>()) + .add( + new Diagnostic( + toRange(range.getStartInclusive(), range.getEndExclusive()), + message, + severity, + "LF Language Server")); } @Override @@ -104,40 +96,13 @@ public boolean getErrorsOccurred() { .anyMatch(diagnostic -> diagnostic.getSeverity() == DiagnosticSeverity.Error)); } - @Override - public String report(Path file, DiagnosticSeverity severity, String message) { - return report(file, severity, message, 1); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message, int line) { - Optional text = getLine(line - 1); - return report( - file, - severity, - message, - Position.fromOneBased(line, 1), - Position.fromOneBased(line, 1 + (text.isEmpty() ? 0 : text.get().length()))); - } - - @Override - public String report( - Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { - if (file == null) file = getMainFile(); - diagnostics.putIfAbsent(file, new ArrayList<>()); - diagnostics - .get(file) - .add(new Diagnostic(toRange(startPos, endPos), message, severity, "LF Language Server")); - return "" + severity + ": " + message; - } - /** * Save a reference to the language client. * * @param client the language client */ public static void setClient(LanguageClient client) { - LanguageServerErrorReporter.client = client; + LanguageServerMessageReporter.client = client; } /** Publish diagnostics by forwarding them to the language client. */ diff --git a/core/src/main/java/org/lflang/generator/MainContext.java b/core/src/main/java/org/lflang/generator/MainContext.java index 4a60c4d6a8..7357929c06 100644 --- a/core/src/main/java/org/lflang/generator/MainContext.java +++ b/core/src/main/java/org/lflang/generator/MainContext.java @@ -8,9 +8,9 @@ import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.util.RuntimeIOException; -import org.lflang.DefaultErrorReporter; -import org.lflang.ErrorReporter; +import org.lflang.DefaultMessageReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.generator.IntegratedBuilder.ReportProgress; @@ -23,7 +23,7 @@ public class MainContext implements LFGeneratorContext { /** This constructor will be set by the LF plugin, if the generator is running in Epoch. */ - public static Function EPOCH_ERROR_REPORTER_CONSTRUCTOR = null; + public static Function EPOCH_ERROR_REPORTER_CONSTRUCTOR = null; /** The indicator that shows whether this build process is canceled. */ private final CancelIndicator cancelIndicator; @@ -41,7 +41,7 @@ public class MainContext implements LFGeneratorContext { private GeneratorResult result = null; private final Properties args; - private final ErrorReporter errorReporter; + private final MessageReporter messageReporter; /** * Initialize the context of a build process whose cancellation is indicated by {@code @@ -62,7 +62,7 @@ public MainContext( fsa, (mode == Mode.EPOCH && EPOCH_ERROR_REPORTER_CONSTRUCTOR != null) ? EPOCH_ERROR_REPORTER_CONSTRUCTOR - : fileConfig -> new DefaultErrorReporter()); + : fileConfig -> new DefaultMessageReporter()); } /** @@ -86,7 +86,7 @@ public MainContext( Properties args, Resource resource, IFileSystemAccess2 fsa, - Function constructErrorReporter) { + Function constructErrorReporter) { this.mode = mode; this.cancelIndicator = cancelIndicator == null ? () -> false : cancelIndicator; this.reportProgress = reportProgress; @@ -104,7 +104,7 @@ public MainContext( throw new RuntimeIOException("Error during FileConfig instantiation", e); } - this.errorReporter = constructErrorReporter.apply(this.fileConfig); + this.messageReporter = constructErrorReporter.apply(this.fileConfig); loadTargetConfig(); } @@ -125,8 +125,8 @@ public Properties getArgs() { } @Override - public ErrorReporter getErrorReporter() { - return errorReporter; + public MessageReporter getErrorReporter() { + return messageReporter; } @Override @@ -165,6 +165,6 @@ public void reportProgress(String message, int percentage) { */ public void loadTargetConfig() { this.targetConfig = - new TargetConfig(args, GeneratorUtils.findTargetDecl(fileConfig.resource), errorReporter); + new TargetConfig(args, GeneratorUtils.findTargetDecl(fileConfig.resource), messageReporter); } } diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 60a904ef5b..c9e89f40b0 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -29,7 +29,7 @@ import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.lf.Input; import org.lflang.lf.Output; import org.lflang.lf.Parameter; @@ -70,16 +70,16 @@ public PortInstance(Port definition, ReactorInstance parent) { * * @param definition The declaration in the AST. * @param parent The parent. - * @param errorReporter An error reporter, or null to throw exceptions. + * @param messageReporter An error reporter, or null to throw exceptions. */ - public PortInstance(Port definition, ReactorInstance parent, ErrorReporter errorReporter) { + public PortInstance(Port definition, ReactorInstance parent, MessageReporter messageReporter) { super(definition, parent); if (parent == null) { throw new NullPointerException("Cannot create a PortInstance with no parent."); } - setInitialWidth(errorReporter); + setInitialWidth(messageReporter); } ////////////////////////////////////////////////////// @@ -388,16 +388,16 @@ private List> eventualSources(RuntimeRange { * @param reactor The top-level reactor. * @param reporter The error reporter. */ - public ReactorInstance(Reactor reactor, ErrorReporter reporter, List reactors) { + public ReactorInstance(Reactor reactor, MessageReporter reporter, List reactors) { this(ASTUtils.createInstantiation(reactor), null, reporter, -1, reactors); assert !reactors.isEmpty(); } @@ -101,7 +101,7 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter, List re * @param reactor The top-level reactor. * @param reporter The error reporter. */ - public ReactorInstance(Reactor reactor, ErrorReporter reporter) { + public ReactorInstance(Reactor reactor, MessageReporter reporter) { this(ASTUtils.createInstantiation(reactor), null, reporter, -1, List.of()); } @@ -113,7 +113,7 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter) { * @param reporter The error reporter. * @param desiredDepth The depth to which to go, or -1 to construct the full hierarchy. */ - public ReactorInstance(Reactor reactor, ErrorReporter reporter, int desiredDepth) { + public ReactorInstance(Reactor reactor, MessageReporter reporter, int desiredDepth) { this(ASTUtils.createInstantiation(reactor), null, reporter, desiredDepth, List.of()); } @@ -125,7 +125,7 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter, int desiredDepth * @param parent The parent reactor instance. * @param reporter The error reporter. */ - public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter reporter) { + public ReactorInstance(Reactor reactor, ReactorInstance parent, MessageReporter reporter) { this(ASTUtils.createInstantiation(reactor), parent, reporter, -1, List.of()); } @@ -721,7 +721,7 @@ public TimeValue getTimeValue(Expression expr) { //// Protected fields. /** The generator that created this reactor instance. */ - protected ErrorReporter reporter; // FIXME: This accumulates a lot of redundant references + protected MessageReporter reporter; // FIXME: This accumulates a lot of redundant references /** The map of used built-in triggers. */ protected Map> builtinTriggers = @@ -804,7 +804,7 @@ protected void createWatchdogInstances() { public ReactorInstance( Instantiation definition, ReactorInstance parent, - ErrorReporter reporter, + MessageReporter reporter, int desiredDepth, List reactors) { super(definition, parent); @@ -839,13 +839,13 @@ public ReactorInstance( this.recursive = foundSelfAsParent; if (recursive) { - reporter.reportError(definition, "Recursive reactor instantiation."); + reporter.at(definition).error("Recursive reactor instantiation."); } // If the reactor definition is null, give up here. Otherwise, diagram generation // will fail an NPE. if (reactorDefinition == null) { - reporter.reportError(definition, "Reactor instantiation has no matching reactor definition."); + reporter.at(definition).error("Reactor instantiation has no matching reactor definition."); return; } @@ -944,11 +944,11 @@ private void establishPortConnections() { // Check for empty lists. if (!srcRanges.hasNext()) { if (dstRanges.hasNext()) { - reporter.reportWarning(connection, "No sources to provide inputs."); + reporter.at(connection).warning("No sources to provide inputs."); } return; } else if (!dstRanges.hasNext()) { - reporter.reportWarning(connection, "No destination. Outputs will be lost."); + reporter.at(connection).warning("No destination. Outputs will be lost."); return; } @@ -961,8 +961,9 @@ private void establishPortConnections() { if (!dstRanges.hasNext()) { if (srcRanges.hasNext()) { // Should not happen (checked by the validator). - reporter.reportWarning( - connection, "Source is wider than the destination. Outputs will be lost."); + reporter + .at(connection) + .warning("Source is wider than the destination. Outputs will be lost."); } break; } @@ -972,8 +973,9 @@ private void establishPortConnections() { } else { if (dstRanges.hasNext()) { // Should not happen (checked by the validator). - reporter.reportWarning( - connection, "Destination is wider than the source. Inputs will be missing."); + reporter + .at(connection) + .warning("Destination is wider than the source. Inputs will be missing."); } break; } @@ -986,8 +988,9 @@ private void establishPortConnections() { src = src.tail(dst.width); if (!dstRanges.hasNext()) { // Should not happen (checked by the validator). - reporter.reportWarning( - connection, "Source is wider than the destination. Outputs will be lost."); + reporter + .at(connection) + .warning("Source is wider than the destination. Outputs will be lost."); break; } dst = dstRanges.next(); @@ -999,8 +1002,9 @@ private void establishPortConnections() { if (connection.isIterated()) { srcRanges = leftPorts.iterator(); } else { - reporter.reportWarning( - connection, "Destination is wider than the source. Inputs will be missing."); + reporter + .at(connection) + .warning("Destination is wider than the source. Inputs will be missing."); break; } } @@ -1063,7 +1067,7 @@ private List> listPortInstances( for (VarRef portRef : references) { // Simple error checking first. if (!(portRef.getVariable() instanceof Port)) { - reporter.reportError(portRef, "Not a port."); + reporter.at(portRef).error("Not a port."); return result; } // First, figure out which reactor we are dealing with. diff --git a/core/src/main/java/org/lflang/generator/SubContext.java b/core/src/main/java/org/lflang/generator/SubContext.java index cf90cb1fda..9c2bb24e5c 100644 --- a/core/src/main/java/org/lflang/generator/SubContext.java +++ b/core/src/main/java/org/lflang/generator/SubContext.java @@ -2,8 +2,8 @@ import java.util.Properties; import org.eclipse.xtext.util.CancelIndicator; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; /** @@ -20,7 +20,7 @@ public class SubContext implements LFGeneratorContext { private final int endPercentProgress; private GeneratorResult result = null; - protected ErrorReporter errorReporter; + protected MessageReporter messageReporter; /** * Initializes the context within {@code containingContext} of the process that extends from @@ -55,7 +55,7 @@ public Properties getArgs() { } @Override - public ErrorReporter getErrorReporter() { + public MessageReporter getErrorReporter() { return containingContext.getErrorReporter(); } diff --git a/core/src/main/java/org/lflang/generator/TimerInstance.java b/core/src/main/java/org/lflang/generator/TimerInstance.java index b25552bb6c..1f4626f5be 100644 --- a/core/src/main/java/org/lflang/generator/TimerInstance.java +++ b/core/src/main/java/org/lflang/generator/TimerInstance.java @@ -63,14 +63,14 @@ public TimerInstance(Timer definition, ReactorInstance parent) { try { this.offset = parent.getTimeValue(definition.getOffset()); } catch (IllegalArgumentException ex) { - parent.reporter.reportError(definition.getOffset(), "Invalid time."); + parent.reporter.at(definition.getOffset()).error("Invalid time."); } } if (definition.getPeriod() != null) { try { this.period = parent.getTimeValue(definition.getPeriod()); } catch (IllegalArgumentException ex) { - parent.reporter.reportError(definition.getPeriod(), "Invalid time."); + parent.reporter.at(definition.getPeriod()).error("Invalid time."); } } } diff --git a/core/src/main/java/org/lflang/generator/Validator.java b/core/src/main/java/org/lflang/generator/Validator.java index f9ae61f854..33eaec4238 100644 --- a/core/src/main/java/org/lflang/generator/Validator.java +++ b/core/src/main/java/org/lflang/generator/Validator.java @@ -15,7 +15,7 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import org.eclipse.xtext.util.CancelIndicator; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.util.LFCommand; /** @@ -43,15 +43,15 @@ public Pair(S first, T second) { } } - protected final ErrorReporter errorReporter; + protected final MessageReporter messageReporter; protected final ImmutableMap codeMaps; /** * Initialize a {@code Validator} that reports errors to {@code errorReporter} and adjusts * document positions using {@code codeMaps}. */ - protected Validator(ErrorReporter errorReporter, Map codeMaps) { - this.errorReporter = errorReporter; + protected Validator(MessageReporter messageReporter, Map codeMaps) { + this.messageReporter = messageReporter; this.codeMaps = ImmutableMap.copyOf(codeMaps); } @@ -77,11 +77,11 @@ public final void doValidate(LFGeneratorContext context) f.get() .first .getErrorReportingStrategy() - .report(f.get().second.getErrors().toString(), errorReporter, codeMaps); + .report(f.get().second.getErrors(), messageReporter, codeMaps); f.get() .first .getOutputReportingStrategy() - .report(f.get().second.getOutput().toString(), errorReporter, codeMaps); + .report(f.get().second.getOutput(), messageReporter, codeMaps); } } @@ -141,12 +141,8 @@ private static List> getFutures(List> tasks) */ public final int run(LFCommand command, CancelIndicator cancelIndicator) { final int returnCode = command.run(cancelIndicator); - getBuildReportingStrategies() - .first - .report(command.getErrors().toString(), errorReporter, codeMaps); - getBuildReportingStrategies() - .second - .report(command.getOutput().toString(), errorReporter, codeMaps); + getBuildReportingStrategies().first.report(command.getErrors(), messageReporter, codeMaps); + getBuildReportingStrategies().second.report(command.getOutput(), messageReporter, codeMaps); return returnCode; } diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index fd818b45a5..7b6cb006c2 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -31,8 +31,8 @@ import java.util.List; import java.util.Objects; import java.util.stream.Stream; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.TargetProperty.Platform; import org.lflang.generator.CodeBuilder; @@ -86,7 +86,7 @@ public CCmakeGenerator( * * @param sources A list of .c files to build. * @param executableName The name of the output executable. - * @param errorReporter Used to report errors. + * @param messageReporter Used to report errors. * @param CppMode Indicate if the compilation should happen in C++ mode * @param hasMain Indicate if the .lf file has a main reactor or not. If not, a library target * will be created instead of an executable. @@ -97,7 +97,7 @@ public CCmakeGenerator( CodeBuilder generateCMakeCode( List sources, String executableName, - ErrorReporter errorReporter, + MessageReporter messageReporter, boolean CppMode, boolean hasMain, String cMakeExtras, @@ -312,9 +312,11 @@ CodeBuilder generateCMakeCode( break; } default: - errorReporter.reportWarning( - "Using the flags target property with cmake is dangerous.\n" - + " Use cmake-include instead."); + messageReporter + .nowhere() + .warning( + "Using the flags target property with cmake is dangerous.\n" + + " Use cmake-include instead."); cMakeCode.pr("add_compile_options( " + compilerFlag + " )"); cMakeCode.pr("add_link_options( " + compilerFlag + ")"); } diff --git a/core/src/main/java/org/lflang/generator/c/CCompiler.java b/core/src/main/java/org/lflang/generator/c/CCompiler.java index 27bb06af14..844b2f6682 100644 --- a/core/src/main/java/org/lflang/generator/c/CCompiler.java +++ b/core/src/main/java/org/lflang/generator/c/CCompiler.java @@ -33,8 +33,8 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; @@ -60,7 +60,7 @@ public class CCompiler { FileConfig fileConfig; TargetConfig targetConfig; - ErrorReporter errorReporter; + MessageReporter messageReporter; /** * Indicate whether the compiler is in C++ mode. In C++ mode, the compiler produces .cpp files @@ -76,18 +76,18 @@ public class CCompiler { * * @param targetConfig The current target configuration. * @param fileConfig The current file configuration. - * @param errorReporter Used to report errors. + * @param messageReporter Used to report errors. * @param cppMode Whether the generated code should be compiled as if it were C++. */ public CCompiler( TargetConfig targetConfig, FileConfig fileConfig, - ErrorReporter errorReporter, + MessageReporter messageReporter, boolean cppMode) { this.fileConfig = fileConfig; this.targetConfig = targetConfig; - this.errorReporter = errorReporter; - this.commandFactory = new GeneratorCommandFactory(errorReporter, fileConfig); + this.messageReporter = messageReporter; + this.commandFactory = new GeneratorCommandFactory(messageReporter, fileConfig); this.cppMode = cppMode; } @@ -133,17 +133,18 @@ public boolean runCCompiler(GeneratorBase generator, LFGeneratorContext context) if (cMakeReturnCode != 0 && context.getMode() == LFGeneratorContext.Mode.STANDALONE - && !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { - errorReporter.reportError( - targetConfig.compiler + " failed with error code " + cMakeReturnCode); + && !outputContainsKnownCMakeErrors(compile.getErrors())) { + messageReporter + .nowhere() + .error(targetConfig.compiler + " failed with error code " + cMakeReturnCode); } // For warnings (vs. errors), the return code is 0. // But we still want to mark the IDE. - if (compile.getErrors().toString().length() > 0 + if (compile.getErrors().length() > 0 && context.getMode() != LFGeneratorContext.Mode.STANDALONE - && !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { - generator.reportCommandErrors(compile.getErrors().toString()); + && !outputContainsKnownCMakeErrors(compile.getErrors())) { + generator.reportCommandErrors(compile.getErrors()); } int makeReturnCode = 0; @@ -155,35 +156,38 @@ public boolean runCCompiler(GeneratorBase generator, LFGeneratorContext context) if (makeReturnCode != 0 && context.getMode() == LFGeneratorContext.Mode.STANDALONE - && !outputContainsKnownCMakeErrors(build.getErrors().toString())) { - errorReporter.reportError( - targetConfig.compiler + " failed with error code " + makeReturnCode); + && !outputContainsKnownCMakeErrors(build.getErrors())) { + messageReporter + .nowhere() + .error(targetConfig.compiler + " failed with error code " + makeReturnCode); } // For warnings (vs. errors), the return code is 0. // But we still want to mark the IDE. - if (build.getErrors().toString().length() > 0 + if (build.getErrors().length() > 0 && context.getMode() != LFGeneratorContext.Mode.STANDALONE - && !outputContainsKnownCMakeErrors(build.getErrors().toString())) { - generator.reportCommandErrors(build.getErrors().toString()); + && !outputContainsKnownCMakeErrors(build.getErrors())) { + generator.reportCommandErrors(build.getErrors()); } - if (makeReturnCode == 0 && build.getErrors().toString().length() == 0) { - System.out.println( - "SUCCESS: Compiling generated code for " - + fileConfig.name - + " finished with no errors."); + if (makeReturnCode == 0 && build.getErrors().length() == 0) { + messageReporter + .nowhere() + .info( + "SUCCESS: Compiling generated code for " + + fileConfig.name + + " finished with no errors."); } if (targetConfig.platformOptions.platform == Platform.ZEPHYR && targetConfig.platformOptions.flash) { - System.out.println("Invoking flash command for Zephyr"); + messageReporter.nowhere().info("Invoking flash command for Zephyr"); LFCommand flash = buildWestFlashCommand(); int flashRet = flash.run(); if (flashRet != 0) { - errorReporter.reportError("West flash command failed with error code " + flashRet); + messageReporter.nowhere().error("West flash command failed with error code " + flashRet); } else { - System.out.println("SUCCESS: Flashed application with west"); + messageReporter.nowhere().info("SUCCESS: Flashed application with west"); } } } @@ -201,11 +205,13 @@ public LFCommand compileCmakeCommand() { LFCommand command = commandFactory.createCommand("cmake", cmakeOptions(targetConfig, fileConfig), buildPath); if (command == null) { - errorReporter.reportError( - "The C/CCpp target requires CMAKE >= " - + CCmakeGenerator.MIN_CMAKE_VERSION - + " to compile the generated code. " - + "Auto-compiling can be disabled using the \"no-compile: true\" target property."); + messageReporter + .nowhere() + .error( + "The C/CCpp target requires CMAKE >= " + + CCmakeGenerator.MIN_CMAKE_VERSION + + " to compile the generated code. Auto-compiling can be disabled using the" + + " \"no-compile: true\" target property."); } return command; } @@ -292,9 +298,12 @@ public LFCommand buildCmakeCommand() { buildTypeToCmakeConfig(targetConfig.cmakeBuildType)), buildPath); if (command == null) { - errorReporter.reportError( - "The C/CCpp target requires CMAKE >= 3.5 to compile the generated code. " - + "Auto-compiling can be disabled using the \"no-compile: true\" target property."); + messageReporter + .nowhere() + .error( + "The C/CCpp target requires CMAKE >= 3.5 to compile the generated code." + + " Auto-compiling can be disabled using the \"no-compile: true\" target" + + " property."); } return command; } @@ -315,7 +324,7 @@ public LFCommand buildWestFlashCommand() { cmd = commandFactory.createCommand("west", List.of("flash"), buildPath); } if (cmd == null) { - errorReporter.reportError("Could not create west flash command."); + messageReporter.nowhere().error("Could not create west flash command."); } return cmd; @@ -341,14 +350,18 @@ private boolean outputContainsKnownCMakeErrors(String CMakeOutput) { if (CMakeOutput.contains("The CMAKE_C_COMPILER is set to a C++ compiler")) { // If so, print an appropriate error message if (targetConfig.compiler != null) { - errorReporter.reportError( - "A C++ compiler was requested in the compiler target property." - + " Use the CCpp or the Cpp target instead."); + messageReporter + .nowhere() + .error( + "A C++ compiler was requested in the compiler target property." + + " Use the CCpp or the Cpp target instead."); } else { - errorReporter.reportError( - "\"A C++ compiler was detected." - + " The C target works best with a C compiler." - + " Use the CCpp or the Cpp target instead.\""); + messageReporter + .nowhere() + .error( + "\"A C++ compiler was detected." + + " The C target works best with a C compiler." + + " Use the CCpp or the Cpp target instead.\""); } return true; } @@ -416,9 +429,11 @@ public LFCommand compileCCommand(String fileToCompile, boolean noBinary) { LFCommand command = commandFactory.createCommand(targetConfig.compiler, compileArgs, fileConfig.getOutPath()); if (command == null) { - errorReporter.reportError( - "The C/CCpp target requires GCC >= 7 to compile the generated code. " - + "Auto-compiling can be disabled using the \"no-compile: true\" target property."); + messageReporter + .nowhere() + .error( + "The C/CCpp target requires GCC >= 7 to compile the generated code. Auto-compiling" + + " can be disabled using the \"no-compile: true\" target property."); } return command; } diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index ad58403e1b..dd5e6feb97 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -349,11 +349,11 @@ public void accommodatePhysicalActionsIfPresent() { if (!targetConfig.threading && !targetConfig.setByUser.contains(TargetProperty.THREADING)) { targetConfig.threading = true; - errorReporter.reportWarning( - action, + String message = "Using the threaded C runtime to allow for asynchronous handling of physical action" + " " - + action.getName()); + + action.getName(); + messageReporter.at(action).warning(message); return; } } @@ -368,9 +368,11 @@ public void accommodatePhysicalActionsIfPresent() { protected boolean isOSCompatible() { if (GeneratorUtils.isHostWindows()) { if (CCppMode) { - errorReporter.reportError( - "LF programs with a CCpp target are currently not supported on Windows. " - + "Exiting code generation."); + messageReporter + .nowhere() + .error( + "LF programs with a CCpp target are currently not supported on Windows. " + + "Exiting code generation."); return false; } } @@ -388,7 +390,7 @@ protected boolean isOSCompatible() { @Override public void doGenerate(Resource resource, LFGeneratorContext context) { super.doGenerate(resource, context); - if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return; + if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, messageReporter, context)) return; if (!isOSCompatible()) return; // Incompatible OS and configuration // Perform set up that does not generate code @@ -409,9 +411,11 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { generateHeaders(); code.writeToFile(targetFile); } catch (IOException e) { - errorReporter.reportError(e.getMessage()); + String message = e.getMessage(); + messageReporter.nowhere().error(message); } catch (RuntimeException e) { - errorReporter.reportError(e.getMessage()); + String message = e.getMessage(); + messageReporter.nowhere().error(message); throw e; } @@ -439,7 +443,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { cmakeGenerator.generateCMakeCode( sources, lfModuleName, - errorReporter, + messageReporter, CCppMode, mainDef != null, cMakeExtras, @@ -455,32 +459,35 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { Path include = fileConfig.getSrcGenPath().resolve("include/"); Path src = fileConfig.getSrcGenPath().resolve("src/"); FileUtil.arduinoDeleteHelper(src, targetConfig.threading); - FileUtil.relativeIncludeHelper(src, include); - FileUtil.relativeIncludeHelper(include, include); + FileUtil.relativeIncludeHelper(src, include, messageReporter); + FileUtil.relativeIncludeHelper(include, include, messageReporter); } catch (IOException e) { //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored Exceptions.sneakyThrow(e); } if (!targetConfig.noCompile) { - ArduinoUtil arduinoUtil = new ArduinoUtil(context, commandFactory, errorReporter); + ArduinoUtil arduinoUtil = new ArduinoUtil(context, commandFactory, messageReporter); arduinoUtil.buildArduino(fileConfig, targetConfig); context.finish(GeneratorResult.Status.COMPILED, null); } else { - System.out.println("********"); - System.out.println( - "To compile your program, run the following command to see information about the board" - + " you plugged in:\n\n" - + "\tarduino-cli board list\n\n" - + "Grab the FQBN and PORT from the command and run the following command in the" - + " generated sources directory:\n\n" - + "\tarduino-cli compile -b --build-property" - + " compiler.c.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" - + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' --build-property" - + " compiler.cpp.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" - + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' .\n\n" - + "To flash/upload your generated sketch to the board, run the following command in" - + " the generated sources directory:\n\n" - + "\tarduino-cli upload -b -p \n"); + messageReporter.nowhere().info("********"); + messageReporter + .nowhere() + .info( + "To compile your program, run the following command to see information about the" + + " board you plugged in:\n\n" + + "\tarduino-cli board list\n\n" + + "Grab the FQBN and PORT from the command and run the following command in the" + + " generated sources directory:\n\n" + + "\tarduino-cli compile -b --build-property" + + " compiler.c.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" + + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10'" + + " --build-property compiler.cpp.extra_flags='-DLF_UNTHREADED" + + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10" + + " -DINITIAL_REACT_QUEUE_SIZE=10' .\n\n" + + "To flash/upload your generated sketch to the board, run the following" + + " command in the generated sources directory:\n\n" + + "\tarduino-cli upload -b -p \n"); // System.out.println("For a list of all boards installed on your computer, you can use the // following command:\n\n\tarduino-cli board listall\n"); context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); @@ -554,7 +561,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // ); // FIXME: Move to FedGenerator // Create the compiler to be used later - var cCompiler = new CCompiler(targetConfig, threadFileConfig, errorReporter, CppMode); + var cCompiler = new CCompiler(targetConfig, threadFileConfig, messageReporter, CppMode); try { if (!cCompiler.runCCompiler(generator, context)) { // If compilation failed, remove any bin files that may have been created. @@ -579,13 +586,13 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { fileConfig, targetConfig, commandFactory, - errorReporter, + messageReporter, this::reportCommandErrors, context.getMode()); context.finish(GeneratorResult.Status.COMPILED, null); } if (!errorsOccurred()) { - System.out.println("Compiled binary is in " + fileConfig.binPath); + messageReporter.nowhere().info("Compiled binary is in " + fileConfig.binPath); } } else { context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); @@ -762,7 +769,7 @@ protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { var destination = this.fileConfig.getSrcGenPath(); FileUtil.copyFilesOrDirectories( - targetConfig.cmakeIncludes, destination, fileConfig, errorReporter, true); + targetConfig.cmakeIncludes, destination, fileConfig, messageReporter, true); // FIXME: Unclear what the following does, but it does not appear to belong here. if (!StringExtensions.isNullOrEmpty(targetConfig.fedSetupPreamble)) { @@ -771,8 +778,9 @@ protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { fileConfig.srcFile.getParent().resolve(targetConfig.fedSetupPreamble), destination.resolve(targetConfig.fedSetupPreamble)); } catch (IOException e) { - errorReporter.reportError( - "Failed to find _fed_setup file " + targetConfig.fedSetupPreamble); + messageReporter + .nowhere() + .error("Failed to find _fed_setup file " + targetConfig.fedSetupPreamble); } } } @@ -829,7 +837,7 @@ private void generateHeaders() throws IOException { it.tpr, p, getTarget(), - errorReporter, + messageReporter, types, new CodeBuilder(), true, @@ -882,7 +890,7 @@ private void pickCompilePlatform() { if (targetConfig.platformOptions.platform != Platform.AUTO) { osName = targetConfig.platformOptions.platform.toString(); } else if (Stream.of("mac", "darwin", "win", "nux").noneMatch(osName::contains)) { - errorReporter.reportError("Platform " + osName + " is not supported"); + messageReporter.nowhere().error("Platform " + osName + " is not supported"); } } @@ -987,7 +995,7 @@ private void generateReactorClassBody( generateAuxiliaryStructs(header, tpr, false); // The following must go before the self struct so the #include watchdog.h ends up in the // header. - CWatchdogGenerator.generateWatchdogs(src, header, tpr, errorReporter); + CWatchdogGenerator.generateWatchdogs(src, header, tpr, messageReporter); generateSelfStruct(header, tpr, constructorCode); generateMethods(src, tpr); generateReactions(src, tpr); @@ -1059,7 +1067,7 @@ protected void generateAuxiliaryStructs( for (Port p : allPorts(tpr.reactor())) { builder.pr( CPortGenerator.generateAuxiliaryStruct( - tpr, p, getTarget(), errorReporter, types, federatedExtension, userFacing, null)); + tpr, p, getTarget(), messageReporter, types, federatedExtension, userFacing, null)); } // The very first item on this struct needs to be // a trigger_t* because the struct will be cast to (trigger_t*) @@ -1328,7 +1336,7 @@ protected void generateReaction( tpr, reactionIndex, mainDef, - errorReporter, + messageReporter, types, targetConfig, getTarget().requiresTypes)); @@ -1615,7 +1623,7 @@ public void processProtoFile(String filename) { List.of("--c_out=" + this.fileConfig.getSrcGenPath(), filename), fileConfig.srcPath); if (protoc == null) { - errorReporter.reportError("Processing .proto files requires protoc-c >= 1.3.3."); + messageReporter.nowhere().error("Processing .proto files requires protoc-c >= 1.3.3."); return; } var returnCode = protoc.run(); @@ -1628,7 +1636,7 @@ public void processProtoFile(String filename) { targetConfig.compileLibraries.add("protobuf-c"); targetConfig.compilerFlags.add("-lprotobuf-c"); } else { - errorReporter.reportError("protoc-c returns error code " + returnCode); + messageReporter.nowhere().error("protoc-c returns error code " + returnCode); } } @@ -1948,19 +1956,24 @@ protected void setUpGeneralParameters() { && (targetConfig.platformOptions.board == null || !targetConfig.platformOptions.board.contains("mbed"))) { // non-MBED boards should not use threading - System.out.println( - "Threading is incompatible on your current Arduino flavor. Setting threading to false."); + messageReporter + .nowhere() + .info( + "Threading is incompatible on your current Arduino flavor. Setting threading to" + + " false."); targetConfig.threading = false; } if (targetConfig.platformOptions.platform == Platform.ARDUINO && !targetConfig.noCompile && targetConfig.platformOptions.board == null) { - System.out.println( - "To enable compilation for the Arduino platform, you must specify the fully-qualified" - + " board name (FQBN) in the target property. For example, platform: {name: arduino," - + " board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target" - + " code only."); + messageReporter + .nowhere() + .info( + "To enable compilation for the Arduino platform, you must specify the fully-qualified" + + " board name (FQBN) in the target property. For example, platform: {name:" + + " arduino, board: arduino:avr:leonardo}. Entering \"no-compile\" mode and" + + " generating target code only."); targetConfig.noCompile = true; } @@ -1971,9 +1984,11 @@ protected void setUpGeneralParameters() { PlatformOption.USER_THREADS.name(), String.valueOf(targetConfig.platformOptions.userThreads)); } else if (targetConfig.platformOptions.userThreads > 0) { - errorReporter.reportWarning( - "Specifying user threads is only for threaded Lingua Franca on the Zephyr platform. This" - + " option will be ignored."); + messageReporter + .nowhere() + .warning( + "Specifying user threads is only for threaded Lingua Franca on the Zephyr platform." + + " This option will be ignored."); } if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake @@ -2088,10 +2103,12 @@ private void createMainReactorInstance() { if (this.main == null) { // Recursively build instances. this.main = - new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter, reactors); + new ReactorInstance(toDefinition(mainDef.getReactorClass()), messageReporter, reactors); var reactionInstanceGraph = this.main.assignLevels(); if (reactionInstanceGraph.nodeCount() > 0) { - errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); + messageReporter + .nowhere() + .error("Main reactor has causality cycles. Skipping code generation."); return; } if (hasDeadlines) { @@ -2100,7 +2117,7 @@ private void createMainReactorInstance() { // Inform the run-time of the breadth/parallelism of the reaction graph var breadth = reactionInstanceGraph.getBreadth(); if (breadth == 0) { - errorReporter.reportWarning("The program has no reactions"); + messageReporter.nowhere().warning("The program has no reactions"); } else { targetConfig.compileDefinitions.put( "LF_REACTION_GRAPH_BREADTH", String.valueOf(reactionInstanceGraph.getBreadth())); diff --git a/core/src/main/java/org/lflang/generator/c/CPortGenerator.java b/core/src/main/java/org/lflang/generator/c/CPortGenerator.java index 04fbfec925..6aafeaef5a 100644 --- a/core/src/main/java/org/lflang/generator/c/CPortGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CPortGenerator.java @@ -3,7 +3,7 @@ import static org.lflang.generator.c.CGenerator.variableStructType; import org.lflang.AttributeUtils; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.Target; import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; @@ -37,7 +37,7 @@ public static void generateDeclarations( * @param tpr The reactor * @param port The port to generate the struct * @param target The target of the code generation (C, CCpp or Python) - * @param errorReporter The error reporter + * @param messageReporter The error reporter * @param types The helper object for types related stuff * @param federatedExtension The code needed to support federated execution * @param userFacing Whether this struct is to be presented in a user-facing header @@ -49,7 +49,7 @@ public static String generateAuxiliaryStruct( TypeParameterizedReactor tpr, Port port, Target target, - ErrorReporter errorReporter, + MessageReporter messageReporter, CTypes types, CodeBuilder federatedExtension, boolean userFacing, @@ -70,7 +70,7 @@ public static String generateAuxiliaryStruct( "size_t length;", // From token_template_t "bool is_present;", "lf_port_internal_t _base;")); - code.pr(valueDeclaration(tpr, port, target, errorReporter, types)); + code.pr(valueDeclaration(tpr, port, target, messageReporter, types)); code.pr(federatedExtension.toString()); code.unindent(); var name = @@ -191,11 +191,12 @@ private static String valueDeclaration( TypeParameterizedReactor tpr, Port port, Target target, - ErrorReporter errorReporter, + MessageReporter messageReporter, CTypes types) { if (port.getType() == null && target.requiresTypes) { // This should have been caught by the validator. - errorReporter.reportError(port, "Port is required to have a type: " + port.getName()); + String message = "Port is required to have a type: " + port.getName(); + messageReporter.at(port).error(message); return ""; } // Do not convert to lf_token_t* using lfTypeToTokenType because there diff --git a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java index d9c24aa1db..e865513991 100644 --- a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java @@ -9,8 +9,8 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.ast.ASTUtils; import org.lflang.federated.extensions.CExtensionUtils; @@ -57,7 +57,7 @@ public static String generateInitializationForReaction( TypeParameterizedReactor tpr, int reactionIndex, CTypes types, - ErrorReporter errorReporter, + MessageReporter messageReporter, Instantiation mainDef, boolean requiresTypes) { // Construct the reactionInitialization code to go into @@ -189,13 +189,14 @@ public static String generateInitializationForReaction( : "reset_transition") + ";"); } else { - errorReporter.reportError( - reaction, "In generateReaction(): " + name + " not a valid mode of this reactor."); + messageReporter + .at(reaction) + .error("In generateReaction(): " + name + " not a valid mode of this reactor."); } } else { if (variable instanceof Output) { reactionInitialization.pr( - generateOutputVariablesInReaction(effect, tpr, errorReporter, requiresTypes)); + generateOutputVariablesInReaction(effect, tpr, messageReporter, requiresTypes)); } else if (variable instanceof Input) { // It is the input of a contained reactor. generateVariablesForSendingToContainedReactors( @@ -207,8 +208,9 @@ public static String generateInitializationForReaction( } else if (variable instanceof Watchdog) { reactionInitialization.pr(generateWatchdogVariablesInReaction(effect)); } else { - errorReporter.reportError( - reaction, "In generateReaction(): effect is not an input, output, or watchdog."); + messageReporter + .at(reaction) + .error("In generateReaction(): effect is not an input, output, or watchdog."); } } } @@ -764,13 +766,13 @@ private static String generateInputVariablesInReaction( public static String generateOutputVariablesInReaction( VarRef effect, TypeParameterizedReactor tpr, - ErrorReporter errorReporter, + MessageReporter messageReporter, boolean requiresTypes) { Output output = (Output) effect.getVariable(); String outputName = output.getName(); String outputWidth = generateWidthVariable(outputName); if (output.getType() == null && requiresTypes) { - errorReporter.reportError(output, "Output is required to have a type: " + outputName); + messageReporter.at(output).error("Output is required to have a type: " + outputName); return ""; } else { // The container of the output may be a contained reactor or @@ -1116,7 +1118,7 @@ public static String generateReaction( TypeParameterizedReactor tpr, int reactionIndex, Instantiation mainDef, - ErrorReporter errorReporter, + MessageReporter messageReporter, CTypes types, TargetConfig targetConfig, boolean requiresType) { @@ -1124,7 +1126,7 @@ public static String generateReaction( var body = ASTUtils.toText(getCode(types, reaction, tpr)); String init = generateInitializationForReaction( - body, reaction, tpr, reactionIndex, types, errorReporter, mainDef, requiresType); + body, reaction, tpr, reactionIndex, types, messageReporter, mainDef, requiresType); code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader())); diff --git a/core/src/main/java/org/lflang/generator/c/CUtil.java b/core/src/main/java/org/lflang/generator/c/CUtil.java index ab873ca987..aa53e9edc0 100644 --- a/core/src/main/java/org/lflang/generator/c/CUtil.java +++ b/core/src/main/java/org/lflang/generator/c/CUtil.java @@ -36,9 +36,9 @@ import java.util.Objects; import java.util.Queue; import java.util.stream.Collectors; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.ast.ASTUtils; import org.lflang.generator.ActionInstance; @@ -610,7 +610,7 @@ public static void runBuildCommand( FileConfig fileConfig, TargetConfig targetConfig, GeneratorCommandFactory commandFactory, - ErrorReporter errorReporter, + MessageReporter messageReporter, ReportCommandErrors reportCommandErrors, LFGeneratorContext.Mode mode) { List commands = @@ -622,18 +622,22 @@ public static void runBuildCommand( for (LFCommand cmd : commands) { int returnCode = cmd.run(); if (returnCode != 0 && mode != LFGeneratorContext.Mode.EPOCH) { - errorReporter.reportError( - String.format( - // FIXME: Why is the content of stderr not provided to the user in this error - // message? - "Build command \"%s\" failed with error code %d.", - targetConfig.buildCommands, returnCode)); + // FIXME: Why is the content of stderr not provided to the user in this error + // message? + messageReporter + .nowhere() + .error( + String.format( + // FIXME: Why is the content of stderr not provided to the user in this error + // message? + "Build command \"%s\" failed with error code %d.", + targetConfig.buildCommands, returnCode)); return; } // For warnings (vs. errors), the return code is 0. // But we still want to mark the IDE. - if (!cmd.getErrors().toString().isEmpty() && mode == LFGeneratorContext.Mode.EPOCH) { - reportCommandErrors.report(cmd.getErrors().toString()); + if (!cmd.getErrors().isEmpty() && mode == LFGeneratorContext.Mode.EPOCH) { + reportCommandErrors.report(cmd.getErrors()); return; // FIXME: Why do we return here? Even if there are warnings, the build process // should proceed. } diff --git a/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java b/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java index d304593352..30a1df331b 100644 --- a/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java @@ -9,7 +9,7 @@ package org.lflang.generator.c; import java.util.List; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.generator.ReactorInstance; @@ -100,11 +100,11 @@ protected static void generateWatchdogs( CodeBuilder src, CodeBuilder header, TypeParameterizedReactor tpr, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { if (hasWatchdogs(tpr.reactor())) { header.pr("#include \"core/threaded/watchdog.h\""); for (Watchdog watchdog : ASTUtils.allWatchdogs(tpr.reactor())) { - src.pr(generateWatchdogFunction(watchdog, tpr, errorReporter)); + src.pr(generateWatchdogFunction(watchdog, tpr, messageReporter)); } } } @@ -180,7 +180,7 @@ protected static String generateWatchdogTable(int count) { * @param tpr The concrete reactor class that has the watchdog */ private static String generateInitializationForWatchdog( - Watchdog watchdog, TypeParameterizedReactor tpr, ErrorReporter errorReporter) { + Watchdog watchdog, TypeParameterizedReactor tpr, MessageReporter messageReporter) { // Construct the reactionInitialization code to go into // the body of the function before the verbatim code. @@ -225,11 +225,12 @@ private static String generateInitializationForWatchdog( : "reset_transition") + ";"); } else { - errorReporter.reportError( - watchdog, - "In generateInitializationForWatchdog(): " - + name - + " not a valid mode of this reactor."); + messageReporter + .at(watchdog) + .error( + "In generateInitializationForWatchdog(): " + + name + + " not a valid mode of this reactor."); } } } @@ -270,10 +271,10 @@ private static String generateFunction(String header, String init, Watchdog watc /** Generate the watchdog handler function. */ private static String generateWatchdogFunction( - Watchdog watchdog, TypeParameterizedReactor tpr, ErrorReporter errorReporter) { + Watchdog watchdog, TypeParameterizedReactor tpr, MessageReporter messageReporter) { return generateFunction( generateWatchdogFunctionHeader(watchdog, tpr), - generateInitializationForWatchdog(watchdog, tpr, errorReporter), + generateInitializationForWatchdog(watchdog, tpr, messageReporter), watchdog); } diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index 91fdfaffad..f925acd9c5 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -303,14 +303,14 @@ public void processProtoFile(String filename) { fileConfig.srcPath); if (protoc == null) { - errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1"); + messageReporter.nowhere().error("Processing .proto files requires libprotoc >= 3.6.1"); return; } int returnCode = protoc.run(); if (returnCode == 0) { pythonRequiredModules.add("google-api-python-client"); } else { - errorReporter.reportError("protoc returns error code " + returnCode); + messageReporter.nowhere().error("protoc returns error code " + returnCode); } } @@ -395,19 +395,19 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { generatePythonFileName(lfModuleName)); codeMaps.putAll(codeMapsForFederate); copyTargetFiles(); - new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context); + new PythonValidator(fileConfig, messageReporter, codeMaps, protoNames).doValidate(context); if (targetConfig.noCompile) { - System.out.println(PythonInfoGenerator.generateSetupInfo(fileConfig)); + messageReporter.nowhere().info(PythonInfoGenerator.generateSetupInfo(fileConfig)); } } catch (Exception e) { //noinspection ConstantConditions throw Exceptions.sneakyThrow(e); } - System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, lfModuleName)); + messageReporter.nowhere().info(PythonInfoGenerator.generateRunInfo(fileConfig, lfModuleName)); } - if (errorReporter.getErrorsOccurred()) { + if (messageReporter.getErrorsOccurred()) { context.unsuccessfulFinish(); } else { context.finish(GeneratorResult.Status.COMPILED, codeMaps); @@ -440,7 +440,7 @@ protected void generateReaction( } src.pr( PythonReactionGenerator.generateCReaction( - reaction, tpr, reactor, reactionIndex, mainDef, errorReporter, types)); + reaction, tpr, reactor, reactionIndex, mainDef, messageReporter, types)); } /** diff --git a/core/src/main/java/org/lflang/generator/python/PythonReactionGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonReactionGenerator.java index 87f594a71a..c65d4ad3de 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonReactionGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonReactionGenerator.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Set; import org.lflang.AttributeUtils; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.Target; import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; @@ -134,7 +134,7 @@ private static String generateCPythonFunctionCaller( * @param r The reactor to which reaction belongs to. * @param reactionIndex The index number of the reaction in decl. * @param mainDef The main reactor. - * @param errorReporter An error reporter. + * @param messageReporter An error reporter. * @param types A helper class for type-related stuff. */ public static String generateCReaction( @@ -143,14 +143,14 @@ public static String generateCReaction( Reactor r, int reactionIndex, Instantiation mainDef, - ErrorReporter errorReporter, + MessageReporter messageReporter, CTypes types) { // Contains the actual comma separated list of inputs to the reaction of type // generic_port_instance_struct. // Each input must be cast to (PyObject *) (aka their descriptors for Py_BuildValue are "O") List pyObjects = new ArrayList<>(); CodeBuilder code = new CodeBuilder(); - String cPyInit = generateCPythonInitializers(reaction, r, pyObjects, errorReporter); + String cPyInit = generateCPythonInitializers(reaction, r, pyObjects, messageReporter); String cInit = CReactionGenerator.generateInitializationForReaction( "", @@ -158,7 +158,7 @@ public static String generateCReaction( tpr, reactionIndex, types, - errorReporter, + messageReporter, mainDef, Target.Python.requiresTypes); code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader())); @@ -214,7 +214,10 @@ public static String generateFunction(String header, String init, Code code, Str * We will use as a format string, "(O...O)" where the number of O's is equal to the length of the list. */ private static String generateCPythonInitializers( - Reaction reaction, ReactorDecl decl, List pyObjects, ErrorReporter errorReporter) { + Reaction reaction, + ReactorDecl decl, + List pyObjects, + MessageReporter messageReporter) { Set actionsAsTriggers = new LinkedHashSet<>(); Reactor reactor = ASTUtils.toDefinition(decl); CodeBuilder code = new CodeBuilder(); @@ -269,11 +272,11 @@ private static String generateCPythonInitializers( PythonPortGenerator.generateVariablesForSendingToContainedReactors( pyObjects, effect.getContainer(), (Input) effect.getVariable())); } else { - errorReporter.reportError( - reaction, + String message = "In generateReaction(): " + effect.getVariable().getName() - + " is neither an input nor an output."); + + " is neither an input nor an output."; + messageReporter.at(reaction).error(message); } } } diff --git a/core/src/main/java/org/lflang/generator/python/PythonValidator.java b/core/src/main/java/org/lflang/generator/python/PythonValidator.java index 7d040bace0..8dedb2f9d6 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonValidator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonValidator.java @@ -15,12 +15,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.lsp4j.DiagnosticSeverity; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.generator.CodeMap; import org.lflang.generator.DiagnosticReporting; import org.lflang.generator.DiagnosticReporting.Strategy; import org.lflang.generator.Position; +import org.lflang.generator.Range; import org.lflang.generator.ValidationStrategy; import org.lflang.util.LFCommand; @@ -151,7 +152,7 @@ public DiagnosticSeverity getSeverity() { Pattern.compile("Instance of '(?\\w+)' has no .*"); private final FileConfig fileConfig; - private final ErrorReporter errorReporter; + private final MessageReporter messageReporter; private final ImmutableMap codeMaps; /** @@ -159,7 +160,7 @@ public DiagnosticSeverity getSeverity() { * errors to {@code errorReporter}. * * @param fileConfig The file configuration of this build. - * @param errorReporter The reporter to which diagnostics should be sent. + * @param messageReporter The reporter to which diagnostics should be sent. * @param codeMaps A mapping from generated file paths to code maps that map them back to LF * sources. * @param protoNames The names of any protocol buffer message types that are used in the LF @@ -167,12 +168,12 @@ public DiagnosticSeverity getSeverity() { */ public PythonValidator( FileConfig fileConfig, - ErrorReporter errorReporter, + MessageReporter messageReporter, Map codeMaps, Set protoNames) { - super(errorReporter, codeMaps); + super(messageReporter, codeMaps); this.fileConfig = fileConfig; - this.errorReporter = errorReporter; + this.messageReporter = messageReporter; this.codeMaps = ImmutableMap.copyOf(codeMaps); this.protoNames = ImmutableSet.copyOf(protoNames); } @@ -198,7 +199,7 @@ public Strategy getErrorReportingStrategy() { @Override public Strategy getOutputReportingStrategy() { return (String validationOutput, - ErrorReporter errorReporter, + MessageReporter messageReporter, Map map) -> { String[] lines = (validationOutput + "\n\n\n").lines().toArray(String[]::new); for (int i = 0; i < lines.length - 3; i++) { @@ -232,16 +233,14 @@ private boolean tryReportTypical(String[] lines, int i) { Position genPosition = Position.fromOneBased(line, Integer.MAX_VALUE); // Column is just a placeholder. if (map == null) { - errorReporter.report( - null, DiagnosticSeverity.Error, message, 1); // Undesirable fallback + messageReporter.nowhere().error(message); // Undesirable fallback } else { for (Path lfFile : map.lfSourcePaths()) { Position lfPosition = map.adjusted(lfFile, genPosition); // TODO: We could be more precise than just getting the right line, but the way // the output // is formatted (with leading whitespace possibly trimmed) does not make it easy. - errorReporter.report( - lfFile, DiagnosticSeverity.Error, message, lfPosition.getOneBasedLine()); + messageReporter.at(lfFile, lfPosition).error(message); } } return true; @@ -267,13 +266,12 @@ private void tryReportAlternative(String[] lines, int i) { for (CodeMap map : relevantMaps) { // There should almost always be exactly one of these for (Path lfFile : map.lfSourcePaths()) { - errorReporter.report( - lfFile, - DiagnosticSeverity.Error, - main.group().replace("*** ", "").replace("Sorry: ", ""), + Position pos = map.adjusted( - lfFile, Position.fromOneBased(line, map.firstNonWhitespace(line))) - .getOneBasedLine()); + lfFile, Position.fromOneBased(line, map.firstNonWhitespace(line))); + messageReporter + .at(lfFile, pos) + .error(main.group().replace("*** ", "").replace("Sorry: ", "")); } } } @@ -337,10 +335,12 @@ public Strategy getOutputReportingStrategy() { } catch (JsonProcessingException e) { System.err.printf("Failed to parse \"%s\":%n", validationOutput); e.printStackTrace(); - errorReporter.reportWarning( - "Failed to parse linter output. The Lingua Franca code generator is tested with" - + " Pylint version 2.12.2. Consider updating Pylint if you have an older" - + " version."); + errorReporter + .nowhere() + .warning( + "Failed to parse linter output. The Lingua Franca code generator is tested" + + " with Pylint version 2.12.2. Consider updating Pylint if you have an" + + " older version."); } }; } @@ -367,7 +367,7 @@ private boolean shouldIgnore(PylintMessage message) { /** Make a best-effort attempt to place the diagnostic on the correct line. */ private void bestEffortReport( - ErrorReporter errorReporter, + MessageReporter messageReporter, Function adjust, Position lfStart, Position lfEnd, @@ -375,7 +375,7 @@ private void bestEffortReport( DiagnosticSeverity severity, String humanMessage) { if (!lfEnd.equals(Position.ORIGIN) && !lfStart.equals(Position.ORIGIN)) { // Ideal case - errorReporter.report(file, severity, humanMessage, lfStart, lfEnd); + messageReporter.at(file, new Range(lfStart, lfEnd)).report(severity, humanMessage); } else { // Fallback: Try to report on the correct line, or failing that, just line 1. if (lfStart.equals(Position.ORIGIN)) lfStart = @@ -384,7 +384,7 @@ private void bestEffortReport( // FIXME: It might be better to improve style of generated code instead of quietly // returning here. if (lfStart.equals(Position.ORIGIN) && severity != DiagnosticSeverity.Error) return; - errorReporter.report(file, severity, humanMessage, lfStart.getOneBasedLine()); + messageReporter.at(file, lfStart).report(severity, humanMessage); } } diff --git a/core/src/main/java/org/lflang/generator/rust/CargoDependencySpec.java b/core/src/main/java/org/lflang/generator/rust/CargoDependencySpec.java index ff97860970..cd160f53c1 100644 --- a/core/src/main/java/org/lflang/generator/rust/CargoDependencySpec.java +++ b/core/src/main/java/org/lflang/generator/rust/CargoDependencySpec.java @@ -31,6 +31,8 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.emf.ecore.EObject; +import org.lflang.MessageReporter; import org.lflang.TargetProperty; import org.lflang.TargetProperty.TargetPropertyType; import org.lflang.ast.ASTUtils; @@ -281,7 +283,10 @@ public void check(Element element, String name, LFValidator v) { try { parseValue(pair); } catch (InvalidLfSourceException e) { - v.getErrorReporter().reportError(e.getNode(), e.getProblem()); + MessageReporter messageReporter = v.getErrorReporter(); + EObject object = e.getNode(); + String message = e.getProblem(); + messageReporter.at(object).error(message); } } } diff --git a/core/src/main/java/org/lflang/generator/rust/RustTargetConfig.java b/core/src/main/java/org/lflang/generator/rust/RustTargetConfig.java index b4e9a1778b..7c164c1957 100644 --- a/core/src/main/java/org/lflang/generator/rust/RustTargetConfig.java +++ b/core/src/main/java/org/lflang/generator/rust/RustTargetConfig.java @@ -31,7 +31,7 @@ import java.util.List; import java.util.Map; import org.eclipse.emf.ecore.EObject; -import org.lflang.ErrorReporter; +import org.lflang.MessageReporter; import org.lflang.TargetProperty.BuildType; /** @@ -61,18 +61,18 @@ public void setCargoDependencies(Map cargoDependenc this.cargoDependencies = cargoDependencies; } - public void addAndCheckTopLevelModule(Path path, EObject errorOwner, ErrorReporter err) { + public void addAndCheckTopLevelModule(Path path, EObject errorOwner, MessageReporter err) { String fileName = path.getFileName().toString(); if (!Files.exists(path)) { - err.reportError(errorOwner, "File not found"); + err.at(errorOwner).error("File not found"); } else if (Files.isRegularFile(path) && !fileName.endsWith(".rs")) { - err.reportError(errorOwner, "Not a rust file"); + err.at(errorOwner).error("Not a rust file"); } else if (fileName.equals("main.rs")) { - err.reportError(errorOwner, "Cannot use 'main.rs' as a module name (reserved)"); + err.at(errorOwner).error("Cannot use 'main.rs' as a module name (reserved)"); } else if (fileName.equals("reactors") || fileName.equals("reactors.rs")) { - err.reportError(errorOwner, "Cannot use 'reactors' as a module name (reserved)"); + err.at(errorOwner).error("Cannot use 'reactors' as a module name (reserved)"); } else if (Files.isDirectory(path) && !Files.exists(path.resolve("mod.rs"))) { - err.reportError(errorOwner, "Cannot find module descriptor in directory"); + err.at(errorOwner).error("Cannot find module descriptor in directory"); } this.rustTopLevelModules.add(path); } diff --git a/core/src/main/java/org/lflang/util/ArduinoUtil.java b/core/src/main/java/org/lflang/util/ArduinoUtil.java index af741da7f2..13f854f9c7 100644 --- a/core/src/main/java/org/lflang/util/ArduinoUtil.java +++ b/core/src/main/java/org/lflang/util/ArduinoUtil.java @@ -6,8 +6,8 @@ import java.io.IOException; import java.util.List; import org.eclipse.xtext.xbase.lib.Exceptions; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.generator.GeneratorCommandFactory; import org.lflang.generator.LFGeneratorContext; @@ -26,15 +26,15 @@ public class ArduinoUtil { private LFGeneratorContext context; private GeneratorCommandFactory commandFactory; - private ErrorReporter errorReporter; + private MessageReporter messageReporter; public ArduinoUtil( LFGeneratorContext context, GeneratorCommandFactory commandFactory, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { this.context = context; this.commandFactory = commandFactory; - this.errorReporter = errorReporter; + this.messageReporter = messageReporter; } /** Return true if arduino-cli exists, false otherwise. */ @@ -103,24 +103,28 @@ private LFCommand arduinoCompileCommand(FileConfig fileConfig, TargetConfig targ * @param targetConfig */ public void buildArduino(FileConfig fileConfig, TargetConfig targetConfig) { - System.out.println("Retrieving Arduino Compile Script"); + messageReporter.nowhere().info("Retrieving Arduino Compile Script"); try { LFCommand command = arduinoCompileCommand(fileConfig, targetConfig); // Use ProcessBuilder for finer control. int retCode = 0; retCode = command.run(context.getCancelIndicator()); if (retCode != 0 && context.getMode() == LFGeneratorContext.Mode.STANDALONE) { - errorReporter.reportError("arduino-cli failed with error code " + retCode); + messageReporter.nowhere().error("arduino-cli failed with error code " + retCode); throw new IOException("arduino-cli failure"); } } catch (IOException e) { Exceptions.sneakyThrow(e); } - System.out.println( - "SUCCESS: Compiling generated code for " + fileConfig.name + " finished with no errors."); + messageReporter + .nowhere() + .info( + "SUCCESS: Compiling generated code for " + + fileConfig.name + + " finished with no errors."); if (targetConfig.platformOptions.flash) { if (targetConfig.platformOptions.port != null) { - System.out.println("Invoking flash command for Arduino"); + messageReporter.nowhere().info("Invoking flash command for Arduino"); LFCommand flash = commandFactory.createCommand( "arduino-cli", @@ -132,26 +136,30 @@ public void buildArduino(FileConfig fileConfig, TargetConfig targetConfig) { targetConfig.platformOptions.port), fileConfig.getSrcGenPath()); if (flash == null) { - errorReporter.reportError("Could not create arduino-cli flash command."); + messageReporter.nowhere().error("Could not create arduino-cli flash command."); } int flashRet = flash.run(); if (flashRet != 0) { - errorReporter.reportError("arduino-cli flash command failed with error code " + flashRet); + messageReporter + .nowhere() + .error("arduino-cli flash command failed with error code " + flashRet); } else { - System.out.println("SUCCESS: Flashed board using arduino-cli"); + messageReporter.nowhere().info("SUCCESS: Flashed board using arduino-cli"); } } else { - errorReporter.reportError("Need to provide a port on which to automatically flash."); + messageReporter.nowhere().error("Need to provide a port on which to automatically flash."); } } else { - System.out.println("********"); - System.out.println( - "To flash your program, run the following command to see information about the board you" - + " plugged in:\n\n" - + "\tarduino-cli board list\n\n" - + "Grab the FQBN and PORT from the command and run the following command in the" - + " generated sources directory:\n\n" - + "\tarduino-cli upload -b -p \n"); + messageReporter.nowhere().info("********"); + messageReporter + .nowhere() + .info( + "To flash your program, run the following command to see information about the board" + + " you plugged in:\n\n" + + "\tarduino-cli board list\n\n" + + "Grab the FQBN and PORT from the command and run the following command in the" + + " generated sources directory:\n\n" + + "\tarduino-cli upload -b -p \n"); } } } diff --git a/core/src/main/java/org/lflang/util/FileUtil.java b/core/src/main/java/org/lflang/util/FileUtil.java index d5bd2a3ba0..063e383df8 100644 --- a/core/src/main/java/org/lflang/util/FileUtil.java +++ b/core/src/main/java/org/lflang/util/FileUtil.java @@ -33,8 +33,8 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.util.RuntimeIOException; -import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.MessageReporter; public class FileUtil { @@ -307,13 +307,13 @@ public static Path findAndCopyFile(String file, Path dstDir, FileConfig fileConf * @param dstDir The location to copy the files to. * @param fileConfig The file configuration that specifies where the find entries the given * entries. - * @param errorReporter An error reporter to report problems. + * @param messageReporter An error reporter to report problems. */ public static void copyFilesOrDirectories( List entries, Path dstDir, FileConfig fileConfig, - ErrorReporter errorReporter, + MessageReporter messageReporter, boolean fileEntriesOnly) { for (String fileOrDirectory : entries) { var path = Paths.get(fileOrDirectory); @@ -325,13 +325,14 @@ public static void copyFilesOrDirectories( } else { FileUtil.copyFromFileSystem(found, dstDir, false); } - System.out.println("Copied '" + fileOrDirectory + "' from the file system."); + messageReporter.nowhere().info("Copied '" + fileOrDirectory + "' from the file system."); } catch (IOException e) { - errorReporter.reportError( + String message = "Unable to copy '" + fileOrDirectory + "' from the file system. Reason: " - + e.toString()); + + e.toString(); + messageReporter.nowhere().error(message); } } else { try { @@ -339,14 +340,15 @@ public static void copyFilesOrDirectories( copyFileFromClassPath(fileOrDirectory, dstDir, false); } else { FileUtil.copyFromClassPath(fileOrDirectory, dstDir, false, false); - System.out.println("Copied '" + fileOrDirectory + "' from the class path."); + messageReporter.nowhere().info("Copied '" + fileOrDirectory + "' from the class path."); } } catch (IOException e) { - errorReporter.reportError( + String message = "Unable to copy '" + fileOrDirectory + "' from the class path. Reason: " - + e.toString()); + + e.toString(); + messageReporter.nowhere().error(message); } } } @@ -710,10 +712,12 @@ public static boolean isCFile(Path path) { * Convert all includes recursively inside files within a specified folder to relative links * * @param dir The folder to search for includes to change. + * @param messageReporter Error reporter * @throws IOException If the given set of files cannot be relativized. */ - public static void relativeIncludeHelper(Path dir, Path includePath) throws IOException { - System.out.println("Relativizing all includes in " + dir.toString()); + public static void relativeIncludeHelper( + Path dir, Path includePath, MessageReporter messageReporter) throws IOException { + messageReporter.nowhere().info("Relativizing all includes in " + dir.toString()); List includePaths = Files.walk(includePath) .filter(Files::isRegularFile) @@ -777,6 +781,7 @@ public static void delete(Path fileOrDirectory) throws IOException { */ public static void deleteDirectory(Path dir) throws IOException { if (Files.isDirectory(dir)) { + // fixme system.out System.out.println("Cleaning " + dir); List pathsToDelete = Files.walk(dir).sorted(Comparator.reverseOrder()).toList(); for (Path path : pathsToDelete) { diff --git a/core/src/main/java/org/lflang/util/LFCommand.java b/core/src/main/java/org/lflang/util/LFCommand.java index 4b4116f264..8bed14916e 100644 --- a/core/src/main/java/org/lflang/util/LFCommand.java +++ b/core/src/main/java/org/lflang/util/LFCommand.java @@ -29,7 +29,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.io.PrintStream; import java.nio.file.Path; import java.nio.file.Paths; @@ -75,13 +74,13 @@ protected LFCommand(ProcessBuilder pb, boolean quiet) { } /** Get the output collected during command execution */ - public OutputStream getOutput() { - return output; + public String getOutput() { + return output.toString(); } /** Get the error output collected during command execution */ - public OutputStream getErrors() { - return errors; + public String getErrors() { + return errors.toString(); } /** Get this command's program and arguments. */ @@ -165,6 +164,7 @@ public int run(CancelIndicator cancelIndicator) { assert !didRun; didRun = true; + // FIXME remove system out System.out.println("--- Current working directory: " + processBuilder.directory().toString()); System.out.println("--- Executing command: " + String.join(" ", processBuilder.command())); diff --git a/core/src/main/java/org/lflang/validation/LFValidator.java b/core/src/main/java/org/lflang/validation/LFValidator.java index eb3faa2e9a..a602d54076 100644 --- a/core/src/main/java/org/lflang/validation/LFValidator.java +++ b/core/src/main/java/org/lflang/validation/LFValidator.java @@ -1108,7 +1108,7 @@ public void checkTargetDecl(TargetDecl target) throws IOException { } String lfFileName = FileUtil.nameWithoutExtension(target.eResource()); if (Character.isDigit(lfFileName.charAt(0))) { - errorReporter.reportError("LF file names must not start with a number"); + errorReporter.nowhere().error("LF file names must not start with a number"); } } @@ -1647,7 +1647,7 @@ public void checkUnspecifiedTransitionType(Reaction reaction) { //// Public methods. /** Return the error reporter for this validator. */ - public ValidatorErrorReporter getErrorReporter() { + public ValidatorMessageReporter getErrorReporter() { return this.errorReporter; } @@ -1950,8 +1950,8 @@ private boolean sameType(Type type1, Type type2) { //// Private fields. /** The error reporter. */ - private ValidatorErrorReporter errorReporter = - new ValidatorErrorReporter(getMessageAcceptor(), new ValidatorStateAccess()); + private ValidatorMessageReporter errorReporter = + new ValidatorMessageReporter(getMessageAcceptor(), new ValidatorStateAccess()); /** Helper class containing information about the model. */ private ModelInfo info = new ModelInfo(); diff --git a/core/src/main/java/org/lflang/validation/ValidatorErrorReporter.java b/core/src/main/java/org/lflang/validation/ValidatorErrorReporter.java deleted file mode 100644 index d21458a3a9..0000000000 --- a/core/src/main/java/org/lflang/validation/ValidatorErrorReporter.java +++ /dev/null @@ -1,186 +0,0 @@ -/************* - * Copyright (c) 2021, TU Dresden. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - ***************/ - -package org.lflang.validation; - -import java.nio.file.Path; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.lflang.ErrorReporter; - -/** - * This class translates messages reported via the ErrorReporrter interface to the interface of a - * given ValidationMessageAcceptor. - * - *

Effectively this allows to report errors via the ErrorReporter interface during validator - * checks, while having the validator still track all the reported warnings and messages. This is - * required for some functionality, like the construction of an instance graph in - * LFValidator.checkModel(). Since the instance graph is also used in other components, it does not - * report directly to the validator, but uses our custom ErrorReporter interface that we use during - * code generation. This class bridges the gap between the ErrorReporter interface and the messages - * that the validator expects. - * - * @author Christian Menard - */ -public class ValidatorErrorReporter implements ErrorReporter { - - private ValidationMessageAcceptor acceptor; - private BaseLFValidator.ValidatorStateAccess validatorState; - private boolean errorsOccurred = false; - - public ValidatorErrorReporter( - ValidationMessageAcceptor acceptor, BaseLFValidator.ValidatorStateAccess stateAccess) { - this.acceptor = acceptor; - this.validatorState = stateAccess; - } - - /** Report the given message as an error on the object currently under validation. */ - @Override - public String reportError(String message) { - errorsOccurred = true; - acceptor.acceptError( - message, - validatorState.getCurrentObject(), - null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, - null); - return message; - } - - /** Report the given message as an error on the given object. */ - @Override - public String reportError(EObject object, String message) { - errorsOccurred = true; - acceptor.acceptError( - message, object, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - /** - * Report the given message as an error on the current object. - * - *

Unfortunately, there is no way to provide a path and a line number to the - * ValidationMessageAcceptor as messages can only be reported directly as EObjects. While it is - * not an ideal solution, this method composes a messages indicating the location of the error and - * reports this on the object currently under validation. This way, the error message is not lost, - * but it is not necessarily reported precisely at the location of the actual error. - */ - @Override - public String reportError(Path file, Integer line, String message) { - errorsOccurred = true; - String fullMessage = - message + " (Reported from " + file.toString() + " on line " + line.toString() + ")"; - acceptor.acceptError( - fullMessage, - validatorState.getCurrentObject(), - null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, - null); - return fullMessage; - } - - /** Report the given message as a waring on the object currently under validation. */ - @Override - public String reportWarning(String message) { - acceptor.acceptWarning( - message, - validatorState.getCurrentObject(), - null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, - null); - return message; - } - - @Override - public String reportInfo(String message) { - acceptor.acceptInfo( - message, - validatorState.getCurrentObject(), - null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, - null); - return message; - } - - /** Report the given message as a warning on the given object. */ - @Override - public String reportWarning(EObject object, String message) { - acceptor.acceptWarning( - message, object, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - @Override - public String reportInfo(EObject object, String message) { - acceptor.acceptInfo(message, object, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - /** - * Report the given message as an warning on the current object. - * - *

Unfortunately, there is no way to provide a path and a line number to the - * ValidationMessageAcceptor as messages can only be reported directly as EObjects. While it is - * not an ideal solution, this method composes a messages indicating the location of the warning - * and reports this on the object currently under validation. This way, the warning message is not - * lost, but it is not necessarily reported precisely at the location of the actual warning. - */ - @Override - public String reportWarning(Path file, Integer line, String message) { - String fullMessage = - message + " (Reported from " + file.toString() + " on line " + line.toString() + ")"; - acceptor.acceptWarning( - fullMessage, - validatorState.getCurrentObject(), - null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, - null); - return fullMessage; - } - - @Override - public String reportInfo(Path file, Integer line, String message) { - String fullMessage = - message + " (Reported from " + file.toString() + " on line " + line.toString() + ")"; - acceptor.acceptInfo( - fullMessage, - validatorState.getCurrentObject(), - null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, - null); - return fullMessage; - } - - /** - * Check if errors where reported. - * - * @return true if errors where reported - */ - @Override - public boolean getErrorsOccurred() { - return errorsOccurred; - } -} diff --git a/core/src/main/java/org/lflang/validation/ValidatorMessageReporter.java b/core/src/main/java/org/lflang/validation/ValidatorMessageReporter.java new file mode 100644 index 0000000000..3ef4e05798 --- /dev/null +++ b/core/src/main/java/org/lflang/validation/ValidatorMessageReporter.java @@ -0,0 +1,98 @@ +/************* + * Copyright (c) 2021, TU Dresden. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + ***************/ + +package org.lflang.validation; + +import java.nio.file.Path; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +import org.lflang.MessageReporterBase; +import org.lflang.generator.Range; + +/** + * This class translates messages reported via the ErrorReporrter interface to the interface of a + * given ValidationMessageAcceptor. + * + *

Effectively this allows to report errors via the ErrorReporter interface during validator + * checks, while having the validator still track all the reported warnings and messages. This is + * required for some functionality, like the construction of an instance graph in + * LFValidator.checkModel(). Since the instance graph is also used in other components, it does not + * report directly to the validator, but uses our custom ErrorReporter interface that we use during + * code generation. This class bridges the gap between the ErrorReporter interface and the messages + * that the validator expects. + * + * @author Christian Menard + */ +public class ValidatorMessageReporter extends MessageReporterBase { + + private final ValidationMessageAcceptor acceptor; + private final BaseLFValidator.ValidatorStateAccess validatorState; + + public ValidatorMessageReporter( + ValidationMessageAcceptor acceptor, BaseLFValidator.ValidatorStateAccess stateAccess) { + this.acceptor = acceptor; + this.validatorState = stateAccess; + } + + @Override + protected void reportWithoutPosition(DiagnosticSeverity severity, String message) { + reportOnNode(validatorState.getCurrentObject(), severity, message); + } + + /** + * Report the given message as an error on the current object. + * + *

Unfortunately, there is no way to provide a path and a line number to the + * ValidationMessageAcceptor as messages can only be reported directly as EObjects. While it is + * not an ideal solution, this method composes a messages indicating the location of the error and + * reports this on the object currently under validation. This way, the error message is not lost, + * but it is not necessarily reported precisely at the location of the actual error. + */ + @Override + protected void report(Path path, Range range, DiagnosticSeverity severity, String message) { + String fullMessage = + message + + " (Reported from " + + path + + " on line " + + range.getStartInclusive().getOneBasedLine() + + ")"; + reportOnNode(validatorState.getCurrentObject(), severity, fullMessage); + } + + @Override + protected void reportOnNode(EObject node, DiagnosticSeverity severity, String message) { + switch (severity) { + case Error -> acceptor.acceptError( + message, node, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + case Warning -> acceptor.acceptWarning( + message, node, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + case Information, Hint -> acceptor.acceptInfo( + message, node, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + } + } +} diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppActionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppActionGenerator.kt index 27ad6380cd..a0e31d0b47 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppActionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppActionGenerator.kt @@ -24,7 +24,7 @@ package org.lflang.generator.cpp -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.generator.PrependOperator import org.lflang.generator.orZero import org.lflang.inferredType @@ -34,7 +34,7 @@ import org.lflang.lf.BuiltinTrigger import org.lflang.lf.Reactor /** A C++ code generator for actions */ -class CppActionGenerator(private val reactor: Reactor, private val errorReporter: ErrorReporter) { +class CppActionGenerator(private val reactor: Reactor, private val messageReporter: MessageReporter) { companion object { val Action.cppType: String @@ -55,10 +55,12 @@ class CppActionGenerator(private val reactor: Reactor, private val errorReporter private fun generateLogicalInitializer(action: Action): String { return if (action.minSpacing != null || !action.policy.isNullOrEmpty()) { - errorReporter.reportError( - action, + messageReporter.at( + action + ).error( "minSpacing and spacing violation policies are not yet supported for logical actions in reactor-ccp!" ) + "minSpacing and spacing violation policies are not yet supported for logical actions in reactor-ccp!" } else { val time = action.minDelay.orZero().toCppTime() """, ${action.name}{"${action.name}", this, $time}""" @@ -67,10 +69,12 @@ class CppActionGenerator(private val reactor: Reactor, private val errorReporter private fun initializePhysicalInitializer(action: Action): String { return if (action.minDelay != null || action.minSpacing != null || !action.policy.isNullOrEmpty()) { - errorReporter.reportError( - action, + messageReporter.at( + action + ).error( "minDelay, minSpacing and spacing violation policies are not yet supported for physical actions in reactor-ccp!" ) + "minDelay, minSpacing and spacing violation policies are not yet supported for physical actions in reactor-ccp!" } else { """, ${action.name}{"${action.name}", this}""" } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppGenerator.kt index e739560ba8..359811e8d9 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppGenerator.kt @@ -67,7 +67,7 @@ class CppGenerator( override fun doGenerate(resource: Resource, context: LFGeneratorContext) { super.doGenerate(resource, context) - if (!canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return + if (!canGenerate(errorsOccurred(), mainDef, messageReporter, context)) return // create a platform-specific generator val platformGenerator: CppPlatformGenerator = @@ -88,7 +88,7 @@ class CppGenerator( ) if (platformGenerator.doCompile(context)) { - CppValidator(fileConfig, errorReporter, codeMaps).doValidate(context) + CppValidator(fileConfig, messageReporter, codeMaps).doValidate(context) context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, codeMaps)) } else { context.unsuccessfulFinish() @@ -149,7 +149,7 @@ class CppGenerator( // generate header and source files for all reactors for (r in reactors) { - val generator = CppReactorGenerator(r, fileConfig, errorReporter) + val generator = CppReactorGenerator(r, fileConfig, messageReporter) val headerFile = fileConfig.getReactorHeaderPath(r) val sourceFile = if (r.isGeneric) fileConfig.getReactorHeaderImplPath(r) else fileConfig.getReactorSourcePath(r) val reactorCodeMap = CodeMap.fromGeneratedCode(generator.generateSource()) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt index 19622f9ef7..19466c2fce 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt @@ -34,7 +34,7 @@ import org.lflang.validation.AttributeSpec class CppInstanceGenerator( private val reactor: Reactor, private val fileConfig: CppFileConfig, - private val errorReporter: ErrorReporter + private val messageReporter: MessageReporter ) { companion object { val Instantiation.isEnclave: Boolean get() = AttributeUtils.isEnclave(this) @@ -87,12 +87,12 @@ class CppInstanceGenerator( val assignments = parameters.mapNotNull { when { it.rhs.isParens || it.rhs.isBraces -> { - errorReporter.reportError(it, "Parenthesis based initialization is not allowed here!") + messageReporter.at(it).error("Parenthesis based initialization is not allowed here!") null } it.rhs.exprs.size != 1 -> { - errorReporter.reportError(it, "Expected exactly one expression.") + messageReporter.at(it).error("Expected exactly one expression.") null } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppPlatformGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppPlatformGenerator.kt index 09296cfe36..e2106456b8 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppPlatformGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppPlatformGenerator.kt @@ -1,6 +1,6 @@ package org.lflang.generator.cpp -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.TargetConfig import org.lflang.generator.GeneratorCommandFactory import org.lflang.generator.LFGeneratorContext @@ -11,7 +11,7 @@ import java.nio.file.Path abstract class CppPlatformGenerator(protected val generator: CppGenerator) { protected val codeMaps = generator.codeMaps protected val cppSources = generator.cppSources - protected val errorReporter: ErrorReporter = generator.errorReporter + protected val messageReporter: MessageReporter = generator.messageReporter protected val fileConfig: CppFileConfig = generator.fileConfig protected val targetConfig: TargetConfig = generator.targetConfig protected val commandFactory: GeneratorCommandFactory = generator.commandFactory @@ -32,4 +32,4 @@ abstract class CppPlatformGenerator(protected val generator: CppGenerator) { "-DREACTOR_CPP_LOG_LEVEL=${targetConfig.logLevel.severity}", "-DLF_SRC_PKG_PATH=${fileConfig.srcPkgPath}", ) -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt index 78df4519f2..c28aa607ab 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt @@ -23,7 +23,7 @@ ***************/ package org.lflang.generator.cpp -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.generator.PrependOperator import org.lflang.isGeneric import org.lflang.lf.Reactor @@ -33,7 +33,7 @@ import org.lflang.toUnixString /** * A C++ code generator that produces a C++ class representing a single reactor */ -class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfig, errorReporter: ErrorReporter) { +class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfig, messageReporter: MessageReporter) { /** Comment to be inserted at the top of generated files */ private val fileComment = fileComment(reactor.eResource()) @@ -50,9 +50,9 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi private val parameters = CppParameterGenerator(reactor) private val state = CppStateGenerator(reactor) private val methods = CppMethodGenerator(reactor) - private val instances = CppInstanceGenerator(reactor, fileConfig, errorReporter) + private val instances = CppInstanceGenerator(reactor, fileConfig, messageReporter) private val timers = CppTimerGenerator(reactor) - private val actions = CppActionGenerator(reactor, errorReporter) + private val actions = CppActionGenerator(reactor, messageReporter) private val ports = CppPortGenerator(reactor) private val reactions = CppReactionGenerator(reactor, ports, instances) private val assemble = CppAssembleMethodGenerator(reactor) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt index 17c2c70315..76969269c0 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt @@ -39,7 +39,8 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator val ros2Version = System.getenv("ROS_DISTRO") if (ros2Version.isNullOrBlank()) { - errorReporter.reportError( + messageReporter.nowhere( + ).error( "Could not find a ROS2 installation! Please install ROS2 and source the setup script. " + "Also see https://docs.ros.org/en/galactic/Installation.html" ) @@ -58,11 +59,11 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator fileConfig.outPath ) val returnCode = colconCommand?.run(context.cancelIndicator); - if (returnCode != 0 && !errorReporter.errorsOccurred) { + if (returnCode != 0 && !messageReporter.errorsOccurred) { // If errors occurred but none were reported, then the following message is the best we can do. - errorReporter.reportError("colcon failed with error code $returnCode") + messageReporter.nowhere().error("colcon failed with error code $returnCode") } - return !errorReporter.errorsOccurred + return !messageReporter.errorsOccurred } } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppStandaloneGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppStandaloneGenerator.kt index 8e6e60227a..45fa65f4f5 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppStandaloneGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppStandaloneGenerator.kt @@ -66,7 +66,7 @@ class CppStandaloneGenerator(generator: CppGenerator) : if (cmakeReturnCode == 0 && runMake) { // If cmake succeeded, run make val makeCommand = createMakeCommand(fileConfig.buildPath, version, fileConfig.name) - val makeReturnCode = CppValidator(fileConfig, errorReporter, codeMaps).run(makeCommand, context.cancelIndicator) + val makeReturnCode = CppValidator(fileConfig, messageReporter, codeMaps).run(makeCommand, context.cancelIndicator) var installReturnCode = 0 if (makeReturnCode == 0) { val installCommand = createMakeCommand(fileConfig.buildPath, version, "install") @@ -77,16 +77,16 @@ class CppStandaloneGenerator(generator: CppGenerator) : println("Compiled binary is in ${fileConfig.binPath}") } } - if ((makeReturnCode != 0 || installReturnCode != 0) && !errorReporter.errorsOccurred) { + if ((makeReturnCode != 0 || installReturnCode != 0) && !messageReporter.errorsOccurred) { // If errors occurred but none were reported, then the following message is the best we can do. - errorReporter.reportError("make failed with error code $makeReturnCode") + messageReporter.nowhere().error("make failed with error code $makeReturnCode") } } if (cmakeReturnCode != 0) { - errorReporter.reportError("cmake failed with error code $cmakeReturnCode") + messageReporter.nowhere().error("cmake failed with error code $cmakeReturnCode") } } - return !errorReporter.errorsOccurred + return !messageReporter.errorsOccurred } private fun checkCmakeVersion(): String? { @@ -98,7 +98,8 @@ class CppStandaloneGenerator(generator: CppGenerator) : version = regex.find(cmd.output.toString())?.value } if (version == null || version.compareVersion("3.5.0") < 0) { - errorReporter.reportError( + messageReporter.nowhere( + ).error( "The C++ target requires CMAKE >= 3.5.0 to compile the generated code. " + "Auto-compiling can be disabled using the \"no-compile: true\" target property." ) @@ -138,7 +139,7 @@ class CppStandaloneGenerator(generator: CppGenerator) : private fun createMakeCommand(buildPath: Path, version: String, target: String): LFCommand { val makeArgs: List if (version.compareVersion("3.12.0") < 0) { - errorReporter.reportWarning("CMAKE is older than version 3.12. Parallel building is not supported.") + messageReporter.nowhere().warning("CMAKE is older than version 3.12. Parallel building is not supported.") makeArgs = listOf("--build", ".", "--target", target, "--config", targetConfig.cmakeBuildType?.toString() ?: "Release") } else { @@ -175,4 +176,4 @@ class CppStandaloneGenerator(generator: CppGenerator) : } return cmd } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppValidator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppValidator.kt index 8a1be2a810..fd642a94bf 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppValidator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppValidator.kt @@ -1,6 +1,6 @@ package org.lflang.generator.cpp -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.generator.CodeMap import org.lflang.generator.DiagnosticReporting import org.lflang.generator.HumanReadableReportingStrategy @@ -19,9 +19,9 @@ import java.util.regex.Pattern */ class CppValidator( private val fileConfig: CppFileConfig, - errorReporter: ErrorReporter, + messageReporter: MessageReporter, codeMaps: Map -): Validator(errorReporter, codeMaps) { +): Validator(messageReporter, codeMaps) { companion object { /** This matches a line in the CMake cache. */ diff --git a/core/src/main/kotlin/org/lflang/generator/rust/RustGenerator.kt b/core/src/main/kotlin/org/lflang/generator/rust/RustGenerator.kt index 711fc9c6f9..41128800e3 100644 --- a/core/src/main/kotlin/org/lflang/generator/rust/RustGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/rust/RustGenerator.kt @@ -25,7 +25,6 @@ package org.lflang.generator.rust import org.eclipse.emf.ecore.resource.Resource -import org.lflang.ErrorReporter import org.lflang.Target import org.lflang.TargetProperty.BuildType import org.lflang.generator.GeneratorUtils.canGenerate @@ -72,11 +71,11 @@ class RustGenerator( override fun doGenerate(resource: Resource, context: LFGeneratorContext) { super.doGenerate(resource, context) - if (!canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return + if (!canGenerate(errorsOccurred(), mainDef, messageReporter, context)) return Files.createDirectories(fileConfig.srcGenPath) - val gen = RustModelBuilder.makeGenerationInfo(targetConfig, reactors, errorReporter) + val gen = RustModelBuilder.makeGenerationInfo(targetConfig, reactors, messageReporter) val codeMaps: Map = RustEmitter.generateRustProject(fileConfig, gen) if (targetConfig.noCompile || errorsOccurred()) { @@ -87,7 +86,7 @@ class RustGenerator( "Code generation complete. Compiling...", IntegratedBuilder.GENERATED_PERCENT_PROGRESS ) Files.deleteIfExists(fileConfig.executable) // cleanup, cargo doesn't do it - if (context.mode == LFGeneratorContext.Mode.LSP_MEDIUM) RustValidator(fileConfig, errorReporter, codeMaps).doValidate(context) + if (context.mode == LFGeneratorContext.Mode.LSP_MEDIUM) RustValidator(fileConfig, messageReporter, codeMaps).doValidate(context) else invokeRustCompiler(context, codeMaps) } } @@ -121,13 +120,13 @@ class RustGenerator( ) ?: return cargoCommand.setQuiet() - val validator = RustValidator(fileConfig, errorReporter, codeMaps) + val validator = RustValidator(fileConfig, messageReporter, codeMaps) val cargoReturnCode = validator.run(cargoCommand, context.cancelIndicator) if (cargoReturnCode == 0) { // We still have to copy the compiled binary to the destination folder. val buildType = targetConfig.rust.buildType - val binaryPath = validator.getMetadata()?.targetDirectory!! + val binaryPath = validator.metadata?.targetDirectory!! .resolve(buildType.cargoProfileName) .resolve(fileConfig.executable.fileName) val destPath = fileConfig.executable @@ -143,9 +142,12 @@ class RustGenerator( } else if (context.cancelIndicator.isCanceled) { context.finish(GeneratorResult.CANCELLED) } else { - if (!errorsOccurred()) errorReporter.reportError( - "cargo failed with error code $cargoReturnCode and reported the following error(s):\n${cargoCommand.errors}" - ) + if (!errorsOccurred()) { + messageReporter.nowhere( + ).error( + "cargo failed with error code $cargoReturnCode and reported the following error(s):\n${cargoCommand.errors}" + ) + } context.finish(GeneratorResult.FAILED) } } diff --git a/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt b/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt index 82c0080d6e..b2bacb0ce3 100644 --- a/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt +++ b/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt @@ -29,12 +29,6 @@ import org.lflang.* import org.lflang.TargetProperty.BuildType import org.lflang.ast.ASTUtils import org.lflang.generator.* -import org.lflang.inBlock -import org.lflang.indexInContainer -import org.lflang.inferredType -import org.lflang.isBank -import org.lflang.isInput -import org.lflang.isLogical import org.lflang.lf.* import org.lflang.lf.Timer import java.nio.file.Path @@ -428,7 +422,7 @@ object RustModelBuilder { /** * Given the input to the generator, produce the model classes. */ - fun makeGenerationInfo(targetConfig: TargetConfig, reactors: List, errorReporter: ErrorReporter): GenerationInfo { + fun makeGenerationInfo(targetConfig: TargetConfig, reactors: List, messageReporter: MessageReporter): GenerationInfo { val reactorsInfos = makeReactorInfos(reactors) // todo how do we pick the main reactor? it seems like super.doGenerate sets that field... val mainReactor = reactorsInfos.lastOrNull { it.isMain } ?: reactorsInfos.last() @@ -436,7 +430,7 @@ object RustModelBuilder { val dependencies = targetConfig.rust.cargoDependencies.toMutableMap() dependencies.compute(RustEmitterBase.runtimeCrateFullName) { _, spec -> - computeDefaultRuntimeConfiguration(spec, targetConfig, errorReporter) + computeDefaultRuntimeConfiguration(spec, targetConfig, messageReporter) } return GenerationInfo( @@ -464,14 +458,14 @@ object RustModelBuilder { private fun computeDefaultRuntimeConfiguration( userSpec: CargoDependencySpec?, targetConfig: TargetConfig, - errorReporter: ErrorReporter + messageReporter: MessageReporter ): CargoDependencySpec { fun CargoDependencySpec.useDefaultRuntimePath() { this.localPath = System.getenv("LOCAL_RUST_REACTOR_RT")?.also { // Print info to reduce surprise. If the env var is not set, // the runtime will be fetched from the internet by Cargo. If // the value is incorrect, Cargo will crash. - errorReporter.reportInfo("Using the Rust runtime from environment variable LOCAL_RUST_REACTOR_RT=$it") + messageReporter.nowhere().info("Using the Rust runtime from environment variable LOCAL_RUST_REACTOR_RT=$it") } if (localPath == null) { @@ -516,7 +510,7 @@ object RustModelBuilder { } if (!targetConfig.threading && PARALLEL_RT_FEATURE in userSpec.features) { - errorReporter.reportWarning("Threading cannot be disabled as it was enabled manually as a runtime feature.") + messageReporter.nowhere().warning("Threading cannot be disabled as it was enabled manually as a runtime feature.") } return userSpec diff --git a/core/src/main/kotlin/org/lflang/generator/rust/RustValidator.kt b/core/src/main/kotlin/org/lflang/generator/rust/RustValidator.kt index 964892f368..9b4f6de19b 100644 --- a/core/src/main/kotlin/org/lflang/generator/rust/RustValidator.kt +++ b/core/src/main/kotlin/org/lflang/generator/rust/RustValidator.kt @@ -5,11 +5,12 @@ import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import org.eclipse.lsp4j.DiagnosticSeverity -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.FileConfig import org.lflang.generator.CodeMap import org.lflang.generator.DiagnosticReporting import org.lflang.generator.Position +import org.lflang.generator.Range import org.lflang.generator.ValidationStrategy import org.lflang.generator.Validator import org.lflang.util.LFCommand @@ -24,15 +25,15 @@ import java.nio.file.Paths @Suppress("ArrayInDataClass") // Data classes here must not be used in data structures such as hashmaps. class RustValidator( private val fileConfig: FileConfig, - errorReporter: ErrorReporter, + messageReporter: MessageReporter, codeMaps: Map -): Validator(errorReporter, codeMaps) { +): Validator(messageReporter, codeMaps) { companion object { private val mapper = ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) private const val COMPILER_MESSAGE_REASON = "compiler-message" } // See the following reference for details on cargo metadata: https://doc.rust-lang.org/cargo/commands/cargo-metadata.html - public data class RustMetadata( + data class RustMetadata( // Other fields exist, but we don't need them. The mapper is configured not to fail on unknown properties. @JsonProperty("workspace_root") private val _workspaceRoot: String, @JsonProperty("target_directory") private val _targetDirectory: String @@ -42,6 +43,7 @@ class RustValidator( val targetDirectory: Path get() = Paths.get(_targetDirectory) } + // See the following references for details on these data classes: // * https://doc.rust-lang.org/cargo/reference/external-tools.html#json-messages // * https://doc.rust-lang.org/rustc/json.html @@ -106,36 +108,40 @@ class RustValidator( get() = Position.fromOneBased(lineStart, columnStart) val end: Position get() = Position.fromOneBased(lineEnd, columnEnd) + val range: Range get() = Range(start, end) } private data class RustSpanExpansion( @JsonProperty("span") val span: RustSpan, @JsonProperty("macro_decl_name") val macroDeclName: String, @JsonProperty("def_site_span") val defSiteSpan: RustSpan? ) + private data class RustSpanText( @JsonProperty("text") val text: String, @JsonProperty("highlight_start") val highlightStart: Int, @JsonProperty("highlight_end") val highlightEnd: Int ) - private var _metadata: RustMetadata? = null - public fun getMetadata(): RustMetadata? { - val nullableCommand = LFCommand.get("cargo", listOf("metadata", "--format-version", "1"), true, fileConfig.srcGenPkgPath) - _metadata = _metadata ?: nullableCommand?.let { command -> - command.run { false } - command.output.toString().lines().filter { it.startsWith("{") }.mapNotNull { + val metadata: RustMetadata? by lazy { + val command = + LFCommand.get("cargo", listOf("metadata", "--format-version", "1"), true, fileConfig.srcGenPkgPath) + ?: return@lazy null + + command.run { false } + command.output + .lines() + .filter { it.startsWith("{") } + .firstNotNullOfOrNull { try { mapper.readValue(it, RustMetadata::class.java) } catch (e: JsonProcessingException) { null } - }.firstOrNull() - } - return _metadata + } } - override fun getPossibleStrategies(): Collection = listOf(object: ValidationStrategy { + private val rustValidationStrategy = object : ValidationStrategy { override fun getCommand(generatedFile: Path?): LFCommand { return LFCommand.get( "cargo", @@ -147,36 +153,39 @@ class RustValidator( override fun getErrorReportingStrategy() = DiagnosticReporting.Strategy { _, _, _ -> } - override fun getOutputReportingStrategy() = DiagnosticReporting.Strategy { - validationOutput, errorReporter, map -> validationOutput.lines().forEach { messageLine -> - if (messageLine.isNotBlank() && mapper.readValue(messageLine, RustOutput::class.java).reason == COMPILER_MESSAGE_REASON) { - val message = mapper.readValue(messageLine, RustCompilerMessage::class.java).message - if (message.spans.isEmpty()) errorReporter.report(null, message.severity, message.message) - for (s: RustSpan in message.spans) { - val p: Path? = getMetadata()?.workspaceRoot?.resolve(s.fileName) - map[p]?.let { - for (lfSourcePath: Path in it.lfSourcePaths()) { - errorReporter.report( - lfSourcePath, - message.severity, - DiagnosticReporting.messageOf(message.message, p, s.start), - it.adjusted(lfSourcePath, s.start), - it.adjusted(lfSourcePath, s.end), - ) - } - } + override fun getOutputReportingStrategy() = DiagnosticReporting.Strategy { validationOutput, errorReporter, map -> + for (messageLine in validationOutput.lines()) { + if (messageLine.isBlank() + || mapper.readValue(messageLine, RustOutput::class.java).reason != COMPILER_MESSAGE_REASON + ) continue + + val message = mapper.readValue(messageLine, RustCompilerMessage::class.java).message + + if (message.spans.isEmpty()) errorReporter.nowhere().report(message.severity, message.message) + for (span in message.spans) { + val genFilePath = metadata?.workspaceRoot?.resolve(span.fileName) + val codeMap = map[genFilePath] ?: continue + + for (lfSourcePath in codeMap.lfSourcePaths()) { // fixme error is reported several times + errorReporter.at(lfSourcePath, codeMap.adjusted(lfSourcePath, span.range)).report( + message.severity, + DiagnosticReporting.messageOf(message.message, genFilePath, span.start) + ) } - message.rendered?.let { println(it) } } + message.rendered?.let { println(it) } } } override fun getPriority(): Int = 0 override fun isFullBatch(): Boolean = true - }) + } + + override fun getPossibleStrategies(): Collection = listOf(rustValidationStrategy) override fun getBuildReportingStrategies(): Pair = Pair( - possibleStrategies.first().errorReportingStrategy, possibleStrategies.first().outputReportingStrategy + rustValidationStrategy.errorReportingStrategy, + rustValidationStrategy.outputReportingStrategy ) } diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConnectionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConnectionGenerator.kt index 9083fc049e..acdd762bdc 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConnectionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConnectionGenerator.kt @@ -1,6 +1,6 @@ package org.lflang.generator.ts -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.hasMultipleConnections import org.lflang.isBank import org.lflang.lf.Connection @@ -13,7 +13,7 @@ import java.util.* */ class TSConnectionGenerator ( private val connections: List, - private val errorReporter: ErrorReporter + private val messageReporter: MessageReporter ) { // There is no generateClassProperties() for connections @@ -70,4 +70,4 @@ class TSConnectionGenerator ( } return connectionInstantiations.joinToString("\n") } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index a1ec96c9ac..c28d9277bb 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -1,6 +1,6 @@ package org.lflang.generator.ts -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.TargetConfig import org.lflang.generator.PrependOperator import org.lflang.generator.getTargetInitializer @@ -18,7 +18,7 @@ import java.util.* * registrations. */ class TSConstructorGenerator( - private val errorReporter: ErrorReporter, + private val messageReporter: MessageReporter, private val reactor: Reactor ) { @@ -94,8 +94,8 @@ class TSConstructorGenerator( isFederate: Boolean, networkMessageActions: List ): String { - val connections = TSConnectionGenerator(reactor.connections, errorReporter) - val reactions = TSReactionGenerator(errorReporter, reactor) + val connections = TSConnectionGenerator(reactor.connections, messageReporter) + val reactions = TSReactionGenerator(messageReporter, reactor) return with(PrependOperator) { """ diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSGenerator.kt index 111a6d5b9a..a9f1e60be4 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSGenerator.kt @@ -112,7 +112,7 @@ class TSGenerator( instantiationGraph - if (!canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return + if (!canGenerate(errorsOccurred(), mainDef, messageReporter, context)) return if (!isOsCompatible()) return // createMainReactorInstance() @@ -148,7 +148,7 @@ class TSGenerator( println("No .proto files have been imported. Skipping protocol buffer compilation.") } val parsingContext = SubContext(context, COLLECTED_DEPENDENCIES_PERCENT_PROGRESS, 100) - val validator = TSValidator(fileConfig, errorReporter, codeMaps) + val validator = TSValidator(fileConfig, messageReporter, codeMaps) if (!context.cancelIndicator.isCanceled) { if (context.mode == LFGeneratorContext.Mode.LSP_MEDIUM) { if (!passesChecks(validator, parsingContext)) { @@ -222,9 +222,9 @@ class TSGenerator( private fun copyConfigFiles() { FileUtil.copyFromClassPath(LIB_PATH, fileConfig.srcGenPath, true, true) for (configFile in CONFIG_FILES) { - var override = FileUtil.findAndCopyFile(configFile, fileConfig.srcGenPath, fileConfig); + val override = FileUtil.findAndCopyFile(configFile, fileConfig.srcGenPath, fileConfig); if (override != null) { - System.out.println("Using user-provided '" + override + "'"); + messageReporter.nowhere().info("Using user-provided '" + override + "'"); } else { System.out.println("Using default '" + configFile + "'"); } @@ -253,7 +253,7 @@ class TSGenerator( val (mainParameters, parameterCode) = parameterGenerator.generateParameters() tsCode.append(parameterCode) - val reactorGenerator = TSReactorGenerator(this, errorReporter, targetConfig) + val reactorGenerator = TSReactorGenerator(this, messageReporter, targetConfig) for (reactor in reactors) { tsCode.append(reactorGenerator.generateReactor(reactor)) } @@ -302,31 +302,33 @@ class TSGenerator( if (pnpmInstall != null) { val ret = pnpmInstall.run(context.cancelIndicator) if (ret != 0) { - val errors: String = pnpmInstall.errors.toString() - errorReporter.reportError( - GeneratorUtils.findTargetDecl(resource), - "ERROR: pnpm install command failed" + if (errors.isBlank()) "." else ":\n$errors") + val errors: String = pnpmInstall.errors + messageReporter.at(GeneratorUtils.findTargetDecl(resource)) + .error("pnpm install command failed" + if (errors.isBlank()) "." else ":\n$errors") } installProtoBufsIfNeeded(true, path, context.cancelIndicator) } else { - errorReporter.reportWarning( + messageReporter.nowhere( + ).warning( "Falling back on npm. To prevent an accumulation of replicated dependencies, " + - "it is highly recommended to install pnpm globally (npm install -g pnpm).") + "it is highly recommended to install pnpm globally (npm install -g pnpm)." + ) val npmInstall = commandFactory.createCommand("npm", if (production) listOf("install", "--production") else listOf("install"), path) if (npmInstall == null) { - errorReporter.reportError(NO_NPM_MESSAGE) + messageReporter.nowhere().error(NO_NPM_MESSAGE) return } if (npmInstall.run(context.cancelIndicator) != 0) { - errorReporter.reportError( - GeneratorUtils.findTargetDecl(resource), - "ERROR: npm install command failed: " + npmInstall.errors.toString()) - errorReporter.reportError( - GeneratorUtils.findTargetDecl(resource), "ERROR: npm install command failed." + - "\nFor installation instructions, see: https://www.npmjs.com/get-npm") + messageReporter.at(GeneratorUtils.findTargetDecl(resource)) + .error("npm install command failed: " + npmInstall.errors) + messageReporter.at(GeneratorUtils.findTargetDecl(resource)) + .error( + "npm install command failed." + + "\nFor installation instructions, see: https://www.npmjs.com/get-npm" + ) return } @@ -336,9 +338,8 @@ class TSGenerator( val rtPath = path.resolve("node_modules").resolve("@lf-lang").resolve("reactor-ts") val buildRuntime = commandFactory.createCommand("npm", listOf("run", "build"), rtPath) if (buildRuntime.run(context.cancelIndicator) != 0) { - errorReporter.reportError( - GeneratorUtils.findTargetDecl(resource), - "ERROR: unable to build runtime in dev mode: " + buildRuntime.errors.toString()) + messageReporter.at(GeneratorUtils.findTargetDecl(resource)) + .error("Unable to build runtime in dev mode: " + buildRuntime.errors.toString()) } } @@ -378,7 +379,7 @@ class TSGenerator( val protoc = commandFactory.createCommand("protoc", protocArgs, fileConfig.srcPath) if (protoc == null) { - errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1.") + messageReporter.nowhere().error("Processing .proto files requires libprotoc >= 3.6.1.") return } @@ -393,7 +394,7 @@ class TSGenerator( // targetConfig.compileLibraries.add('-l') // targetConfig.compileLibraries.add('protobuf-c') } else { - errorReporter.reportError("protoc failed with error code $returnCode.") + messageReporter.nowhere().error("protoc failed with error code $returnCode.") } // FIXME: report errors from this command. } @@ -419,14 +420,14 @@ class TSGenerator( val tsc = commandFactory.createCommand("npm", listOf("run", "build"), fileConfig.srcGenPkgPath) if (tsc == null) { - errorReporter.reportError(NO_NPM_MESSAGE) + messageReporter.nowhere().error(NO_NPM_MESSAGE) return } if (validator.run(tsc, cancelIndicator) == 0) { println("SUCCESS (compiling generated TypeScript code)") } else { - errorReporter.reportError("Compiler failed with the following errors:\n${tsc.errors}") + messageReporter.nowhere().error("Compiler failed with the following errors:\n${tsc.errors}") } } @@ -435,7 +436,7 @@ class TSGenerator( * @param context The context of the compilation. */ private fun concludeCompilation(context: LFGeneratorContext, codeMaps: Map) { - if (errorReporter.errorsOccurred) { + if (messageReporter.errorsOccurred) { context.unsuccessfulFinish() } else { context.finish(GeneratorResult.Status.COMPILED, codeMaps) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt index b82e1d20af..ac2e7af402 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt @@ -1,6 +1,6 @@ package org.lflang.generator.ts -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.ast.ASTUtils import org.lflang.generator.PrependOperator import org.lflang.isBank @@ -24,7 +24,7 @@ import java.util.LinkedList * @author Hokeun Kim */ class TSReactionGenerator( - private val errorReporter: ErrorReporter, + private val messageReporter: MessageReporter, private val reactor: Reactor ) { @@ -136,7 +136,11 @@ class TSReactionGenerator( is Timer -> "__Tag" is Action -> (trigOrSource.variable as Action).tsActionType is Port -> (trigOrSource.variable as Port).tsPortType - else -> errorReporter.reportError("Invalid trigger: ${trigOrSource.variable.name}") + else -> { + val message = "Invalid trigger: ${trigOrSource.variable.name}" + messageReporter.nowhere().error(message) + message + } } val portClassType = if (trigOrSource.variable.isMultiport) { @@ -298,7 +302,7 @@ class TSReactionGenerator( val functArg = effect.generateVarRef() when (val effectVar = effect.variable) { is Timer -> { - errorReporter.reportError("A timer cannot be an effect of a reaction") + messageReporter.nowhere().error("A timer cannot be an effect of a reaction") } is Action -> { diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index 4d537f5a19..29e99af361 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -17,7 +17,7 @@ import java.util.* */ class TSReactorGenerator( private val tsGenerator: TSGenerator, - private val errorReporter: ErrorReporter, + private val messageReporter: MessageReporter, private val targetConfig: TargetConfig ) { @@ -30,18 +30,6 @@ class TSReactorGenerator( """ } - // Initializer functions - fun getTargetInitializerHelper(param: Parameter, - list: List): String { - return if (list.isEmpty()) { - errorReporter.reportError(param, "Parameters must have a default value!") - } else if (list.size == 1) { - list[0] - } else { - list.joinToString(", ", "[", "]") - } - } - /** Generate the main app instance. This function is only used once * because all other reactors are instantiated as properties of the * main one. @@ -137,7 +125,7 @@ ${" |"..preamble.code.toText()} val actionGenerator = TSActionGenerator(reactor.actions, networkMessageActions) val portGenerator = TSPortGenerator(reactor.inputs, reactor.outputs) - val constructorGenerator = TSConstructorGenerator(errorReporter, reactor) + val constructorGenerator = TSConstructorGenerator(messageReporter, reactor) return with(PrependOperator) { """ |// =============== START reactor class ${reactor.name} diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSValidator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSValidator.kt index b040cd297f..65af1b8f11 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSValidator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSValidator.kt @@ -4,16 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import org.eclipse.lsp4j.DiagnosticSeverity -import org.eclipse.xtext.util.CancelIndicator -import org.lflang.ErrorReporter +import org.lflang.MessageReporter import org.lflang.FileConfig -import org.lflang.generator.CodeMap -import org.lflang.generator.DiagnosticReporting -import org.lflang.generator.HumanReadableReportingStrategy -import org.lflang.generator.LFGeneratorContext -import org.lflang.generator.Position -import org.lflang.generator.ValidationStrategy -import org.lflang.generator.Validator +import org.lflang.generator.* import org.lflang.util.LFCommand import java.nio.file.Path import java.util.regex.Pattern @@ -31,15 +24,15 @@ private val TSC_LABEL: Pattern = Pattern.compile("((?<=\\s))(~+)") @Suppress("ArrayInDataClass") // Data classes here must not be used in data structures such as hashmaps. class TSValidator( private val fileConfig: FileConfig, - errorReporter: ErrorReporter, + messageReporter: MessageReporter, codeMaps: Map -): Validator(errorReporter, codeMaps) { +): Validator(messageReporter, codeMaps) { private class TSLinter( private val fileConfig: FileConfig, - errorReporter: ErrorReporter, + messageReporter: MessageReporter, codeMaps: Map - ): Validator(errorReporter, codeMaps) { + ): Validator(messageReporter, codeMaps) { companion object { private val mapper = ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) } @@ -70,6 +63,7 @@ class TSValidator( ) { val start: Position = Position.fromOneBased(line, column) val end: Position = if (endLine >= line) Position.fromOneBased(endLine, endColumn) else start.plus(" ") + val range: Range get() = Range(start, end) val severity: DiagnosticSeverity = when (_severity) { 0 -> DiagnosticSeverity.Information 1 -> DiagnosticSeverity.Warning @@ -83,39 +77,32 @@ class TSValidator( ) override fun getPossibleStrategies(): Collection = listOf(object: ValidationStrategy { - override fun getCommand(generatedFile: Path?): LFCommand? { - return generatedFile?.let { - LFCommand.get( - "npx", - listOf("eslint", "--format", "json", fileConfig.srcGenPkgPath.relativize(it).toString()), - true, - fileConfig.srcGenPkgPath - ) - } - } + override fun getCommand(generatedFile: Path): LFCommand = + LFCommand.get( + "npx", + listOf("eslint", "--format", "json", fileConfig.srcGenPkgPath.relativize(generatedFile).toString()), + true, + fileConfig.srcGenPkgPath + ) override fun getErrorReportingStrategy() = DiagnosticReporting.Strategy { _, _, _ -> } - override fun getOutputReportingStrategy() = DiagnosticReporting.Strategy { - validationOutput, errorReporter, map -> validationOutput.lines().filter { it.isNotBlank() }.forEach { - line: String -> mapper.readValue(line, Array::class.java).forEach { - output: ESLintOutput -> output.messages.forEach { - message: ESLintMessage -> - val genPath: Path = fileConfig.srcGenPkgPath.resolve(output.filePath) - map[genPath]?.let { - codeMap -> - codeMap.lfSourcePaths().forEach { - val lfStart = codeMap.adjusted(it, message.start) - val lfEnd = codeMap.adjusted(it, message.end) - if (!lfStart.equals(Position.ORIGIN)) { // Ignore linting errors in non-user-supplied code. - errorReporter.report( - it, + override fun getOutputReportingStrategy() = DiagnosticReporting.Strategy { validationOutput, errorReporter, map -> + for (line in validationOutput.lines().filter { it.isNotBlank() }) { + for (output in mapper.readValue(line, Array::class.java)) { + for (message in output.messages) { + + val genPath = fileConfig.srcGenPkgPath.resolve(output.filePath) + val codeMap = map[genPath] ?: continue + + for (path in codeMap.lfSourcePaths()) { + val range = codeMap.adjusted(path, message.range) + if (range.startInclusive != Position.ORIGIN) { // Ignore linting errors in non-user-supplied code. + errorReporter.at(path, range) + .report( message.severity, - DiagnosticReporting.messageOf(message.message, genPath, message.start), - lfStart, - if (lfEnd > lfStart) lfEnd else lfStart.plus(" "), + DiagnosticReporting.messageOf(message.message, genPath, message.start) ) - } } } } @@ -134,9 +121,9 @@ class TSValidator( override fun getPossibleStrategies(): Collection = listOf(object: ValidationStrategy { - override fun getCommand(generatedFile: Path?): LFCommand? { // FIXME: Add "--incremental" argument if we update to TypeScript 4 - return LFCommand.get("npx", listOf("tsc", "--pretty", "--noEmit"), true, fileConfig.srcGenPkgPath) - } + override fun getCommand(generatedFile: Path): LFCommand { // FIXME: Add "--incremental" argument if we update to TypeScript 4 + return LFCommand.get("npx", listOf("tsc", "--pretty", "--noEmit"), true, fileConfig.srcGenPkgPath) + } override fun getErrorReportingStrategy() = DiagnosticReporting.Strategy { _, _, _ -> } @@ -158,7 +145,7 @@ class TSValidator( * @param context The context of the current build. */ fun doLint(context: LFGeneratorContext) { - TSLinter(fileConfig, errorReporter, codeMaps).doValidate(context) + TSLinter(fileConfig, messageReporter, codeMaps).doValidate(context) } // If this is not true, then the user might as well be writing JavaScript. diff --git a/core/src/test/java/org/lflang/tests/compiler/LetInferenceTests.java b/core/src/test/java/org/lflang/tests/compiler/LetInferenceTests.java index 38387ac8a4..8f78cd5906 100644 --- a/core/src/test/java/org/lflang/tests/compiler/LetInferenceTests.java +++ b/core/src/test/java/org/lflang/tests/compiler/LetInferenceTests.java @@ -36,7 +36,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.lflang.DefaultErrorReporter; +import org.lflang.DefaultMessageReporter; import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -125,7 +125,7 @@ public void testLet() throws Exception { } ReactorInstance mainInstance = - new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultErrorReporter()); + new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultMessageReporter()); for (ReactorInstance reactorInstance : mainInstance.children) { if (reactorInstance.isGeneratedDelay()) { diff --git a/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java b/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java index 516c712498..182a421e10 100644 --- a/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java +++ b/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java @@ -37,7 +37,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.lflang.DefaultErrorReporter; +import org.lflang.DefaultMessageReporter; import org.lflang.ModelInfo; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Instantiation; @@ -144,7 +144,7 @@ public void cyclicDependency() throws Exception { } ReactorInstance instance = - new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultErrorReporter()); + new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultMessageReporter()); Assertions.assertFalse(instance.getCycles().isEmpty()); } @@ -171,7 +171,7 @@ public void circularInstantiation() throws Exception { Assertions.assertNotNull(model); ModelInfo info = new ModelInfo(); - info.update(model, new DefaultErrorReporter()); + info.update(model, new DefaultMessageReporter()); Assertions.assertTrue( info.instantiationGraph.hasCycles() == true, "Did not detect cyclic instantiation."); } diff --git a/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java b/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java index 8b8ef52a8f..e5c94dbec3 100644 --- a/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java +++ b/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java @@ -3,8 +3,8 @@ import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.lflang.DefaultErrorReporter; -import org.lflang.ErrorReporter; +import org.lflang.DefaultMessageReporter; +import org.lflang.MessageReporter; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; @@ -17,7 +17,7 @@ public class PortInstanceTests { - private ErrorReporter reporter = new DefaultErrorReporter(); + private MessageReporter reporter = new DefaultMessageReporter(); private static LfFactory factory = LfFactory.eINSTANCE; @Test diff --git a/core/src/test/java/org/lflang/tests/compiler/RangeTests.java b/core/src/test/java/org/lflang/tests/compiler/RangeTests.java index 2c17145aaa..07d7a3da34 100644 --- a/core/src/test/java/org/lflang/tests/compiler/RangeTests.java +++ b/core/src/test/java/org/lflang/tests/compiler/RangeTests.java @@ -4,8 +4,8 @@ import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.lflang.DefaultErrorReporter; -import org.lflang.ErrorReporter; +import org.lflang.DefaultMessageReporter; +import org.lflang.MessageReporter; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactorInstance; import org.lflang.generator.RuntimeRange; @@ -16,7 +16,7 @@ public class RangeTests { - private ErrorReporter reporter = new DefaultErrorReporter(); + private MessageReporter reporter = new DefaultMessageReporter(); private static LfFactory factory = LfFactory.eINSTANCE; @Test diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index a9c901f456..b1b52b0c2e 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -35,7 +35,7 @@ import org.eclipse.xtext.util.RuntimeIOException; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; -import org.lflang.DefaultErrorReporter; +import org.lflang.DefaultMessageReporter; import org.lflang.FileConfig; import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; @@ -425,7 +425,7 @@ private void configure(LFTest test, Configurator configurator, TestLevel level) props, r, fileAccess, - fileConfig -> new DefaultErrorReporter()); + fileConfig -> new DefaultMessageReporter()); test.configure(context); diff --git a/lsp/src/main/java/org/lflang/diagram/lsp/LanguageDiagramServer.java b/lsp/src/main/java/org/lflang/diagram/lsp/LanguageDiagramServer.java index 96e887d242..0531d97721 100644 --- a/lsp/src/main/java/org/lflang/diagram/lsp/LanguageDiagramServer.java +++ b/lsp/src/main/java/org/lflang/diagram/lsp/LanguageDiagramServer.java @@ -19,7 +19,7 @@ import org.eclipse.xtext.ide.server.LanguageServerImpl; import org.eclipse.xtext.service.AbstractGenericModule; import org.eclipse.xtext.util.Modules2; -import org.lflang.generator.LanguageServerErrorReporter; +import org.lflang.generator.LanguageServerMessageReporter; import org.lflang.ide.LFIdeSetup; /** @@ -77,7 +77,7 @@ public void onConnect() { super.onConnect(); constraints.setClient((KGraphLanguageClient) languageClient); rectPack.setClient((KGraphLanguageClient) languageClient); - LanguageServerErrorReporter.setClient(languageClient); + LanguageServerMessageReporter.setClient(languageClient); lfExtension.setClient(languageClient); // The following is needed because VS Code treats System.err like System.out and System.out // like a shout