diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java index 593e302d25..eede6a6831 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java @@ -94,7 +94,7 @@ public void testFederation() throws Exception { generator.doGenerate(resource, fileAccess, context); String lfSrc = Files.readAllLines( - ((FedFileConfig)context.getFileConfig()).getSrcPath().resolve("a.lf") + ((FedFileConfig)context.getFileConfig()).getSrcPath().resolve("federate__a.lf") ).stream().reduce("\n", String::concat); Model federate = parser.parse(lfSrc); assertHasTargetProperty(federate, "tracing"); diff --git a/org.lflang/src/org/lflang/ModelInfo.java b/org.lflang/src/org/lflang/ModelInfo.java index 45e9995045..fbe5c2047c 100644 --- a/org.lflang/src/org/lflang/ModelInfo.java +++ b/org.lflang/src/org/lflang/ModelInfo.java @@ -11,15 +11,15 @@ 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 +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 +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. ***************/ @@ -28,6 +28,8 @@ import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -46,6 +48,7 @@ import org.lflang.lf.ParameterReference; import org.lflang.lf.Reactor; import org.lflang.lf.STP; +import org.lflang.util.FileUtil; /** @@ -81,7 +84,7 @@ public class ModelInfo { * interval. */ public Set overflowingDeadlines; - + /** * The set of STP offsets that use a too-large constant to specify their time * interval. @@ -137,6 +140,27 @@ public void update(Model model, ErrorReporter reporter) { if (target == Target.C) { this.collectOverflowingNodes(); } + + checkCaseInsensitiveNameCollisions(model, reporter); + } + + public void checkCaseInsensitiveNameCollisions(Model model, ErrorReporter reporter) { + var reactorNames = new HashSet<>(); + var bad = new ArrayList<>(); + for (var reactor : model.getReactors()) { + var lowerName = getName(reactor).toLowerCase(); + if (reactorNames.contains(lowerName)) bad.add(lowerName); + reactorNames.add(lowerName); + } + for (var badName : bad) { + model.getReactors().stream() + .filter(it -> getName(it).toLowerCase().equals(badName)) + .forEach(it -> reporter.reportError(it, "Multiple reactors have the same name up to case differences.")); + } + } + + private String getName(Reactor r) { + return r.getName() != null ? r.getName() : FileUtil.nameWithoutExtension(Path.of(model.eResource().getURI().toFileString())); } public Set> topologyCycles() { @@ -223,7 +247,7 @@ private boolean detectOverflow(Set visited, Parameter current) { // Check for overflow in the referenced parameter. overflow = detectOverflow(visited, ((ParameterReference)expr).getParameter()) || overflow; } else { - // The right-hand side of the assignment is a + // The right-hand side of the assignment is a // constant; check whether it is too large. if (isTooLarge(ASTUtils.getLiteralTimeValue(expr))) { this.overflowingAssignments.add(assignment); diff --git a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java index 9ae5c9d2cc..2c9f32aad8 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java @@ -51,9 +51,6 @@ Map generateFederate( + " in directory " + fileConfig.getSrcPath()); - Path lfFilePath = fileConfig.getSrcPath().resolve( - fedName + ".lf"); - String federateCode = String.join( "\n", new FedTargetEmitter().generateTarget(context, numOfFederates, federate, fileConfig, errorReporter, rtiConfig), @@ -67,6 +64,7 @@ Map generateFederate( ) ); Map codeMapMap = new HashMap<>(); + var lfFilePath = lfFilePath(fileConfig, federate); try (var srcWriter = Files.newBufferedWriter(lfFilePath)) { var codeMap = CodeMap.fromGeneratedCode(federateCode); codeMapMap.put(lfFilePath, codeMap); @@ -74,4 +72,8 @@ Map generateFederate( } return codeMapMap; } + + public static Path lfFilePath(FedFileConfig fileConfig, FederateInstance federate) { + return fileConfig.getSrcPath().resolve(federate.name + ".lf"); + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java index cd9c2e95d0..dddae562f7 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java +++ b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java @@ -286,7 +286,7 @@ private Map compileFederates( final int id = i; compileThreadPool.execute(() -> { Resource res = rs.getResource(URI.createFileURI( - fileConfig.getSrcPath().resolve(fed.name + ".lf").toAbsolutePath().toString() + FedEmitter.lfFilePath(fileConfig, fed).toAbsolutePath().toString() ), true); FileConfig subFileConfig = LFGenerator.createFileConfig(res, fileConfig.getSrcGenPath(), true); ErrorReporter subContextErrorReporter = new LineAdjustingErrorReporter(threadSafeErrorReporter, lf2lfCodeMapMap); diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index 7ca5a19bca..cdd64f03b2 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -25,7 +25,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.federated.generator; -import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -40,17 +39,13 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.ASTUtils; import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TimeValue; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.ActionInstance; -import org.lflang.generator.GeneratorUtils; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; -import org.lflang.generator.SubContext; import org.lflang.generator.TriggerInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; @@ -97,8 +92,8 @@ public class FederateInstance { // why does this not extend ReactorInstance? * @param errorReporter The error reporter */ public FederateInstance( - Instantiation instantiation, - int id, + Instantiation instantiation, + int id, int bankIndex, TargetConfig targetConfig, ErrorReporter errorReporter) { @@ -107,31 +102,32 @@ public FederateInstance( this.bankIndex = bankIndex; this.errorReporter = errorReporter; this.targetConfig = targetConfig; - + if (instantiation != null) { - this.name = 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 = instantiation.getName() + "__" + bankIndex; + 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<>(); - + /** * The host, if specified using the 'at' keyword. */ @@ -150,7 +146,7 @@ public Instantiation getInstantiation() { * A list of individual connections between federates */ public Set connections = new HashSet<>(); - + /** * Map from the federates that this federate receives messages from * to the delays on connections from that federate. The delay set @@ -158,17 +154,17 @@ public Instantiation getInstantiation() { * from the federate instance that has no delay. */ 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 @@ -176,12 +172,12 @@ public Instantiation getInstantiation() { * from the federate instance that has no delay. */ public Map> sendsTo = new LinkedHashMap<>(); - + /** * The user, if specified using the 'at' keyword. */ public String user = null; - + /** * The integer ID of this federate. */ @@ -194,7 +190,7 @@ public Instantiation getInstantiation() { * this instance if the instantiation is of a bank of reactors. */ public String name = "Unnamed"; - + /** * List of networkMessage actions. Each of these handles a message * received from another federate over the network. The ID of @@ -202,7 +198,7 @@ public Instantiation getInstantiation() { * The sending federate needs to specify this ID. */ public List networkMessageActions = new ArrayList<>(); - + /** * A set of federates with which this federate has an inbound connection * There will only be one physical connection even if federate A has defined multiple @@ -211,7 +207,7 @@ public Instantiation getInstantiation() { * to help the receiver distinguish different events. */ public Set inboundP2PConnections = new LinkedHashSet<>(); - + /** * A list of federate with which this federate has an outbound physical connection. * There will only be one physical connection even if federate A has defined multiple @@ -220,29 +216,29 @@ public Instantiation getInstantiation() { * scheduling the appropriate action. */ public Set outboundP2PConnections = new LinkedHashSet<>(); - + /** * A list of triggers for network input control reactions. This is used to trigger * all the input network control reactions that might be nested in a hierarchy. */ public List networkInputControlReactionsTriggers = new ArrayList<>(); - + /** * The trigger that triggers the output control reaction of this * federate. - * + * * The network output control reactions send a PORT_ABSENT message for a network output port, * if it is absent at the current tag, to notify all downstream federates that no value will * be present on the given network port, allowing input control reactions on those federates * to stop blocking. */ public Variable networkOutputControlReactionsTrigger = null; - + /** * Indicates whether the federate is remote or local */ public boolean isRemote = false; - + /** * List of generated network reactions (network receivers, * network input control reactions, network senders, and network output control @@ -389,16 +385,16 @@ private boolean contains(Action action) { } } } - - return false; + + return false; } - /** + /** * Return 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. - * + * * NOTE: This method assumes that it will not be called with reaction arguments * that are within 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 @@ -411,7 +407,7 @@ private boolean contains(Reaction reaction) { assert reactor != null; if (!reactor.getReactions().contains(reaction)) return false; - + if (networkReactions.contains(reaction)) { // Reaction is a network reaction that belongs to this federate return true; @@ -421,15 +417,15 @@ private boolean contains(Reaction reaction) { if (reactionBankIndex >= 0 && this.bankIndex >= 0 && reactionBankIndex != this.bankIndex) { return false; } - + // If this has been called before, then the result of the // following check is cached. if (excludeReactions != null) { return !excludeReactions.contains(reaction); } - + indexExcludedTopLevelReactions(reactor); - + return !excludeReactions.contains(reaction); } @@ -462,17 +458,17 @@ 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. - * + * * 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. - * + * * @param instance The reactor instance. * @return True if this federate contains the reactor instance */ @@ -496,7 +492,7 @@ public boolean contains(ReactorInstance instance) { * federatedReactor) that don't belong to this federate * instance. This index is put in the excludeReactions * class variable. - * + * * @param federatedReactor The top-level federated reactor */ private void indexExcludedTopLevelReactions(Reactor federatedReactor) { @@ -537,7 +533,7 @@ private void indexExcludedTopLevelReactions(Reactor federatedReactor) { } } } - + /** * Return true if all members of 'varRefs' belong to this federate. * @@ -564,7 +560,7 @@ private boolean containsAllVarRefs(Iterable varRefs) { } return inFederate; } - + /** * Find output ports that are connected to a physical action trigger upstream * in the same reactor. Return a list of such outputs paired with the minimum delay @@ -596,7 +592,7 @@ public List getZeroDelayImmediateUpstreamFederates() { .filter(e -> e.getValue().contains(null)) .map(Map.Entry::getKey).toList(); } - + @Override public String toString() { return "Federate " + id + ": " @@ -605,21 +601,21 @@ public String toString() { ///////////////////////////////////////////// //// Private Fields - + /** * Cached result of analysis of which reactions to exclude from main. */ private Set excludeReactions = null; - + /** * An error reporter */ private final ErrorReporter errorReporter; - + /** * Find the nearest (shortest) path to a physical action trigger from this * 'reaction' in terms of minimum delay. - * + * * @param reaction The reaction to start with * @return The minimum delay found to the nearest physical action and * TimeValue.MAX_VALUE otherwise @@ -646,7 +642,7 @@ public TimeValue findNearestPhysicalActionTrigger(ReactionInstance reaction) { } } } - + } else if (trigger.getDefinition() instanceof Output) { // Outputs of contained reactions PortInstance outputInstance = (PortInstance) trigger; @@ -660,7 +656,7 @@ public TimeValue findNearestPhysicalActionTrigger(ReactionInstance reaction) { } return minDelay; } - + // TODO: Put this function into a utils file instead private List convertToEmptyListIfNull(List list) { return list == null ? new ArrayList<>() : list; diff --git a/org.lflang/src/org/lflang/generator/DockerGenerator.java b/org.lflang/src/org/lflang/generator/DockerGenerator.java index 902d664631..851ee6c0be 100644 --- a/org.lflang/src/org/lflang/generator/DockerGenerator.java +++ b/org.lflang/src/org/lflang/generator/DockerGenerator.java @@ -43,7 +43,7 @@ public DockerData generateDockerData() { var dockerFilePath = context.getFileConfig().getSrcGenPath().resolve("Dockerfile"); var dockerFileContent = generateDockerFileContent(); - return new DockerData(name, dockerFilePath, dockerFileContent); + return new DockerData(name.replace("_", ""), dockerFilePath, dockerFileContent); } public static DockerGenerator dockerGeneratorFactory(LFGeneratorContext context) { diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index eb5163ade6..20f9718aea 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -323,7 +323,8 @@ public String getName() { @Override public String uniqueID() { if (this.isMainOrFederated()) { - return super.uniqueID() + "_main"; + if (reactorDefinition.isFederated() && !super.uniqueID().startsWith("federate__")) return "federate__" + super.uniqueID() + "_main"; + return super.uniqueID() + "_main"; } return super.uniqueID(); }