Search in sources :

Example 1 with ErrorReporter

use of org.lflang.ErrorReporter in project lingua-franca by lf-lang.

the class GeneratorBase method doGenerate.

/**
 * Generate code from the Lingua Franca model contained by the specified resource.
 *
 * This is the main entry point for code generation. This base class finds all
 * reactor class definitions, including any reactors defined in imported .lf files
 * (except any main reactors in those imported files), and adds them to the
 * {@link #GeneratorBase.reactors reactors} list. If errors occur during
 * generation, then a subsequent call to errorsOccurred() will return true.
 * @param resource The resource containing the source code.
 * @param context Context relating to invocation of the code generator.
 * In stand alone mode, this object is also used to relay CLI arguments.
 */
public void doGenerate(Resource resource, LFGeneratorContext context) {
    GeneratorUtils.setTargetConfig(context, GeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter);
    cleanIfNeeded(context);
    printInfo(context.getMode());
    // Markers mark problems in the Eclipse IDE when running in integrated mode.
    if (errorReporter instanceof EclipseErrorReporter) {
        ((EclipseErrorReporter) errorReporter).clearMarkers();
    }
    ASTUtils.setMainName(fileConfig.resource, fileConfig.name);
    createMainInstantiation();
    // 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(fileConfig).conflicts) {
            errorReporter.reportError(this.mainDef.getReactorClass(), "Conflicting main reactor in " + conflict);
        }
    }
    // Configure the command factory
    commandFactory.setVerbose();
    if (Objects.equal(context.getMode(), LFGeneratorContext.Mode.STANDALONE) && context.getArgs().containsKey("quiet")) {
        commandFactory.setQuiet();
    }
    // This must be done before desugaring delays below.
    analyzeFederates(context);
    // Process target files. Copy each of them into the src-gen dir.
    // FIXME: Should we do this here? This doesn't make sense for federates the way it is
    // done here.
    copyUserFiles(this.targetConfig, this.fileConfig);
    // Collect reactors and create an instantiation graph.
    // These are needed to figure out which resources we need
    // to validate, which happens in setResources().
    setReactorsAndInstantiationGraph(context.getMode());
    GeneratorUtils.validate(context, fileConfig, instantiationGraph, errorReporter);
    List<Resource> allResources = GeneratorUtils.getResources(reactors);
    resources.addAll(// FIXME: This filter reproduces the behavior of the method it replaces. But why must it be so complicated? Why are we worried about weird corner cases like this?
    allResources.stream().filter(it -> !Objects.equal(it, fileConfig.resource) || mainDef != null && it == mainDef.getReactorClass().eResource()).map(it -> GeneratorUtils.getLFResource(it, fileConfig.getSrcGenBasePath(), context, errorReporter)).collect(Collectors.toList()));
    GeneratorUtils.accommodatePhysicalActionsIfPresent(allResources, getTarget().setsKeepAliveOptionAutomatically(), targetConfig, errorReporter);
    // FIXME: Should the GeneratorBase pull in `files` from imported
    // resources?
    // Reroute connections that have delays associated with them via
    // generated delay reactors.
    transformDelays();
    // Transform connections that reside in mutually exclusive modes and are otherwise conflicting
    // This should be done before creating the instantiation graph
    transformConflictingConnectionsInModalReactors();
    // Invoke these functions a second time because transformations
    // may have introduced new reactors!
    setReactorsAndInstantiationGraph(context.getMode());
    // Check for existence and support of modes
    hasModalReactors = IterableExtensions.exists(reactors, it -> !it.getModes().isEmpty());
    checkModalReactorSupport(false);
    enableSupportForSerializationIfApplicable(context.getCancelIndicator());
}
Also used : SupportedSerializers(org.lflang.federated.serialization.SupportedSerializers) Delay(org.lflang.lf.Delay) FedASTUtils(org.lflang.federated.FedASTUtils) Action(org.lflang.lf.Action) InferredType(org.lflang.InferredType) Matcher(java.util.regex.Matcher) Map(java.util.Map) Instantiation(org.lflang.lf.Instantiation) Objects(com.google.common.base.Objects) Path(java.nio.file.Path) Connection(org.lflang.lf.Connection) MainConflictChecker(org.lflang.MainConflictChecker) TargetConfig(org.lflang.TargetConfig) Collection(java.util.Collection) Set(java.util.Set) EObject(org.eclipse.emf.ecore.EObject) Collectors(java.util.stream.Collectors) Parameter(org.lflang.lf.Parameter) List(java.util.List) Value(org.lflang.lf.Value) CollectionLiterals(org.eclipse.xtext.xbase.lib.CollectionLiterals) Target(org.lflang.Target) Resource(org.eclipse.emf.ecore.resource.Resource) Pattern(java.util.regex.Pattern) Pair(org.eclipse.xtext.xbase.lib.Pair) FileConfig(org.lflang.FileConfig) ASTUtils(org.lflang.ASTUtils) FederateInstance(org.lflang.federated.FederateInstance) Iterables(com.google.common.collect.Iterables) LfFactory(org.lflang.lf.LfFactory) ErrorReporter(org.lflang.ErrorReporter) AbstractLFValidator(org.lflang.validation.AbstractLFValidator) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) CancelIndicator(org.eclipse.xtext.util.CancelIndicator) Reaction(org.lflang.lf.Reaction) TimeUnit(org.lflang.TimeUnit) TimeValue(org.lflang.TimeValue) LinkedHashSet(java.util.LinkedHashSet) IMarker(org.eclipse.core.resources.IMarker) InstantiationGraph(org.lflang.graph.InstantiationGraph) CoordinationType(org.lflang.TargetProperty.CoordinationType) Model(org.lflang.lf.Model) IOException(java.io.IOException) Time(org.lflang.lf.Time) File(java.io.File) IteratorExtensions(org.eclipse.xtext.xbase.lib.IteratorExtensions) IterableExtensions(org.eclipse.xtext.xbase.lib.IterableExtensions) Paths(java.nio.file.Paths) Reactor(org.lflang.lf.Reactor) VarRef(org.lflang.lf.VarRef) MainConflictChecker(org.lflang.MainConflictChecker) Resource(org.eclipse.emf.ecore.resource.Resource)

