diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index e5d0da81ac..3484dbfe18 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -63,6 +63,7 @@ import org.lflang.TimeValue; import org.lflang.generator.CodeMap; import org.lflang.generator.InvalidSourceException; +import org.lflang.generator.NamedInstance; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.Assignment; @@ -1662,6 +1663,15 @@ public static boolean isInCode(INode node) { && pri.getName().equals("Body"); } + /** + * Return {@code true} if the given instance is top-level, i.e., its parent is {@code null}. + * + * @param instance The instance to check. + */ + public static boolean isTopLevel(NamedInstance instance) { + return instance.getParent() == null; + } + /** Return true if the given node starts on the same line as the given other node. */ public static Predicate sameLine(ICompositeNode compNode) { return other -> { 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 71b809baf6..3b6ec8a2bb 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -441,7 +441,7 @@ private static Set findUpstreamPortsInFederate( Set reactionVisited) { Set toReturn = new HashSet<>(); if (port == null) return toReturn; - else if (federate.contains(port.getParent())) { + else if (ASTUtils.isTopLevel(port.getParent()) || federate.includes(port.getParent())) { // Reached the requested federate toReturn.add(port); visitedPorts.add(port); diff --git a/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java index 2e21534fc4..bde700dbcf 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java @@ -23,7 +23,7 @@ public class FedImportEmitter { String generateImports(FederateInstance federate, FedFileConfig fileConfig) { var imports = ((Model) federate.instantiation.eContainer().eContainer()) - .getImports().stream().filter(federate::contains).toList(); + .getImports().stream().filter(federate::references).toList(); // Transform the URIs imports.stream() @@ -46,7 +46,7 @@ String generateImports(FederateInstance federate, FedFileConfig fileConfig) { var new_import = EcoreUtil.copy(i); new_import .getReactorClasses() - .removeIf(importedReactor -> !federate.contains(importedReactor)); + .removeIf(importedReactor -> !federate.references(importedReactor)); return new_import; }) .map(FormattingUtil.renderer(federate.targetConfig.target)) 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 4d262d1bb4..74c398303b 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -37,23 +37,21 @@ String generateMainReactor( "\n", renderer.apply(federate.instantiation), ASTUtils.allStateVars(originalMainReactor).stream() - .filter(federate::contains) .map(renderer) .collect(Collectors.joining("\n")), ASTUtils.allActions(originalMainReactor).stream() - .filter(federate::contains) + .filter(federate::includes) .map(renderer) .collect(Collectors.joining("\n")), ASTUtils.allTimers(originalMainReactor).stream() - .filter(federate::contains) + .filter(federate::includes) .map(renderer) .collect(Collectors.joining("\n")), ASTUtils.allMethods(originalMainReactor).stream() - .filter(federate::contains) .map(renderer) .collect(Collectors.joining("\n")), ASTUtils.allReactions(originalMainReactor).stream() - .filter(federate::contains) + .filter(federate::includes) .map(renderer) .collect(Collectors.joining("\n"))) .indent(4) @@ -72,7 +70,7 @@ private CharSequence generateMainSignature( FederateInstance federate, Reactor originalMainReactor, Function renderer) { var paramList = ASTUtils.allParameters(originalMainReactor).stream() - .filter(federate::contains) + .filter(federate::references) .map(renderer) .collect(Collectors.joining(",", "(", ")")); // Empty "()" is currently not allowed by the syntax diff --git a/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java index f00d590f12..bb33f6e624 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java @@ -15,7 +15,7 @@ public FedReactorEmitter() {} String generateReactorDefinitions(FederateInstance federate) { return ((Model) federate.instantiation.eContainer().eContainer()) .getReactors().stream() - .filter(federate::contains) + .filter(federate::references) .map(FormattingUtil.renderer(federate.targetConfig.target)) .collect(Collectors.joining("\n")); } 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 22e70a6c73..dfefff5ae0 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -35,7 +35,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.eclipse.emf.ecore.EObject; import org.lflang.MessageReporter; import org.lflang.TargetConfig; import org.lflang.TimeValue; @@ -59,31 +58,27 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.StateVar; import org.lflang.lf.Timer; import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; /** - * Instance of a federate, or marker that no federation has been defined (if isSingleton() returns - * true) FIXME: this comment makes no sense. Every top-level reactor (contained directly by the main - * reactor) is a federate, so there will be one instance of this class for each top-level reactor. + * Class that represents an instance of a federate, i.e., a reactor that is instantiated at the top + * level of a federated reactor. * * @author Edward A. Lee * @author Soroush Bateni */ -public class FederateInstance { // why does this not extend ReactorInstance? +public class FederateInstance { /** - * Construct a new instance with the specified instantiation of of a top-level reactor. The - * federate will be given the specified integer ID. + * Construct a new federate instance on the basis of an instantiation in the federated reactor. * - * @param instantiation The instantiation of a top-level reactor, or null if no federation has - * been defined. - * @param id The federate ID. - * @param bankIndex If instantiation.widthSpec !== null, this gives the bank position. - * @param messageReporter The error reporter + * @param instantiation The AST node of the instantiation. + * @param id An identifier. + * @param bankIndex If {@code instantiation.widthSpec !== null}, this gives the bank position. + * @param messageReporter An object for reporting messages to the user. */ public FederateInstance( Instantiation instantiation, @@ -97,28 +92,20 @@ public FederateInstance( this.messageReporter = messageReporter; this.targetConfig = targetConfig; - if (instantiation != null) { - // If the instantiation is in a bank, then we have to append - // the bank index to the name. - if (instantiation.getWidthSpec() != null) { - this.name = "federate__" + instantiation.getName() + "__" + bankIndex; - } else { - this.name = "federate__" + instantiation.getName(); - } + // If the instantiation is in a bank, then we have to append + // the bank index to the name. + if (instantiation.getWidthSpec() != null) { + this.name = "federate__" + instantiation.getName() + "__" + bankIndex; + } else { + this.name = "federate__" + instantiation.getName(); } } - ///////////////////////////////////////////// - //// Public Fields - /** * The position within a bank of reactors for this federate. This is 0 if the instantiation is not * a bank of reactors. */ - public int bankIndex = 0; - - /** A list of outputs that can be triggered directly or indirectly by physical actions. */ - public Set outputsConnectedToPhysicalActions = new LinkedHashSet<>(); + public int bankIndex; /** The host, if specified using the 'at' keyword. */ public String host = "localhost"; @@ -140,16 +127,13 @@ public Instantiation getInstantiation() { */ public Map> dependsOn = new LinkedHashMap<>(); - /** The directory, if specified using the 'at' keyword. */ - public String dir = null; - /** The port, if specified using the 'at' keyword. */ public int port = 0; /** - * Map from the federates that this federate sends messages to to the delays on connections to - * that federate. The delay set may may include null, meaning that there is a connection from the - * federate instance that has no delay. + * Map from the federates that this federate sends messages to the delays on connections to that + * federate. The delay set may include null, meaning that there is a connection from the federate + * instance that has no delay. */ public Map> sendsTo = new LinkedHashMap<>(); @@ -157,14 +141,14 @@ public Instantiation getInstantiation() { public String user = null; /** The integer ID of this federate. */ - public int id = 0; + public int id; /** * The name of this federate instance. This will be the instantiation name, possibly appended with * "__n", where n is the bank position of this instance if the instantiation is of a bank of * reactors. */ - public String name = "Unnamed"; + public String name; /** * List of networkMessage actions. Each of these handles a message received from another federate @@ -220,63 +204,74 @@ public Instantiation getInstantiation() { /** Keep a unique list of enabled serializers */ public HashSet enabledSerializers = new HashSet<>(); + /** Cached result of analysis of which reactions to exclude from main. */ + private Set excludeReactions = null; + + /** An error reporter */ + private final MessageReporter messageReporter; + /** - * Return true if the specified EObject should be included in the code generated for this - * federate. + * Return {@code true} if the class declaration of the given {@code instantiation} references the + * {@code reactor}, either directly or indirectly (e.g., via a superclass or a type parameter). * - * @param object An {@code EObject} - * @return True if this federate contains the EObject. + * @param declaration The reactor declaration to check if it is referenced. */ - public boolean contains(EObject object) { - if (object instanceof Action) { - return contains((Action) object); - } else if (object instanceof Reaction) { - return contains((Reaction) object); - } else if (object instanceof Timer) { - return contains((Timer) object); - } else if (object instanceof ReactorDecl) { - return contains(this.instantiation, (ReactorDecl) object); - } else if (object instanceof Import) { - return contains((Import) object); - } else if (object instanceof Parameter) { - return contains((Parameter) object); - } else if (object instanceof StateVar) { - return true; // FIXME: Should we disallow state vars at the top level? - } - throw new UnsupportedOperationException( - "EObject class " + object.eClass().getName() + " not supported."); + public boolean references(ReactorDecl declaration) { + return references(this.instantiation, declaration); } /** - * Return true if the specified reactor belongs to this federate. + * Return {@code true} if the class declaration of the given {@code instantiation} references the + * {@code reactor}, either directly or indirectly (e.g., via a superclass or a type parameter). * - * @param instantiation The instantiation to look inside - * @param reactor The reactor declaration to find + * @param instantiation The instantiation the class of which may refer to the reactor declaration. + * @param declaration The reactor declaration to check if it is referenced. */ - private boolean contains(Instantiation instantiation, ReactorDecl reactor) { - if (instantiation.getReactorClass().equals(ASTUtils.toDefinition(reactor))) { + private boolean references(Instantiation instantiation, ReactorDecl declaration) { + if (instantiation.getReactorClass().equals(ASTUtils.toDefinition(declaration))) { return true; } - boolean instantiationsCheck = false; - // For a federate, we don't need to look inside imported reactors. if (instantiation.getReactorClass() instanceof Reactor reactorDef) { + // Check if the reactor is instantiated for (Instantiation child : reactorDef.getInstantiations()) { - instantiationsCheck |= contains(child, reactor); + if (references(child, declaration)) { + return true; + } + } + // Check if the reactor is a super class + for (var parent : reactorDef.getSuperClasses()) { + if (declaration instanceof Reactor r) { + if (r.equals(parent)) { + return true; + } + // Check if there are instantiations of the reactor in a super class + if (parent instanceof Reactor p) { + for (var inst : p.getInstantiations()) { + if (references(inst, declaration)) { + return true; + } + } + } + } + if (declaration instanceof ImportedReactor i) { + if (i.getReactorClass().equals(parent)) { + return true; + } + } } } - - return instantiationsCheck; + return false; } /** - * Return true if the specified import should be included in the code generated for this federate. + * Return {@code true} if this federate references the given import. * - * @param imp The import + * @param imp The import to check if it is referenced. */ - private boolean contains(Import imp) { + public boolean references(Import imp) { for (ImportedReactor reactor : imp.getReactorClasses()) { - if (contains(reactor)) { + if (this.references(reactor)) { return true; } } @@ -284,15 +279,13 @@ private boolean contains(Import imp) { } /** - * Return true if the specified parameter should be included in the code generated for this - * federate. + * Return {@code true} if this federate references the given parameter. * - * @param param The parameter + * @param param The parameter to check if it is referenced. */ - private boolean contains(Parameter param) { - boolean returnValue = false; + public boolean references(Parameter param) { // Check if param is referenced in this federate's instantiation - returnValue = + var returnValue = instantiation.getParameters().stream() .anyMatch( assignment -> @@ -306,27 +299,28 @@ private boolean contains(Parameter param) { var topLevelUserDefinedReactions = ((Reactor) instantiation.eContainer()) .getReactions().stream() - .filter(r -> !networkReactions.contains(r) && contains(r)) + .filter(r -> !networkReactions.contains(r) && includes(r)) .collect(Collectors.toCollection(ArrayList::new)); returnValue |= !topLevelUserDefinedReactions.isEmpty(); return returnValue; } /** - * Return true if the specified action should be included in the code generated for this federate. - * This means that either the action is used as a trigger, a source, or an effect in a top-level - * reaction that belongs to this federate. This returns true if the program is not federated. + * Return {@code true} if this federate includes the given action from the top-level of the + * federation, which is necessary when the federate includes a reaction that references the given + * action. + * + *

Specifically, this means that either the action is used as a trigger, a source, or an effect + * in a top-level reaction that is included by this federate. * - * @param action The action - * @return True if this federate contains the action. + * @param action The action to check if it is to be included. */ - private boolean contains(Action action) { + public boolean includes(Action action) { Reactor reactor = ASTUtils.getEnclosingReactor(action); - // If the action is used as a trigger, a source, or an effect for a top-level reaction // that belongs to this federate, then generate it. for (Reaction react : ASTUtils.allReactions(reactor)) { - if (contains(react)) { + if (includes(react)) { // Look in triggers for (TriggerRef trigger : convertToEmptyListIfNull(react.getTriggers())) { if (trigger instanceof VarRef triggerAsVarRef) { @@ -354,7 +348,7 @@ private boolean contains(Action action) { } /** - * Return true if the specified reaction should be included in the code generated for this + * Return {@code true} if the specified reaction should be included in the code generated for this * federate at the top-level. This means that if the reaction is triggered by or sends data to a * port of a contained reactor, then that reaction is in the federate. Otherwise, return false. * @@ -362,9 +356,9 @@ private boolean contains(Action action) { * other federates. It should only be called on reactions that are either at the top level or * within this federate. For this reason, for any reaction not at the top level, it returns true. * - * @param reaction The reaction. + * @param reaction The reaction to check if it is to be included. */ - private boolean contains(Reaction reaction) { + public boolean includes(Reaction reaction) { Reactor reactor = ASTUtils.getEnclosingReactor(reaction); assert reactor != null; @@ -392,19 +386,19 @@ private boolean contains(Reaction reaction) { } /** - * Return true if the specified timer should be included in the code generated for the federate. - * This means that the timer is used as a trigger in a top-level reaction that belongs to this - * federate. This also returns true if the program is not federated. + * Return {@code true} if this federate includes the given timer from the top-level of the + * federation, which is necessary when the federate includes a reaction that uses the given timer. * - * @return True if this federate contains the action in the specified reactor + *

Specifically, this means that either the timer is used as a trigger in a top-level reaction + * that is included by this federate. + * + * @param timer The action to check if it is to be included. */ - private boolean contains(Timer timer) { + public boolean includes(Timer timer) { Reactor reactor = ASTUtils.getEnclosingReactor(timer); - // If the action is used as a trigger, a source, or an effect for a top-level reaction - // that belongs to this federate, then generate it. for (Reaction r : ASTUtils.allReactions(reactor)) { - if (contains(r)) { + if (includes(r)) { // Look in triggers for (TriggerRef trigger : convertToEmptyListIfNull(r.getTriggers())) { if (trigger instanceof VarRef triggerAsVarRef) { @@ -415,26 +409,18 @@ private boolean contains(Timer timer) { } } } - return false; } /** - * Return true if the specified reactor instance or any parent reactor instance is contained by - * this federate. If the specified instance is the top-level reactor, return true (the top-level - * reactor belongs to all federates). If this federate instance is a singleton, then return true - * if the instance is non null. + * Return {@code true} if this federate instance includes the given instance. * - *

NOTE: If the instance is bank within the top level, then this returns true even though only - * one of the bank members is in the federate. + *

NOTE: If the instance is bank within the top level, then this returns {@code true} even + * though only one of the bank members is included in the federate. * - * @param instance The reactor instance. - * @return True if this federate contains the reactor instance + * @param instance The reactor instance to check if it is to be included. */ - public boolean contains(ReactorInstance instance) { - if (instance.getParent() == null) { - return true; // Top-level reactor - } + public boolean includes(ReactorInstance instance) { // Start with this instance, then check its parents. ReactorInstance i = instance; while (i != null) { @@ -453,7 +439,7 @@ public boolean contains(ReactorInstance instance) { * @param federatedReactor The top-level federated reactor */ private void indexExcludedTopLevelReactions(Reactor federatedReactor) { - boolean inFederate = false; + boolean inFederate; if (excludeReactions != null) { throw new IllegalStateException( "The index for excluded reactions at the top level is already built."); @@ -542,17 +528,6 @@ public LinkedHashMap findOutputsConnectedToPhysicalActions( return physicalActionToOutputMinDelay; } - /** - * Return a list of federates that are upstream of this federate and have a zero-delay (direct) - * connection to this federate. - */ - public List getZeroDelayImmediateUpstreamFederates() { - return this.dependsOn.entrySet().stream() - .filter(e -> e.getValue().contains(null)) - .map(Map.Entry::getKey) - .toList(); - } - @Override public String toString() { return "Federate " @@ -561,15 +536,6 @@ public String toString() { + ((instantiation != null) ? instantiation.getName() : "no name"); } - ///////////////////////////////////////////// - //// Private Fields - - /** Cached result of analysis of which reactions to exclude from main. */ - private Set excludeReactions = null; - - /** An error reporter */ - private final MessageReporter messageReporter; - /** * Find the nearest (shortest) path to a physical action trigger from this 'reaction' in terms of * minimum delay. diff --git a/test/C/src/federated/InheritanceFederated.lf b/test/C/src/federated/InheritanceFederated.lf new file mode 100644 index 0000000000..70f429dbc8 --- /dev/null +++ b/test/C/src/federated/InheritanceFederated.lf @@ -0,0 +1,21 @@ +// Test for inheritance in a federated program. +// Compilation without errors is success. +// Based on https://github.com/lf-lang/lingua-franca/issues/1733. +target C { + timeout: 1 ms +} + +reactor A { + reaction(startup) {= printf("Hello\n"); =} +} + +reactor B { + a = new A() +} + +reactor C extends B { +} + +federated reactor { + c = new C() +} diff --git a/test/C/src/federated/InheritanceFederatedImport.lf b/test/C/src/federated/InheritanceFederatedImport.lf new file mode 100644 index 0000000000..0918e6809b --- /dev/null +++ b/test/C/src/federated/InheritanceFederatedImport.lf @@ -0,0 +1,15 @@ +// Test for inheritance in a federated program where the superclass is imported from a different file. +// Compilation without errors is success. +target C { + timeout: 1 ms +} + +import HelloWorld2 from "../HelloWorld.lf" + +reactor Print extends HelloWorld2 { + reaction(startup) {= printf("Foo\n"); =} +} + +federated reactor { + print = new Print() +}