diff --git a/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java b/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java index f6b04494a7..016fe1f2b5 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java @@ -1,20 +1,10 @@ package org.lflang.federated.generator; -import static org.lflang.ast.ASTUtils.convertToEmptyListIfNull; - -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; import org.eclipse.emf.ecore.resource.Resource; -import org.lflang.MessageReporter; -import org.lflang.ast.ASTUtils; import org.lflang.generator.GeneratorUtils; import org.lflang.generator.LFGeneratorContext; -import org.lflang.lf.KeyValuePair; import org.lflang.target.Target; import org.lflang.target.TargetConfig; -import org.lflang.target.property.FileListProperty; -import org.lflang.util.FileUtil; /** * Subclass of TargetConfig with a specialized constructor for creating configurations for @@ -43,7 +33,7 @@ public FederateTargetConfig(LFGeneratorContext context, Resource federateResourc load(federationResource, reporter); // Load properties from the federate file - mergeImportedConfig(federateResource, federationResource, reporter); + mergeImportedConfig(federateResource, federationResource, p -> p.loadFromFederate(), reporter); // Load properties from the generator context load(context.getArgs(), reporter); @@ -52,66 +42,4 @@ public FederateTargetConfig(LFGeneratorContext context, Resource federateResourc this.validate(reporter); } - - /** - * If the federate that target configuration applies to is imported, merge target properties - * declared in the file that it was imported from. - * - * @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 messageReporter An error reporter to use when problems are encountered. - */ - private void mergeImportedConfig( - 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)) { - var importedTargetDecl = GeneratorUtils.findTargetDecl(federateResource); - var targetProperties = importedTargetDecl.getConfig(); - if (targetProperties != null) { - // Merge properties - update( - this, - convertToEmptyListIfNull(targetProperties.getPairs()), - getRelativePath(mainResource, federateResource), - messageReporter); - } - } - } - - private Path getRelativePath(Resource source, Resource target) { - return FileUtil.toPath(source.getURI()) - .getParent() - .relativize(FileUtil.toPath(target.getURI()).getParent()); - } - - /** - * Update the given configuration using the given target properties. - * - * @param config The configuration object to update. - * @param pairs AST node that holds all the target properties. - * @param relativePath The path from the main resource to the resource from which the new - * properties originate. - */ - public void update( - TargetConfig config, List pairs, Path relativePath, MessageReporter err) { - pairs.forEach( - pair -> { - var p = config.forName(pair.getName()); - if (p.isPresent()) { - var value = pair.getValue(); - var property = p.get(); - if (property instanceof FileListProperty fileListProperty) { - var files = - ASTUtils.elementToListOfStrings(value).stream() - .map(relativePath::resolve) // assume all paths are relative - .map(Objects::toString) - .toList(); - fileListProperty.update(config, files); - } else if (property.loadFromFederate()) { - p.get().update(this, pair, err); - } - } - }); - } } diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java index 759195474c..d8b3d2d7d4 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorBase.java +++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java @@ -31,11 +31,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; import org.eclipse.core.resources.IMarker; import org.eclipse.emf.ecore.EObject; @@ -80,18 +78,12 @@ */ public abstract class GeneratorBase extends AbstractLFValidator { - //////////////////////////////////////////// - //// Public fields. - /** The main (top-level) reactor instance. */ public ReactorInstance main; /** An error reporter for reporting any errors or warnings during the code generation */ public MessageReporter messageReporter; - //////////////////////////////////////////// - //// Protected fields. - /** The current target configuration. */ protected final TargetConfig targetConfig; @@ -125,9 +117,6 @@ public Instantiation getMainDef() { */ protected List reactors = new ArrayList<>(); - /** The set of resources referenced reactor classes reside in. */ - protected Set resources = new LinkedHashSet<>(); // FIXME: Why do we need this? - /** * Graph that tracks dependencies between instantiations. This is a graph where each node is a * Reactor (not a ReactorInstance) and an arc from Reactor A to Reactor B means that B contains an @@ -147,9 +136,6 @@ public Instantiation getMainDef() { /** Indicates whether the program has any watchdogs. This is used to check for support. */ public boolean hasWatchdogs = false; - // ////////////////////////////////////////// - // // Private fields. - /** A list ot AST transformations to apply before code generation */ private final List astTransformations = new ArrayList<>(); @@ -170,8 +156,26 @@ protected void registerTransformation(AstTransformation transformation) { astTransformations.add(transformation); } - // ////////////////////////////////////////// - // // Code generation functions to override for a concrete code generator. + /** + * If the given reactor is defined in another file, process its target properties so that they are + * reflected in the target configuration. + */ + private void loadTargetProperties(Resource resource) { + var mainFileConfig = this.context.getFileConfig(); + if (resource != mainFileConfig.resource) { + this.context + .getTargetConfig() + .mergeImportedConfig( + LFGenerator.createFileConfig( + resource, + mainFileConfig.getSrcGenBasePath(), + mainFileConfig.useHierarchicalBin) + .resource, + mainFileConfig.resource, + p -> p.loadFromImport(), + this.messageReporter); + } + } /** * Generate code from the Lingua Franca model contained by the specified resource. @@ -218,27 +222,21 @@ public void doGenerate(Resource resource, LFGeneratorContext 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, context.getFileConfig()); - // 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()); List allResources = GeneratorUtils.getResources(reactors); - resources.addAll( - allResources.stream().map(it -> GeneratorUtils.getLFResource(it, context)).toList()); GeneratorUtils.accommodatePhysicalActionsIfPresent( allResources, getTarget().setsKeepAliveOptionAutomatically(), targetConfig, messageReporter); - // FIXME: Should the GeneratorBase pull in {@code files} from imported - // resources? + + // Load target properties for all resources. + allResources.forEach(r -> loadTargetProperties(r)); for (AstTransformation transformation : astTransformations) { transformation.applyTransformation(reactors); @@ -246,7 +244,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Transform connections that reside in mutually exclusive modes and are otherwise conflicting // This should be done before creating the instantiation graph - transformConflictingConnectionsInModalReactors(); + transformConflictingConnectionsInModalReactors(allResources); // Invoke these functions a second time because transformations // may have introduced new reactors! @@ -415,9 +413,9 @@ protected void checkWatchdogSupport(boolean isSupported) { * Finds and transforms connections into forwarding reactions iff the connections have the same * destination as other connections or reaction in mutually exclusive modes. */ - private void transformConflictingConnectionsInModalReactors() { - for (LFResource r : resources) { - var transform = ASTUtils.findConflictingConnectionsInModalReactors(r.eResource); + private void transformConflictingConnectionsInModalReactors(List resources) { + for (Resource r : resources) { + var transform = ASTUtils.findConflictingConnectionsInModalReactors(r); if (!transform.isEmpty()) { var factory = LfFactory.eINSTANCE; for (Connection connection : transform) { diff --git a/core/src/main/java/org/lflang/generator/GeneratorUtils.java b/core/src/main/java/org/lflang/generator/GeneratorUtils.java index c3622c10e3..77240613e6 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorUtils.java +++ b/core/src/main/java/org/lflang/generator/GeneratorUtils.java @@ -9,15 +9,11 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.IteratorExtensions; -import org.lflang.FileConfig; import org.lflang.MessageReporter; -import org.lflang.ast.ASTUtils; import org.lflang.generator.LFGeneratorContext.Mode; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Instantiation; -import org.lflang.lf.KeyValuePair; -import org.lflang.lf.KeyValuePairs; import org.lflang.lf.Reactor; import org.lflang.lf.TargetDecl; import org.lflang.target.TargetConfig; @@ -101,29 +97,6 @@ public static List getResources(Iterable reactors) { return resources; } - /** - * Return the {@code LFResource} representation of the given resource. - * - * @param resource The {@code Resource} to be represented as an {@code LFResource} - * @param context The generator invocation context. - * @return the {@code LFResource} representation of the given resource. - */ - public static LFResource getLFResource(Resource resource, LFGeneratorContext context) { - var target = ASTUtils.targetDecl(resource); - var mainFileConfig = context.getFileConfig(); - var messageReporter = context.getErrorReporter(); - KeyValuePairs config = target.getConfig(); - var targetConfig = new TargetConfig(resource, context.getArgs(), messageReporter); - if (config != null) { - List pairs = config.getPairs(); - targetConfig.load(pairs != null ? pairs : List.of(), messageReporter); - } - FileConfig fc = - LFGenerator.createFileConfig( - resource, mainFileConfig.getSrcGenBasePath(), mainFileConfig.useHierarchicalBin); - return new LFResource(resource, fc, targetConfig); - } - /** * If the mode is Mode.EPOCH (the code generator is running in an Eclipse IDE), then refresh the * project. This will ensure that any generated files become visible in the project. diff --git a/core/src/main/java/org/lflang/generator/LFResource.java b/core/src/main/java/org/lflang/generator/LFResource.java deleted file mode 100644 index 02fe98d032..0000000000 --- a/core/src/main/java/org/lflang/generator/LFResource.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.lflang.generator; - -import org.eclipse.emf.ecore.resource.Resource; -import org.lflang.FileConfig; -import org.lflang.target.TargetConfig; - -/** - * A class that keeps metadata for discovered resources during code generation and the supporting - * structures associated with that resource. - * - * @author Soroush Bateni - */ -public class LFResource { - LFResource(Resource resource, FileConfig fileConfig, TargetConfig targetConfig) { - this.eResource = - resource; // FIXME: this is redundant because fileConfig already has the resource. - this.fileConfig = fileConfig; - this.targetConfig = targetConfig; - } - - /** Resource associated with a file either from the main .lf file or one of the imported ones. */ - Resource eResource; - - public Resource getEResource() { - return this.eResource; - } - ; - - /** - * The file config associated with 'resource' that can be used to discover files relative to that - * resource. - */ - FileConfig fileConfig; - - public FileConfig getFileConfig() { - return this.fileConfig; - } - ; - - /** The target config read from the resource. */ - TargetConfig targetConfig; - - public TargetConfig getTargetConfig() { - return this.targetConfig; - } - ; -} 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 694d09a22c..be0d544352 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -28,7 +28,6 @@ import static org.lflang.ast.ASTUtils.allPorts; import static org.lflang.ast.ASTUtils.allReactions; import static org.lflang.ast.ASTUtils.allStateVars; -import static org.lflang.ast.ASTUtils.convertToEmptyListIfNull; import static org.lflang.ast.ASTUtils.getInferredType; import static org.lflang.ast.ASTUtils.isInitialized; import static org.lflang.ast.ASTUtils.toDefinition; @@ -61,7 +60,6 @@ import org.lflang.generator.GeneratorResult; import org.lflang.generator.GeneratorUtils; import org.lflang.generator.LFGeneratorContext; -import org.lflang.generator.LFResource; import org.lflang.generator.ParameterInstance; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; @@ -582,6 +580,7 @@ private void generateCodeFor(String lfModuleName) throws IOException { code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); // Generate code for each reactor. generateReactorDefinitions(); + copyUserFiles(targetConfig, fileConfig); // Generate main instance, if there is one. // Note that any main reactors in imported files are ignored. @@ -691,42 +690,6 @@ private boolean hasDeadlines(List reactors) { return false; } - /** - * Look at the 'reactor' eResource. If it is an imported .lf file, gather preambles and relevant - * target properties associated with imported reactors. - */ - private void inspectReactorEResource(ReactorDecl reactor) { - // Check if the reactor definition is imported - if (reactor.eResource() != mainDef.getReactorClass().eResource()) { - // Find the LFResource corresponding to this eResource - LFResource lfResource = null; - for (var resource : resources) { - if (resource.getEResource() == reactor.eResource()) { - lfResource = resource; - break; - } - } - - if (lfResource != null) { - var config = lfResource.getTargetConfig(); - // FIXME: this should not happen here, but once, after collecting all the files. - copyUserFiles(config, lfResource.getFileConfig()); - - var pairs = convertToEmptyListIfNull(config.extractTargetDecl().getConfig().getPairs()); - pairs.forEach( - pair -> { - var p = config.forName((pair.getName())); - if (p.isPresent()) { - var property = p.get(); - if (property.loadFromImport()) { - property.update(this.targetConfig, pair, messageReporter); - } - } - }); - } - } - } - /** * Copy all files or directories listed in the target property {@code files}, {@code * cmake-include}, and {@code _fed_setup} into the src-gen folder of the main .lf file @@ -844,7 +807,6 @@ private void generateReactorChildren( if (r.reactorDeclaration != null && !generatedReactors.contains(newTpr)) { generatedReactors.add(newTpr); generateReactorChildren(r, generatedReactors); - inspectReactorEResource(r.reactorDeclaration); generateReactorClass(newTpr); } } diff --git a/core/src/main/java/org/lflang/target/TargetConfig.java b/core/src/main/java/org/lflang/target/TargetConfig.java index a5ce42c6cc..1aafa64201 100644 --- a/core/src/main/java/org/lflang/target/TargetConfig.java +++ b/core/src/main/java/org/lflang/target/TargetConfig.java @@ -24,7 +24,10 @@ ***************/ package org.lflang.target; +import static org.lflang.ast.ASTUtils.convertToEmptyListIfNull; + import com.google.gson.JsonObject; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -33,8 +36,10 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.eclipse.emf.ecore.resource.Resource; import org.lflang.MessageReporter; @@ -48,11 +53,13 @@ import org.lflang.lf.TargetDecl; import org.lflang.target.property.FastProperty; import org.lflang.target.property.FedSetupProperty; +import org.lflang.target.property.FileListProperty; import org.lflang.target.property.LoggingProperty; import org.lflang.target.property.NoCompileProperty; import org.lflang.target.property.TargetProperty; import org.lflang.target.property.TimeOutProperty; import org.lflang.target.property.type.TargetPropertyType; +import org.lflang.util.FileUtil; /** * A class for keeping the current target configuration. @@ -120,14 +127,32 @@ protected TargetConfig(Target target) { * * @param resource A resource to load from. * @param reporter A reporter for reporting issues. + * @param reporter A message reporter for reporting errors and warnings. */ protected void load(Resource resource, MessageReporter reporter) { var targetDecl = GeneratorUtils.findTargetDecl(resource); var properties = targetDecl.getConfig(); + // Load properties from file if (properties != null) { List pairs = properties.getPairs(); - this.load(pairs, reporter); + pairs.forEach( + pair -> { + var p = forName(pair.getName()); + if (p.isPresent()) { + var property = p.get(); + // Record the pair. + keyValuePairs.put(property, pair); + // Only update the config if the pair matches the type. + if (property.checkType(pair, reporter)) { + property.update(this, pair, reporter); + // Ignore properties if they are imported and must not load from imports. + } + } else { + reportUnsupportedTargetProperty( + pair.getName(), reporter.at(pair, Literals.KEY_VALUE_PAIR__NAME)); + } + }); } } @@ -148,6 +173,71 @@ public TargetConfig(Resource resource, GeneratorArguments args, MessageReporter validate(reporter); } + /** + * If the federate that target configuration applies to is imported, merge target properties + * declared in the file that it was imported from. + * + * @param importedResource The resource in which the target configuration is to be loaded from. + * @param mainResource The resource in which the main reactor is specified. + * @param loadOrNot Predicate to determine for each target property whether it should be loaded. + * @param messageReporter An error reporter to use when problems are encountered. + */ + public void mergeImportedConfig( + Resource importedResource, + Resource mainResource, + Predicate loadOrNot, + MessageReporter messageReporter) { + // If the federate is imported, then update the configuration based on target properties + // in the imported file. + if (!importedResource.equals(mainResource)) { + var importedTargetDecl = GeneratorUtils.findTargetDecl(importedResource); + var targetProperties = importedTargetDecl.getConfig(); + if (targetProperties != null) { + // Merge properties + update( + this, + convertToEmptyListIfNull(targetProperties.getPairs()), + FileUtil.getRelativePath(mainResource, importedResource), + loadOrNot, + messageReporter); + } + } + } + + /** + * Update the given configuration using the given target properties. + * + * @param config The configuration object to update. + * @param pairs AST node that holds all the target properties. + * @param relativePath The path from the main resource to the resource from which the new + * properties originate. + */ + public void update( + TargetConfig config, + List pairs, + Path relativePath, + Predicate loadOrNot, + MessageReporter err) { + pairs.forEach( + pair -> { + var p = config.forName(pair.getName()); + if (p.isPresent()) { + var value = pair.getValue(); + var property = p.get(); + if (property instanceof FileListProperty fileListProperty) { + var files = + ASTUtils.elementToListOfStrings(value).stream() + .map(relativePath::resolve) // assume all paths are relative + .map(Objects::toString) + .toList(); + fileListProperty.update(config, files); + } else if (loadOrNot.test(property)) { + p.get().update(this, pair, err); + } + } + }); + } + /** * Update this configuration based on the given JSON object. * @@ -257,6 +347,14 @@ public String listOfRegisteredProperties() { .collect(Collectors.joining(", ")); } + /** Return the target properties that are have been assigned a value. */ + public List> getAssignedProperties() { + return this.properties.keySet().stream() + .filter(this::isSet) + .sorted(Comparator.comparing(p -> p.getClass().getName())) + .collect(Collectors.toList()); + } + /** Return the target properties that are currently registered. */ public List> getRegisteredProperties() { return this.properties.keySet().stream() @@ -286,33 +384,6 @@ public void load(GeneratorArguments args, MessageReporter err) { args.overrides().forEach(a -> a.update(this, err)); } - /** - * Update the configuration using the given pairs from the AST. - * - * @param pairs AST node that holds all the target properties. - * @param err A message reporter for reporting errors and warnings. - */ - public void load(List pairs, MessageReporter err) { - if (pairs != null) { - pairs.forEach( - pair -> { - var p = forName(pair.getName()); - if (p.isPresent()) { - var property = p.get(); - // Record the pair. - keyValuePairs.put(property, pair); - if (property.checkType(pair, err)) { - // Only update the config is the pair matches the type. - property.update(this, pair, err); - } - } else { - reportUnsupportedTargetProperty( - pair.getName(), err.at(pair, Literals.KEY_VALUE_PAIR__NAME)); - } - }); - } - } - /** * Assign the given value to the given target property. * diff --git a/core/src/main/java/org/lflang/target/property/CmakeIncludeProperty.java b/core/src/main/java/org/lflang/target/property/CmakeIncludeProperty.java index b9a90a6ba9..38dbcacf0b 100644 --- a/core/src/main/java/org/lflang/target/property/CmakeIncludeProperty.java +++ b/core/src/main/java/org/lflang/target/property/CmakeIncludeProperty.java @@ -41,12 +41,7 @@ public String name() { } @Override - public Boolean loadFromImport() { - return true; - } - - @Override - public Boolean loadFromFederate() { + public boolean loadFromFederate() { return true; } } diff --git a/core/src/main/java/org/lflang/target/property/FileListProperty.java b/core/src/main/java/org/lflang/target/property/FileListProperty.java index 3409fcde8f..82dfe4cfc5 100644 --- a/core/src/main/java/org/lflang/target/property/FileListProperty.java +++ b/core/src/main/java/org/lflang/target/property/FileListProperty.java @@ -48,4 +48,9 @@ protected List fromString(String string, MessageReporter reporter) { public Element toAstElement(List value) { return ASTUtils.toElement(value); } + + @Override + public boolean loadFromImport() { + return true; + } } diff --git a/core/src/main/java/org/lflang/target/property/TargetProperty.java b/core/src/main/java/org/lflang/target/property/TargetProperty.java index 13182cb036..131721fc8d 100644 --- a/core/src/main/java/org/lflang/target/property/TargetProperty.java +++ b/core/src/main/java/org/lflang/target/property/TargetProperty.java @@ -104,18 +104,27 @@ public Optional astElementFromConfig(TargetConfig config) { /** Return the name of this target property (in kebab case). */ public abstract String name(); - /** Return the policy of loading from imported file */ - public Boolean loadFromImport() { + /** + * Return {@code true} if this property is to be loaded from imported files, {@code false} + * otherwise. + */ + public boolean loadFromImport() { return false; } - /** Return the policy of loading from imported file */ - public Boolean loadFromFederation() { + /** + * Return {@code true} if this property is to be loaded by federates when specified at the level + * of the federation, {@code false} otherwise. + */ + public boolean loadFromFederation() { return true; } - /** Return the policy of loading from imported file */ - public Boolean loadFromFederate() { + /** + * Return {@code true} if this property is to be loaded from imported federates, {@code false} + * otherwise. + */ + public boolean loadFromFederate() { return false; } diff --git a/core/src/main/java/org/lflang/util/FileUtil.java b/core/src/main/java/org/lflang/util/FileUtil.java index 3bc09469f4..cfd44c432c 100644 --- a/core/src/main/java/org/lflang/util/FileUtil.java +++ b/core/src/main/java/org/lflang/util/FileUtil.java @@ -842,6 +842,12 @@ public static Path findInPackage(Path fileOrDirectory, FileConfig fileConfig) { return null; } + public static Path getRelativePath(Resource source, Resource target) { + return FileUtil.toPath(source.getURI()) + .getParent() + .relativize(FileUtil.toPath(target.getURI()).getParent()); + } + /** Get the iResource corresponding to the provided resource if it can be found. */ public static IResource getIResource(Resource r) { return getIResource(FileUtil.toPath(r).toFile().toURI());