Example 2 with ErrorReporter

use of org.lflang.ErrorReporter in project lingua-franca by lf-lang.

the class LFGenerator method doGenerate.

@Override
public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
    final LFGeneratorContext lfContext = LFGeneratorContext.lfGeneratorContextOf(context, resource);
    // The fastest way to generate code is to not generate any code.
    if (lfContext.getMode() == LFGeneratorContext.Mode.LSP_FAST)
        return;
    final Target target = Target.fromDecl(ASTUtils.targetDecl(resource));
    assert target != null;
    FileConfig fileConfig;
    try {
        fileConfig = Objects.requireNonNull(createFileConfig(target, resource, fsa, lfContext));
    } catch (IOException e) {
        throw new RuntimeIOException("Error during FileConfig instantiation", e);
    }
    final ErrorReporter errorReporter = lfContext.constructErrorReporter(fileConfig);
    final GeneratorBase generator = createGenerator(target, fileConfig, errorReporter);
    if (generator != null) {
        generator.doGenerate(resource, lfContext);
        generatorErrorsOccurred = generator.errorsOccurred();
    }
    if (errorReporter instanceof LanguageServerErrorReporter) {
        ((LanguageServerErrorReporter) errorReporter).publishDiagnostics();
    }
}
Also used : FileConfig(org.lflang.FileConfig) RuntimeIOException(org.eclipse.xtext.util.RuntimeIOException) Target(org.lflang.Target) ErrorReporter(org.lflang.ErrorReporter) IOException(java.io.IOException) RuntimeIOException(org.eclipse.xtext.util.RuntimeIOException)

Example 3 with ErrorReporter

