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());
}
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();
}
}
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;
}
});
}
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);
}
Aggregations