use of org.lflang.ErrorReporter in project lingua-franca by lf-lang.

the class PythonValidator method getPossibleStrategies.

@Override
protected Collection<ValidationStrategy> getPossibleStrategies() {
    return List.of(new ValidationStrategy() {

        @Override
        public LFCommand getCommand(Path generatedFile) {
            return LFCommand.get("python3", List.of("-c", "import compileall; compileall.compile_dir('.', quiet=1)"), true, fileConfig.getSrcGenPkgPath());
        }

        @Override
        public Strategy getErrorReportingStrategy() {
            return (a, b, c) -> {
            };
        }

        @Override
        public Strategy getOutputReportingStrategy() {
            return (String validationOutput, ErrorReporter errorReporter, Map<Path, CodeMap> map) -> {
                String[] lines = (validationOutput + "\n\n\n").lines().toArray(String[]::new);
                for (int i = 0; i < lines.length - 3; i++) {
                    if (!tryReportTypical(lines, i)) {
                        tryReportAlternative(lines, i);
                    }
                }
            };
        }

        /**
         * Try to report a typical error message from the Python compiler.
         *
         * @param lines The lines of output from the compiler.
         * @param i     The current index at which a message may start. Guaranteed to be less
         *              than
         *              {@code lines.length - 3}.
         * @return Whether an error message was reported.
         */
        private boolean tryReportTypical(String[] lines, int i) {
            Matcher main = DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]);
            Matcher messageMatcher = MESSAGE.matcher(lines[i + 3]);
            String message = messageMatcher.matches() ? messageMatcher.group() : "Syntax Error";
            if (main.matches()) {
                int line = Integer.parseInt(main.group("line"));
                CodeMap map = codeMaps.get(fileConfig.getSrcGenPkgPath().resolve(Path.of(main.group("path"))).normalize());
                // Column is just a placeholder.
                Position genPosition = Position.fromOneBased(line, Integer.MAX_VALUE);
                if (map == null) {
                    // Undesirable fallback
                    errorReporter.report(null, DiagnosticSeverity.Error, message, 1);
                } 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());
                    }
                }
                return true;
            }
            return false;
        }

        /**
         * Try to report an alternative error message from the Python compiler.
         *
         * @param lines The lines of output from the compiler.
         * @param i     The current index at which a message may start.
         */
        private void tryReportAlternative(String[] lines, int i) {
            Matcher main = ALT_DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]);
            if (main.matches()) {
                int line = Integer.parseInt(main.group("line"));
                Iterable<CodeMap> relevantMaps = codeMaps.keySet().stream().filter(p -> main.group().contains(p.getFileName().toString())).map(codeMaps::get)::iterator;
                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: ", ""), map.adjusted(lfFile, Position.fromOneBased(line, 1)).getOneBasedLine());
                    }
                }
            }
        }

        @Override
        public boolean isFullBatch() {
            return true;
        }

        @Override
        public int getPriority() {
            return 0;
        }
    }, new ValidationStrategy() {

        @Override
        public LFCommand getCommand(Path generatedFile) {
            return LFCommand.get("pylint", List.of("--output-format=json", generatedFile.getFileName().toString()), true, fileConfig.getSrcGenPath());
        }

        @Override
        public Strategy getErrorReportingStrategy() {
            return (a, b, c) -> {
            };
        }

        @Override
        public Strategy getOutputReportingStrategy() {
            return (validationOutput, errorReporter, codeMaps) -> {
                try {
                    for (PylintMessage message : mapper.readValue(validationOutput, PylintMessage[].class)) {
                        if (shouldIgnore(message))
                            continue;
                        CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath()));
                        if (map != null) {
                            for (Path lfFile : map.lfSourcePaths()) {
                                Function<Position, Position> adjust = p -> map.adjusted(lfFile, p);
                                String humanMessage = DiagnosticReporting.messageOf(message.message, message.getPath(fileConfig.getSrcGenPath()), message.getStart());
                                Position lfStart = adjust.apply(message.getStart());
                                Position lfEnd = adjust.apply(message.getEnd());
                                bestEffortReport(errorReporter, adjust, lfStart, lfEnd, lfFile, message.getSeverity(), humanMessage);
                            }
                        }
                    }
                } catch (JsonProcessingException e) {
                    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.");
                    e.printStackTrace();
                }
            };
        }

        /**
         * Return whether the given message should be ignored.
         * @param message A Pylint message that is a candidate to be reported.
         * @return whether {@code message} should be reported.
         */
        private boolean shouldIgnore(PylintMessage message) {
            // Code generation does not preserve whitespace, so this check is unreliable.
            if (message.symbol.equals("trailing-whitespace") || message.symbol.equals("line-too-long"))
                return true;
            // This filters out Pylint messages concerning missing members in types defined by protocol buffers.
            // FIXME: Make this unnecessary, perhaps using https://github.com/nelfin/pylint-protobuf.
            Matcher matcher = PylintNoNamePattern.matcher(message.message);
            return message.symbol.equals("no-member") && matcher.matches() && protoNames.contains(matcher.group("name"));
        }

        /**
         * Make a best-effort attempt to place the diagnostic on the correct line.
         */
        private void bestEffortReport(ErrorReporter errorReporter, Function<Position, Position> adjust, Position lfStart, Position lfEnd, Path file, DiagnosticSeverity severity, String humanMessage) {
            if (!lfEnd.equals(Position.ORIGIN) && !lfStart.equals(Position.ORIGIN)) {
                // Ideal case
                errorReporter.report(file, severity, humanMessage, lfStart, lfEnd);
            } else {
                // Fallback: Try to report on the correct line, or failing that, just line 1.
                if (lfStart.equals(Position.ORIGIN))
                    lfStart = adjust.apply(Position.fromZeroBased(lfStart.getZeroBasedLine(), Integer.MAX_VALUE));
                // 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());
            }
        }

        @Override
        public boolean isFullBatch() {
            return false;
        }

        @Override
        public int getPriority() {
            return 1;
        }
    });
}
Also used : Path(java.nio.file.Path) JsonProperty(com.fasterxml.jackson.annotation.JsonProperty) Strategy(org.lflang.generator.DiagnosticReporting.Strategy) ErrorReporter(org.lflang.ErrorReporter) Function(java.util.function.Function) DeserializationFeature(com.fasterxml.jackson.databind.DeserializationFeature) Matcher(java.util.regex.Matcher) Map(java.util.Map) Path(java.nio.file.Path) DiagnosticSeverity(org.eclipse.lsp4j.DiagnosticSeverity) Position(org.lflang.generator.Position) ImmutableSet(com.google.common.collect.ImmutableSet) ImmutableMap(com.google.common.collect.ImmutableMap) Collection(java.util.Collection) Validator(org.lflang.generator.Validator) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) Set(java.util.Set) Include(com.fasterxml.jackson.annotation.JsonInclude.Include) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) LFCommand(org.lflang.util.LFCommand) DiagnosticReporting(org.lflang.generator.DiagnosticReporting) List(java.util.List) CodeMap(org.lflang.generator.CodeMap) ValidationStrategy(org.lflang.generator.ValidationStrategy) JsonInclude(com.fasterxml.jackson.annotation.JsonInclude) Pattern(java.util.regex.Pattern) FileConfig(org.lflang.FileConfig) Matcher(java.util.regex.Matcher) Position(org.lflang.generator.Position) ValidationStrategy(org.lflang.generator.ValidationStrategy) LFCommand(org.lflang.util.LFCommand) ErrorReporter(org.lflang.ErrorReporter) CodeMap(org.lflang.generator.CodeMap) DiagnosticSeverity(org.eclipse.lsp4j.DiagnosticSeverity) Strategy(org.lflang.generator.DiagnosticReporting.Strategy) ValidationStrategy(org.lflang.generator.ValidationStrategy) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap) CodeMap(org.lflang.generator.CodeMap) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException)

Example 4 with ErrorReporter

use of org.lflang.ErrorReporter in project lingua-franca by lf-lang.

the class IntegratedBuilder method run.

/* ------------------------- PUBLIC METHODS -------------------------- */
/**
 * Generates code from the Lingua Franca file {@code f}.
 * @param uri The URI of a Lingua Franca file.
 * @param mustComplete Whether the build must be taken to completion.
 * @return The result of the build.
 */
public GeneratorResult run(URI uri, boolean mustComplete, ReportProgress reportProgress, CancelIndicator cancelIndicator) {
    // FIXME: A refactoring of the following line is needed. This refactor will affect FileConfig and
    // org.lflang.lfc.Main. The issue is that there is duplicated code.
    fileAccess.setOutputPath(FileConfig.findPackageRoot(Path.of(uri.path()), s -> {
    }).resolve(FileConfig.DEFAULT_SRC_GEN_DIR).toString());
    List<EObject> parseRoots = getResource(uri).getContents();
    if (parseRoots.isEmpty())
        return GeneratorResult.NOTHING;
    ErrorReporter errorReporter = new LanguageServerErrorReporter(parseRoots.get(0));
    reportProgress.apply("Validating...", START_PERCENT_PROGRESS);
    validate(uri, errorReporter);
    reportProgress.apply("Code validation complete.", VALIDATED_PERCENT_PROGRESS);
    if (cancelIndicator.isCanceled())
        return GeneratorResult.CANCELLED;
    if (errorReporter.getErrorsOccurred())
        return GeneratorResult.FAILED;
    reportProgress.apply("Generating code...", VALIDATED_PERCENT_PROGRESS);
    return doGenerate(uri, mustComplete, reportProgress, cancelIndicator);
}
Also used : Properties(java.util.Properties) URI(org.eclipse.emf.common.util.URI) Severity(org.eclipse.xtext.diagnostics.Severity) ErrorReporter(org.lflang.ErrorReporter) Inject(com.google.inject.Inject) GeneratorDelegate(org.eclipse.xtext.generator.GeneratorDelegate) IResourceValidator(org.eclipse.xtext.validation.IResourceValidator) EObject(org.eclipse.emf.ecore.EObject) ResourceSet(org.eclipse.emf.ecore.resource.ResourceSet) List(java.util.List) Provider(com.google.inject.Provider) JavaIoFileSystemAccess(org.eclipse.xtext.generator.JavaIoFileSystemAccess) Issue(org.eclipse.xtext.validation.Issue) CancelIndicator(org.eclipse.xtext.util.CancelIndicator) Resource(org.eclipse.emf.ecore.resource.Resource) Path(java.nio.file.Path) FileConfig(org.lflang.FileConfig) Mode(org.lflang.generator.LFGeneratorContext.Mode) CheckMode(org.eclipse.xtext.validation.CheckMode) ErrorReporter(org.lflang.ErrorReporter) EObject(org.eclipse.emf.ecore.EObject)

Aggregations

ErrorReporter (org.lflang.ErrorReporter)4 FileConfig (org.lflang.FileConfig)4 Path (java.nio.file.Path)3 List (java.util.List)3 IOException (java.io.IOException)2 Collection (java.util.Collection)2 Map (java.util.Map)2 Set (java.util.Set)2 Matcher (java.util.regex.Matcher)2 Pattern (java.util.regex.Pattern)2 EObject (org.eclipse.emf.ecore.EObject)2 Resource (org.eclipse.emf.ecore.resource.Resource)2 CancelIndicator (org.eclipse.xtext.util.CancelIndicator)2 Target (org.lflang.Target)2 JsonInclude (com.fasterxml.jackson.annotation.JsonInclude)1 Include (com.fasterxml.jackson.annotation.JsonInclude.Include)1 JsonProperty (com.fasterxml.jackson.annotation.JsonProperty)1 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)1 DeserializationFeature (com.fasterxml.jackson.databind.DeserializationFeature)1 ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper)1