diff --git a/.gitmodules b/.gitmodules index 3ccd9f3a7f..274bffab2c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ url = https://github.com/lf-lang/reactor-c-py.git [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = org.lflang/src/lib/cpp/reactor-cpp - url = https://github.com/lf-lang/reactor-cpp + url = https://github.com/lf-lang/reactor-cpp.git [submodule "org.lflang/src/lib/rs/reactor-rs"] path = org.lflang/src/lib/rs/reactor-rs - url = https://github.com/lf-lang/reactor-rs + url = https://github.com/lf-lang/reactor-rs.git diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 42a87b1b9c..7dcdb7dfdb 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 42a87b1b9cb678ab9170dbf800019bcbeb80c61f +Subproject commit 7dcdb7dfdbc065c59fe1372779d5e0606dccda30 diff --git a/org.lflang/src/lib/py/reactor-c-py b/org.lflang/src/lib/py/reactor-c-py index 9f82617589..14146b2f7b 160000 --- a/org.lflang/src/lib/py/reactor-c-py +++ b/org.lflang/src/lib/py/reactor-c-py @@ -1 +1 @@ -Subproject commit 9f82617589994ce5f71c88a091b32f5a4ac431f0 +Subproject commit 14146b2f7be6db8261b6a45724e18e0693f1f822 diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index afda11bb46..4606b4d35f 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -1,16 +1,12 @@ /* Copyright (c) 2020, The University of California at Berkeley. - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, 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 @@ -25,6 +21,9 @@ package org.lflang; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -32,7 +31,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Matcher; @@ -40,7 +38,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; - import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; @@ -61,8 +58,6 @@ import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.Assignment; -import org.lflang.lf.AttrParm; -import org.lflang.lf.Attribute; import org.lflang.lf.Code; import org.lflang.lf.Connection; import org.lflang.lf.Element; @@ -91,1734 +86,1667 @@ import org.lflang.lf.Type; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.util.StringUtil; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; - /** * A helper class for modifying and analyzing the AST. + * * @author Marten Lohstroh * @author Edward A. Lee * @author Christian Menard */ public class ASTUtils { - /** - * The Lingua Franca factory for creating new AST nodes. - */ - public static final LfFactory factory = LfFactory.eINSTANCE; - - /** - * The Lingua Franca feature package. - */ - public static final LfPackage featurePackage = LfPackage.eINSTANCE; - - /* Match an abbreviated form of a float literal. */ - private static final Pattern ABBREVIATED_FLOAT = Pattern.compile("[+\\-]?\\.\\d+[\\deE+\\-]*"); - - /** - * A mapping from Reactor features to corresponding Mode features for collecting contained elements. - */ - private static final Map reactorModeFeatureMap = Map.of( - featurePackage.getReactor_Actions(), featurePackage.getMode_Actions(), - featurePackage.getReactor_Connections(), featurePackage.getMode_Connections(), - featurePackage.getReactor_Instantiations(), featurePackage.getMode_Instantiations(), - featurePackage.getReactor_Reactions(), featurePackage.getMode_Reactions(), - featurePackage.getReactor_StateVars(), featurePackage.getMode_StateVars(), - featurePackage.getReactor_Timers(), featurePackage.getMode_Timers() - ); - - - /** - * Get all reactors defined in the given resource. - * @param resource the resource to extract reactors from - * @return An iterable over all reactors found in the resource - */ - public static List getAllReactors(Resource resource) { - return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) - .filter(Reactor.class::isInstance) - .map(Reactor.class::cast) - .collect(Collectors.toList()); - } - - /** - * Find connections in the given resource that would be conflicting writes if they were not located in mutually - * exclusive modes. - * - * @param resource The AST. - * @return a list of connections being able to be transformed - */ - public static Collection findConflictingConnectionsInModalReactors(Resource resource) { - var transform = new HashSet(); - - for (Reactor reactor : getAllReactors(resource)) { - if (!reactor.getModes().isEmpty()) { // Only for modal reactors - var allWriters = HashMultimap., EObject>create(); - - // Collect destinations - for (var rea : allReactions(reactor)) { - for (var eff : rea.getEffects()) { - if (eff.getVariable() instanceof Port) { - allWriters.put(Tuples.pair(eff.getContainer(), eff.getVariable()), rea); - } - } - } - for (var con : ASTUtils.collectElements(reactor, featurePackage.getReactor_Connections(), false, true)) { - for (var port : con.getRightPorts()) { - allWriters.put(Tuples.pair(port.getContainer(), port.getVariable()), con); - } - } - - // Handle conflicting writers - for (var key : allWriters.keySet()) { - var writers = allWriters.get(key); - if (writers.size() > 1) { // has multiple sources - var writerModes = HashMultimap.create(); - // find modes - for (var writer : writers) { - if (writer.eContainer() instanceof Mode) { - writerModes.put((Mode) writer.eContainer(), writer); - } else { - writerModes.put(null, writer); - } - } - // Conflicting connection can only be handled if.. - if (!writerModes.containsKey(null) && // no writer is on root level (outside of modes) and... - writerModes.keySet().stream().map(writerModes::get).allMatch(writersInMode -> // all writers in a mode are either... - writersInMode.size() == 1 || // the only writer or... - writersInMode.stream().allMatch(w -> w instanceof Reaction) // all are reactions and hence ordered - )) { - // Add connections to transform list - writers.stream().filter(w -> w instanceof Connection).forEach(c -> transform.add((Connection) c)); - } - } - } - } - } - - return transform; - } - - /** - * Return the enclosing reactor of an LF EObject in a reactor or mode. - * @param obj the LF model element - * @return the reactor or null - */ - public static Reactor getEnclosingReactor(EObject obj) { - if (obj.eContainer() instanceof Reactor) { - return (Reactor) obj.eContainer(); - } else if (obj.eContainer() instanceof Mode) { - return (Reactor) obj.eContainer().eContainer(); - } - return null; - } - - /** - * Return the main reactor in the given resource if there is one, null otherwise. - */ - public static Reactor findMainReactor(Resource resource) { - return IteratorExtensions.findFirst( - Iterators.filter(resource.getAllContents(), Reactor.class), - Reactor::isMain - ); - } - - /** - * Find the main reactor and change it to a federated reactor. - * Return true if the transformation was successful (or the given resource - * already had a federated reactor); return false otherwise. - */ - public static boolean makeFederated(Resource resource) { - // Find the main reactor - Reactor r = findMainReactor(resource); - if (r == null) { - return false; - } - r.setMain(false); - r.setFederated(true); - return true; - } - - /** - * Change the target name to 'newTargetName'. - * For example, change C to CCpp. - */ - public static boolean changeTargetName(Resource resource, String newTargetName) { - targetDecl(resource).setName(newTargetName); - return true; - } - - /** - * Return the target of the file in which the given node lives. - */ - public static Target getTarget(EObject object) { - TargetDecl targetDecl = targetDecl(object.eResource()); - return Target.fromDecl(targetDecl); - } - - /** - * Add a new target property to the given resource. - * - * This also creates a config object if the resource does not yey have one. - * - * @param resource The resource to modify - * @param name Name of the property to add - * @param value Value to be assigned to the property - */ - public static boolean addTargetProperty(final Resource resource, final String name, final Element value) { - var config = targetDecl(resource).getConfig(); - if (config == null) { - config = LfFactory.eINSTANCE.createKeyValuePairs(); - targetDecl(resource).setConfig(config); - } - final var newProperty = LfFactory.eINSTANCE.createKeyValuePair(); - newProperty.setName(name); - newProperty.setValue(value); - config.getPairs().add(newProperty); - return true; - } - - /** - * Return true if the connection involves multiple ports on the left or right side of the connection, or - * if the port on the left or right of the connection involves a bank of reactors or a multiport. - * @param connection The connection. - */ - public static boolean hasMultipleConnections(Connection connection) { - if (connection.getLeftPorts().size() > 1 || connection.getRightPorts().size() > 1) { - return true; - } - VarRef leftPort = connection.getLeftPorts().get(0); - VarRef rightPort = connection.getRightPorts().get(0); - Instantiation leftContainer = leftPort.getContainer(); - Instantiation rightContainer = rightPort.getContainer(); - Port leftPortAsPort = (Port) leftPort.getVariable(); - Port rightPortAsPort = (Port) rightPort.getVariable(); - return leftPortAsPort.getWidthSpec() != null - || leftContainer != null && leftContainer.getWidthSpec() != null - || rightPortAsPort.getWidthSpec() != null - || rightContainer != null && rightContainer.getWidthSpec() != null; - } - - /** - * Produce a unique identifier within a reactor based on a - * given based name. If the name does not exists, it is returned; - * if does exist, an index is appended that makes the name unique. - * @param reactor The reactor to find a unique identifier within. - * @param name The name to base the returned identifier on. - */ - public static String getUniqueIdentifier(Reactor reactor, String name) { - LinkedHashSet vars = new LinkedHashSet<>(); - allActions(reactor).forEach(it -> vars.add(it.getName())); - allTimers(reactor).forEach(it -> vars.add(it.getName())); - allParameters(reactor).forEach(it -> vars.add(it.getName())); - allInputs(reactor).forEach(it -> vars.add(it.getName())); - allOutputs(reactor).forEach(it -> vars.add(it.getName())); - allStateVars(reactor).forEach(it -> vars.add(it.getName())); - allInstantiations(reactor).forEach(it -> vars.add(it.getName())); - - int index = 0; - String suffix = ""; - boolean exists = true; - while (exists) { - String id = name + suffix; - if (IterableExtensions.exists(vars, it -> it.equals(id))) { - suffix = "_" + index; - index++; - } else { - exists = false; - } - } - return name + suffix; - } - - //////////////////////////////// - //// Utility functions for supporting inheritance and modes - - /** - * Given a reactor class, return a list of all its actions, - * which includes actions of base classes that it extends. - * This also includes actions in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allActions(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Actions()); - } - - /** - * Given a reactor class, return a list of all its connections, - * which includes connections of base classes that it extends. - * This also includes connections in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allConnections(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Connections()); - } - - /** - * Given a reactor class, return a list of all its inputs, - * which includes inputs of base classes that it extends. - * If the base classes include a cycle, where X extends Y and Y extends X, - * then return only the input defined in the base class. - * The returned list may be empty. - * @param definition Reactor class definition. - */ - public static List allInputs(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Inputs()); - } - - /** A list of all ports of {@code definition}, in an unspecified order. */ - public static List allPorts(Reactor definition) { - return Stream.concat(ASTUtils.allInputs(definition).stream(), ASTUtils.allOutputs(definition).stream()).toList(); - } - - /** - * Given a reactor class, return a list of all its instantiations, - * which includes instantiations of base classes that it extends. - * This also includes instantiations in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allInstantiations(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Instantiations()); - } - - public static Stream allNestedClasses(Reactor definition) { - return new HashSet<>(ASTUtils.allInstantiations(definition)).stream() - .map(Instantiation::getReactorClass) - .map(ASTUtils::toDefinition); - } - - /** - * Given a reactor class, return a list of all its methods, - * which includes methods of base classes that it extends. - * @param definition Reactor class definition. - */ - public static List allMethods(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Methods()); - } - - /** - * Given a reactor class, return a list of all its outputs, - * which includes outputs of base classes that it extends. - * @param definition Reactor class definition. - */ - public static List allOutputs(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Outputs()); - } - - /** - * Given a reactor class, return a list of all its parameters, - * which includes parameters of base classes that it extends. - * @param definition Reactor class definition. - */ - public static List allParameters(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Parameters()); - } - - /** - * Given a reactor class, return a list of all its reactions, - * which includes reactions of base classes that it extends. - * This also includes reactions in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allReactions(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Reactions()); - } - - /** - * Given a reactor class, return a list of all its state variables, - * which includes state variables of base classes that it extends. - * This also includes reactions in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allStateVars(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_StateVars()); - } - - /** - * Given a reactor class, return a list of all its timers, - * which includes timers of base classes that it extends. - * This also includes reactions in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allTimers(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Timers()); - } - - /** - * Given a reactor class, returns a list of all its modes, - * which includes modes of base classes that it extends. - * @param definition Reactor class definition. - */ - public static List allModes(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Modes()); - } - - /** A list of all reactors instantiated, transitively or intransitively, by {@code r}. */ - public static List recursiveChildren(ReactorInstance r) { - List ret = new ArrayList<>(); - ret.add(r.reactorDefinition); - for (var child: r.children) { - ret.addAll(recursiveChildren(child)); - } - return ret; - } - - /** - * Return all the superclasses of the specified reactor - * in deepest-first order. For example, if A extends B and C, and - * B and C both extend D, this will return the list [D, B, C, A]. - * Duplicates are removed. If the specified reactor does not extend - * any other reactor, then return an empty list. - * If a cycle is found, where X extends Y and Y extends X, or if - * a superclass is declared that is not found, then return null. - * @param reactor The specified reactor. - */ - public static LinkedHashSet superClasses(Reactor reactor) { - return superClasses(reactor, new LinkedHashSet<>()); - } - - /** - * Collect elements of type T from the class hierarchy and modes - * defined by a given reactor definition. - * @param definition The reactor definition. - * @param The type of elements to collect (e.g., Port, Timer, etc.) - * @return A list of all elements of type T found - */ - public static List collectElements(Reactor definition, EStructuralFeature feature) { - return ASTUtils.collectElements(definition, feature, true, true); - } - - /** - * Collect elements of type T contained in given reactor definition, including - * modes and the class hierarchy defined depending on configuration. - * @param definition The reactor definition. - * @param feature The structual model elements to collect. - * @param includeSuperClasses Whether to include elements in super classes. - * @param includeModes Whether to include elements in modes. - * @param The type of elements to collect (e.g., Port, Timer, etc.) - * @return A list of all elements of type T found - */ - @SuppressWarnings("unchecked") - public static List collectElements(Reactor definition, EStructuralFeature feature, boolean includeSuperClasses, boolean includeModes) { - List result = new ArrayList<>(); - - if (includeSuperClasses) { - // Add elements of elements defined in superclasses. - LinkedHashSet s = superClasses(definition); - if (s != null) { - for (Reactor superClass : s) { - result.addAll((EList) superClass.eGet(feature)); - } - } - } - - // Add elements of the current reactor. - result.addAll((EList) definition.eGet(feature)); - - if (includeModes && reactorModeFeatureMap.containsKey(feature)) { - var modeFeature = reactorModeFeatureMap.get(feature); - // Add elements of elements defined in modes. - for (Mode mode : includeSuperClasses ? allModes(definition) : definition.getModes()) { - insertModeElementsAtTextualPosition(result, (EList) mode.eGet(modeFeature), mode); - } - } - - return result; - } - - /** - * Adds the elements into the given list at a location matching to their textual position. - * - * When creating a flat view onto reactor elements including modes, the final list must be ordered according - * to the textual positions. - * - * Example: - * reactor R { - * reaction // -> is R.reactions[0] - * mode M { - * reaction // -> is R.mode[0].reactions[0] - * reaction // -> is R.mode[0].reactions[1] - * } - * reaction // -> is R.reactions[1] - * } - * In this example, it is important that the reactions in the mode are inserted between the top-level - * reactions to retain the correct global reaction ordering, which will be derived from this flattened view. - * - * @param list The list to add the elements into. - * @param elements The elements to add. - * @param mode The mode containing the elements. - * @param The type of elements to add (e.g., Port, Timer, etc.) - */ - private static void insertModeElementsAtTextualPosition(List list, List elements, Mode mode) { - if (elements.isEmpty()) { - return; // Nothing to add - } - - var idx = list.size(); - if (idx > 0) { - // If there are elements in the list, first check if the last element has the same container as the mode. - // I.e. we don't want to compare textual positions of different reactors (super classes) - if (mode.eContainer() == list.get(list.size() - 1).eContainer()) { - var modeAstNode = NodeModelUtils.getNode(mode); - if (modeAstNode != null) { - var modePos = modeAstNode.getOffset(); - // Now move the insertion index from the last element forward as long as this element has a textual - // position after the mode. - do { - var astNode = NodeModelUtils.getNode(list.get(idx - 1)); - if (astNode != null && astNode.getOffset() > modePos) { - idx--; - } else { - break; // Insertion index is ok. - } - } while (idx > 0); - } + /** The Lingua Franca factory for creating new AST nodes. */ + public static final LfFactory factory = LfFactory.eINSTANCE; + + /** The Lingua Franca feature package. */ + public static final LfPackage featurePackage = LfPackage.eINSTANCE; + + /* Match an abbreviated form of a float literal. */ + private static final Pattern ABBREVIATED_FLOAT = Pattern.compile("[+\\-]?\\.\\d+[\\deE+\\-]*"); + + /** + * A mapping from Reactor features to corresponding Mode features for collecting contained + * elements. + */ + private static final Map reactorModeFeatureMap = + Map.of( + featurePackage.getReactor_Actions(), featurePackage.getMode_Actions(), + featurePackage.getReactor_Connections(), featurePackage.getMode_Connections(), + featurePackage.getReactor_Instantiations(), featurePackage.getMode_Instantiations(), + featurePackage.getReactor_Reactions(), featurePackage.getMode_Reactions(), + featurePackage.getReactor_StateVars(), featurePackage.getMode_StateVars(), + featurePackage.getReactor_Timers(), featurePackage.getMode_Timers(), + featurePackage.getReactor_Watchdogs(), featurePackage.getMode_Watchdogs()); + + /** + * Get all reactors defined in the given resource. + * + * @param resource the resource to extract reactors from + * @return An iterable over all reactors found in the resource + */ + public static List getAllReactors(Resource resource) { + return StreamSupport.stream( + IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) + .filter(Reactor.class::isInstance) + .map(Reactor.class::cast) + .collect(Collectors.toList()); + } + + /** + * Find connections in the given resource that would be conflicting writes if they were not + * located in mutually exclusive modes. + * + * @param resource The AST. + * @return a list of connections being able to be transformed + */ + public static Collection findConflictingConnectionsInModalReactors( + Resource resource) { + var transform = new HashSet(); + + for (Reactor reactor : getAllReactors(resource)) { + if (!reactor.getModes().isEmpty()) { // Only for modal reactors + var allWriters = HashMultimap., EObject>create(); + + // Collect destinations + for (var rea : allReactions(reactor)) { + for (var eff : rea.getEffects()) { + if (eff.getVariable() instanceof Port) { + allWriters.put(Tuples.pair(eff.getContainer(), eff.getVariable()), rea); } + } } - list.addAll(idx, elements); - } - - public static Iterable allElementsOfClass( - Resource resource, - Class elementClass - ) { - //noinspection StaticPseudoFunctionalStyleMethod - return Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), elementClass); - } - - //////////////////////////////// - //// Utility functions for translating AST nodes into text - - /** - * Translate the given code into its textual representation - * with {@code CodeMap.Correspondence} tags inserted, or - * return the empty string if {@code node} is {@code null}. - * This method should be used to generate code. - * @param node AST node to render as string. - * @return Textual representation of the given argument. - */ - public static String toText(EObject node) { - if (node == null) return ""; - return CodeMap.Correspondence.tag(node, toOriginalText(node), node instanceof Code); - } - - /** - * Translate the given code into its textual representation - * without {@code CodeMap.Correspondence} tags, or return - * the empty string if {@code node} is {@code null}. - * This method should be used for analyzing AST nodes in - * cases where they are easiest to analyze as strings. - * @param node AST node to render as string. - * @return Textual representation of the given argument. - */ - public static String toOriginalText(EObject node) { - if (node == null) return ""; - return ToText.instance.doSwitch(node); - } - - /** - * Return an integer representation of the given element. - * - * Internally, this method uses Integer.decode, so it will - * also understand hexadecimal, binary, etc. - * - * @param e The element to be rendered as an integer. - */ - public static Integer toInteger(Element e) { - return Integer.decode(e.getLiteral()); - } - - /** - * Return a time value based on the given element. - * - * @param e The element to be rendered as a time value. - */ - public static TimeValue toTimeValue(Element e) { - return new TimeValue(e.getTime(), TimeUnit.fromName(e.getUnit())); - } - - /** - * Returns the time value represented by the given AST node. - */ - public static TimeValue toTimeValue(Time e) { - if (!isValidTime(e)) { - // invalid unit, will have been reported by validator - throw new IllegalArgumentException(); - } - return new TimeValue(e.getInterval(), TimeUnit.fromName(e.getUnit())); - } - - /** - * Return a boolean based on the given element. - * - * @param e The element to be rendered as a boolean. - */ - public static boolean toBoolean(Element e) { - return elementToSingleString(e).equalsIgnoreCase("true"); - } - - /** - * Given the right-hand side of a target property, return a string that - * represents the given value/ - * - * If the given value is not a literal or and id (but for instance and array or dict), - * an empty string is returned. If the element is a string, any quotes are removed. - * - * @param e The right-hand side of a target property. - */ - public static String elementToSingleString(Element e) { - if (e.getLiteral() != null) { - return StringUtil.removeQuotes(e.getLiteral()).trim(); - } else if (e.getId() != null) { - return e.getId(); + for (var con : + ASTUtils.collectElements( + reactor, featurePackage.getReactor_Connections(), false, true)) { + for (var port : con.getRightPorts()) { + allWriters.put(Tuples.pair(port.getContainer(), port.getVariable()), con); + } } - return ""; - } - /** - * Given the right-hand side of a target property, return a list with all - * the strings that the property lists. - * - * Arrays are traversed, so strings are collected recursively. Empty strings - * are ignored; they are not added to the list. - * @param value The right-hand side of a target property. - */ - public static List elementToListOfStrings(Element value) { - List elements = new ArrayList<>(); - if (value.getArray() != null) { - for (Element element : value.getArray().getElements()) { - elements.addAll(elementToListOfStrings(element)); + // Handle conflicting writers + for (var key : allWriters.keySet()) { + var writers = allWriters.get(key); + if (writers.size() > 1) { // has multiple sources + var writerModes = HashMultimap.create(); + // find modes + for (var writer : writers) { + if (writer.eContainer() instanceof Mode) { + writerModes.put((Mode) writer.eContainer(), writer); + } else { + writerModes.put(null, writer); + } } - return elements; - } else { - String v = elementToSingleString(value); - if (!v.isEmpty()) { - elements.add(v); + // Conflicting connection can only be handled if.. + if (!writerModes.containsKey(null) + && // no writer is on root level (outside of modes) and... + writerModes.keySet().stream() + .map(writerModes::get) + .allMatch( + writersInMode -> // all writers in a mode are either... + writersInMode.size() == 1 + || // the only writer or... + writersInMode.stream() + .allMatch( + w -> + w + instanceof + Reaction) // all are reactions and hence ordered + )) { + // Add connections to transform list + writers.stream() + .filter(w -> w instanceof Connection) + .forEach(c -> transform.add((Connection) c)); } + } } - return elements; - } - - /** - * Convert key-value pairs in an Element to a map, assuming that both the key - * and the value are strings. - */ - public static Map elementToStringMaps(Element value) { - Map elements = new HashMap<>(); - for (var element: value.getKeyvalue().getPairs()) { - elements.put( - element.getName().trim(), - StringUtil.removeQuotes(elementToSingleString(element.getValue())) - ); + } + } + + return transform; + } + + /** + * Return the enclosing reactor of an LF EObject in a reactor or mode. + * + * @param obj the LF model element + * @return the reactor or null + */ + public static Reactor getEnclosingReactor(EObject obj) { + if (obj.eContainer() instanceof Reactor) { + return (Reactor) obj.eContainer(); + } else if (obj.eContainer() instanceof Mode) { + return (Reactor) obj.eContainer().eContainer(); + } + return null; + } + + /** Return the main reactor in the given resource if there is one, null otherwise. */ + public static Reactor findMainReactor(Resource resource) { + return IteratorExtensions.findFirst( + Iterators.filter(resource.getAllContents(), Reactor.class), Reactor::isMain); + } + + /** + * Find the main reactor and change it to a federated reactor. Return true if the transformation + * was successful (or the given resource already had a federated reactor); return false otherwise. + */ + public static boolean makeFederated(Resource resource) { + // Find the main reactor + Reactor r = findMainReactor(resource); + if (r == null) { + return false; + } + r.setMain(false); + r.setFederated(true); + return true; + } + + /** Change the target name to 'newTargetName'. For example, change C to CCpp. */ + public static boolean changeTargetName(Resource resource, String newTargetName) { + targetDecl(resource).setName(newTargetName); + return true; + } + + /** Return the target of the file in which the given node lives. */ + public static Target getTarget(EObject object) { + TargetDecl targetDecl = targetDecl(object.eResource()); + return Target.fromDecl(targetDecl); + } + + /** + * Add a new target property to the given resource. + * + *

This also creates a config object if the resource does not yey have one. + * + * @param resource The resource to modify + * @param name Name of the property to add + * @param value Value to be assigned to the property + */ + public static boolean addTargetProperty( + final Resource resource, final String name, final Element value) { + var config = targetDecl(resource).getConfig(); + if (config == null) { + config = LfFactory.eINSTANCE.createKeyValuePairs(); + targetDecl(resource).setConfig(config); + } + final var newProperty = LfFactory.eINSTANCE.createKeyValuePair(); + newProperty.setName(name); + newProperty.setValue(value); + config.getPairs().add(newProperty); + return true; + } + + /** + * Return true if the connection involves multiple ports on the left or right side of the + * connection, or if the port on the left or right of the connection involves a bank of reactors + * or a multiport. + * + * @param connection The connection. + */ + public static boolean hasMultipleConnections(Connection connection) { + if (connection.getLeftPorts().size() > 1 || connection.getRightPorts().size() > 1) { + return true; + } + VarRef leftPort = connection.getLeftPorts().get(0); + VarRef rightPort = connection.getRightPorts().get(0); + Instantiation leftContainer = leftPort.getContainer(); + Instantiation rightContainer = rightPort.getContainer(); + Port leftPortAsPort = (Port) leftPort.getVariable(); + Port rightPortAsPort = (Port) rightPort.getVariable(); + return leftPortAsPort.getWidthSpec() != null + || leftContainer != null && leftContainer.getWidthSpec() != null + || rightPortAsPort.getWidthSpec() != null + || rightContainer != null && rightContainer.getWidthSpec() != null; + } + + /** + * Produce a unique identifier within a reactor based on a given based name. If the name does not + * exists, it is returned; if does exist, an index is appended that makes the name unique. + * + * @param reactor The reactor to find a unique identifier within. + * @param name The name to base the returned identifier on. + */ + public static String getUniqueIdentifier(Reactor reactor, String name) { + LinkedHashSet vars = new LinkedHashSet<>(); + allActions(reactor).forEach(it -> vars.add(it.getName())); + allTimers(reactor).forEach(it -> vars.add(it.getName())); + allParameters(reactor).forEach(it -> vars.add(it.getName())); + allInputs(reactor).forEach(it -> vars.add(it.getName())); + allOutputs(reactor).forEach(it -> vars.add(it.getName())); + allStateVars(reactor).forEach(it -> vars.add(it.getName())); + allInstantiations(reactor).forEach(it -> vars.add(it.getName())); + + int index = 0; + String suffix = ""; + boolean exists = true; + while (exists) { + String id = name + suffix; + if (IterableExtensions.exists(vars, it -> it.equals(id))) { + suffix = "_" + index; + index++; + } else { + exists = false; + } + } + return name + suffix; + } + + //////////////////////////////// + //// Utility functions for supporting inheritance and modes + + /** + * Given a reactor class, return a list of all its actions, which includes actions of base classes + * that it extends. This also includes actions in modes, returning a flattened view over all + * modes. + * + * @param definition Reactor class definition. + */ + public static List allActions(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Actions()); + } + + /** + * Given a reactor class, return a list of all its connections, which includes connections of base + * classes that it extends. This also includes connections in modes, returning a flattened view + * over all modes. + * + * @param definition Reactor class definition. + */ + public static List allConnections(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Connections()); + } + + /** + * Given a reactor class, return a list of all its inputs, which includes inputs of base classes + * that it extends. If the base classes include a cycle, where X extends Y and Y extends X, then + * return only the input defined in the base class. The returned list may be empty. + * + * @param definition Reactor class definition. + */ + public static List allInputs(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Inputs()); + } + + /** A list of all ports of {@code definition}, in an unspecified order. */ + public static List allPorts(Reactor definition) { + return Stream.concat( + ASTUtils.allInputs(definition).stream(), ASTUtils.allOutputs(definition).stream()) + .toList(); + } + + /** + * Given a reactor class, return a list of all its instantiations, which includes instantiations + * of base classes that it extends. This also includes instantiations in modes, returning a + * flattened view over all modes. + * + * @param definition Reactor class definition. + */ + public static List allInstantiations(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Instantiations()); + } + + public static Stream allNestedClasses(Reactor definition) { + return new HashSet<>(ASTUtils.allInstantiations(definition)) + .stream().map(Instantiation::getReactorClass).map(ASTUtils::toDefinition); + } + + /** + * Given a reactor class, return a list of all its methods, which includes methods of base classes + * that it extends. + * + * @param definition Reactor class definition. + */ + public static List allMethods(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Methods()); + } + + /** + * Given a reactor class, return a list of all its outputs, which includes outputs of base classes + * that it extends. + * + * @param definition Reactor class definition. + */ + public static List allOutputs(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Outputs()); + } + + /** + * Given a reactor class, return a list of all its parameters, which includes parameters of base + * classes that it extends. + * + * @param definition Reactor class definition. + */ + public static List allParameters(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Parameters()); + } + + /** + * Given a reactor class, return a list of all its reactions, which includes reactions of base + * classes that it extends. This also includes reactions in modes, returning a flattened view over + * all modes. + * + * @param definition Reactor class definition. + */ + public static List allReactions(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Reactions()); + } + + /** + * Given a reactor class, return a list of all its watchdogs. + * + * @param definition Reactor class definition + * @return List + */ + public static List allWatchdogs(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Watchdogs()); + } + + /** + * Given a reactor class, return a list of all its state variables, which includes state variables + * of base classes that it extends. This also includes reactions in modes, returning a flattened + * view over all modes. + * + * @param definition Reactor class definition. + */ + public static List allStateVars(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_StateVars()); + } + + /** + * Given a reactor class, return a list of all its timers, which includes timers of base classes + * that it extends. This also includes reactions in modes, returning a flattened view over all + * modes. + * + * @param definition Reactor class definition. + */ + public static List allTimers(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Timers()); + } + + /** + * Given a reactor class, returns a list of all its modes, which includes modes of base classes + * that it extends. + * + * @param definition Reactor class definition. + */ + public static List allModes(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Modes()); + } + + /** A list of all reactors instantiated, transitively or intransitively, by {@code r}. */ + public static List recursiveChildren(ReactorInstance r) { + List ret = new ArrayList<>(); + ret.add(r.reactorDefinition); + for (var child : r.children) { + ret.addAll(recursiveChildren(child)); + } + return ret; + } + + /** + * Return all the superclasses of the specified reactor in deepest-first order. For example, if A + * extends B and C, and B and C both extend D, this will return the list [D, B, C, A]. Duplicates + * are removed. If the specified reactor does not extend any other reactor, then return an empty + * list. If a cycle is found, where X extends Y and Y extends X, or if a superclass is declared + * that is not found, then return null. + * + * @param reactor The specified reactor. + */ + public static LinkedHashSet superClasses(Reactor reactor) { + return superClasses(reactor, new LinkedHashSet<>()); + } + + /** + * Collect elements of type T from the class hierarchy and modes defined by a given reactor + * definition. + * + * @param definition The reactor definition. + * @param The type of elements to collect (e.g., Port, Timer, etc.) + * @return A list of all elements of type T found + */ + public static List collectElements( + Reactor definition, EStructuralFeature feature) { + return ASTUtils.collectElements(definition, feature, true, true); + } + + /** + * Collect elements of type T contained in given reactor definition, including modes and the class + * hierarchy defined depending on configuration. + * + * @param definition The reactor definition. + * @param feature The structual model elements to collect. + * @param includeSuperClasses Whether to include elements in super classes. + * @param includeModes Whether to include elements in modes. + * @param The type of elements to collect (e.g., Port, Timer, etc.) + * @return A list of all elements of type T found + */ + @SuppressWarnings("unchecked") + public static List collectElements( + Reactor definition, + EStructuralFeature feature, + boolean includeSuperClasses, + boolean includeModes) { + List result = new ArrayList<>(); + + if (includeSuperClasses) { + // Add elements of elements defined in superclasses. + LinkedHashSet s = superClasses(definition); + if (s != null) { + for (Reactor superClass : s) { + result.addAll((EList) superClass.eGet(feature)); } - return elements; - } - - // Various utility methods to convert various data types to Elements - - /** - * Convert a map to key-value pairs in an Element. - */ - public static Element toElement(Map map) { - Element e = LfFactory.eINSTANCE.createElement(); - if (map.size() == 0) return null; - else { - var kv = LfFactory.eINSTANCE.createKeyValuePairs(); - for (var entry : map.entrySet()) { - var pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(entry.getKey()); - var element = LfFactory.eINSTANCE.createElement(); - element.setLiteral(StringUtil.addDoubleQuotes(entry.getValue())); - pair.setValue(element); - kv.getPairs().add(pair); - } - e.setKeyvalue(kv); - } - - return e; - } - - /** - * Given a single string, convert it into its AST representation. - * {@code addQuotes} controls if the generated representation should be - * accompanied by double quotes ("") or not. - */ - private static Element toElement(String str, boolean addQuotes) { - if (str == null) return null; - var strToReturn = addQuotes? StringUtil.addDoubleQuotes(str):str; - Element e = LfFactory.eINSTANCE.createElement(); - e.setLiteral(strToReturn); - return e; - - } - - /** - * Given a single string, convert it into its AST representation. - */ - public static Element toElement(String str) { - return toElement(str, true); - } - - /** - * Given a list of strings, convert it into its AST representation. - * Stores the list in the Array field of the element, unless the list only has one string, - * in which case it is stored in the Literal field. Returns null if the provided list is empty. - */ - public static Element toElement(List list) { - Element e = LfFactory.eINSTANCE.createElement(); - if (list.size() == 0) return null; - else if (list.size() == 1) { - return toElement(list.get(0)); - } else { - var arr = LfFactory.eINSTANCE.createArray(); - for (String s : list) { - arr.getElements().add(ASTUtils.toElement(s)); - } - e.setArray(arr); - } - return e; - } - - /** - * Convert a TimeValue to its AST representation. The value is type-cast to int in order to fit inside an Element. - */ - public static Element toElement(TimeValue tv) { - Element e = LfFactory.eINSTANCE.createElement(); - e.setTime((int)tv.time); - if (tv.unit != null) { - e.setUnit(tv.unit.toString()); - } - return e; - } - - public static Element toElement(boolean val) { - return toElement(Boolean.toString(val), false); - } - - public static Element toElement(int val) { - return toElement(Integer.toString(val), false); - } - - /** - * Translate the given type into its textual representation, but - * do not append any array specifications or type arguments. - * @param type AST node to render as string. - * @return Textual representation of the given argument. - */ - public static String baseType(Type type) { - if (type != null) { - if (type.getCode() != null) { - return toText(type.getCode()); + } + } + + // Add elements of the current reactor. + result.addAll((EList) definition.eGet(feature)); + + if (includeModes && reactorModeFeatureMap.containsKey(feature)) { + var modeFeature = reactorModeFeatureMap.get(feature); + // Add elements of elements defined in modes. + for (Mode mode : includeSuperClasses ? allModes(definition) : definition.getModes()) { + insertModeElementsAtTextualPosition(result, (EList) mode.eGet(modeFeature), mode); + } + } + + return result; + } + + /** + * Adds the elements into the given list at a location matching to their textual position. + * + *

When creating a flat view onto reactor elements including modes, the final list must be + * ordered according to the textual positions. + * + *

Example: reactor R { reaction // -> is R.reactions[0] mode M { reaction // -> is + * R.mode[0].reactions[0] reaction // -> is R.mode[0].reactions[1] } reaction // -> is + * R.reactions[1] } In this example, it is important that the reactions in the mode are inserted + * between the top-level reactions to retain the correct global reaction ordering, which will be + * derived from this flattened view. + * + * @param list The list to add the elements into. + * @param elements The elements to add. + * @param mode The mode containing the elements. + * @param The type of elements to add (e.g., Port, Timer, etc.) + */ + private static void insertModeElementsAtTextualPosition( + List list, List elements, Mode mode) { + if (elements.isEmpty()) { + return; // Nothing to add + } + + var idx = list.size(); + if (idx > 0) { + // If there are elements in the list, first check if the last element has the same container + // as the mode. + // I.e. we don't want to compare textual positions of different reactors (super classes) + if (mode.eContainer() == list.get(list.size() - 1).eContainer()) { + var modeAstNode = NodeModelUtils.getNode(mode); + if (modeAstNode != null) { + var modePos = modeAstNode.getOffset(); + // Now move the insertion index from the last element forward as long as this element has + // a textual + // position after the mode. + do { + var astNode = NodeModelUtils.getNode(list.get(idx - 1)); + if (astNode != null && astNode.getOffset() > modePos) { + idx--; } else { - if (type.isTime()) { - return "time"; - } else { - StringBuilder result = new StringBuilder(type.getId()); - - for (String s : convertToEmptyListIfNull(type.getStars())) { - result.append(s); - } - return result.toString(); - } - } - } - return ""; - } - - /** - * Report whether the given literal is zero or not. - * @param literal AST node to inspect. - * @return True if the given literal denotes the constant `0`, false - * otherwise. - */ - public static boolean isZero(String literal) { - try { - if (literal != null && - Integer.parseInt(literal) == 0) { - return true; + break; // Insertion index is ok. } - } catch (NumberFormatException e) { - // Not an int. + } while (idx > 0); } - return false; - } - - /** - * Report whether the given expression is zero or not. - * - * @param expr AST node to inspect. - * @return True if the given value denotes the constant `0`, false otherwise. - */ - public static boolean isZero(Expression expr) { - if (expr instanceof Literal) { - return isZero(((Literal) expr).getLiteral()); - } - return false; - } - - /** - * Report whether the given string literal is an integer number or not. - * - * @param literal AST node to inspect. - * @return True if the given value is an integer, false otherwise. - */ - public static boolean isInteger(String literal) { - try { - //noinspection ResultOfMethodCallIgnored - Integer.decode(literal); - } catch (NumberFormatException e) { - return false; - } - return true; - } - - /** - * Report whether the given string literal is a boolean value or not. - * @param literal AST node to inspect. - * @return True if the given value is a boolean, false otherwise. - */ - public static boolean isBoolean(String literal) { - return literal.equalsIgnoreCase("true") || literal.equalsIgnoreCase("false"); - } + } + } + list.addAll(idx, elements); + } + + public static Iterable allElementsOfClass( + Resource resource, Class elementClass) { + //noinspection StaticPseudoFunctionalStyleMethod + return Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), elementClass); + } + + //////////////////////////////// + //// Utility functions for translating AST nodes into text + + /** + * Translate the given code into its textual representation with {@code CodeMap.Correspondence} + * tags inserted, or return the empty string if {@code node} is {@code null}. This method should + * be used to generate code. + * + * @param node AST node to render as string. + * @return Textual representation of the given argument. + */ + public static String toText(EObject node) { + if (node == null) return ""; + return CodeMap.Correspondence.tag(node, toOriginalText(node), node instanceof Code); + } + + /** + * Translate the given code into its textual representation without {@code CodeMap.Correspondence} + * tags, or return the empty string if {@code node} is {@code null}. This method should be used + * for analyzing AST nodes in cases where they are easiest to analyze as strings. + * + * @param node AST node to render as string. + * @return Textual representation of the given argument. + */ + public static String toOriginalText(EObject node) { + if (node == null) return ""; + return ToText.instance.doSwitch(node); + } + + /** + * Return an integer representation of the given element. + * + *

Internally, this method uses Integer.decode, so it will also understand hexadecimal, binary, + * etc. + * + * @param e The element to be rendered as an integer. + */ + public static Integer toInteger(Element e) { + return Integer.decode(e.getLiteral()); + } + + /** + * Return a time value based on the given element. + * + * @param e The element to be rendered as a time value. + */ + public static TimeValue toTimeValue(Element e) { + return new TimeValue(e.getTime(), TimeUnit.fromName(e.getUnit())); + } + + /** Returns the time value represented by the given AST node. */ + public static TimeValue toTimeValue(Time e) { + if (!isValidTime(e)) { + // invalid unit, will have been reported by validator + throw new IllegalArgumentException(); + } + return new TimeValue(e.getInterval(), TimeUnit.fromName(e.getUnit())); + } + + /** + * Return a boolean based on the given element. + * + * @param e The element to be rendered as a boolean. + */ + public static boolean toBoolean(Element e) { + return elementToSingleString(e).equalsIgnoreCase("true"); + } + + /** + * Given the right-hand side of a target property, return a string that represents the given + * value/ + * + *

If the given value is not a literal or and id (but for instance and array or dict), an empty + * string is returned. If the element is a string, any quotes are removed. + * + * @param e The right-hand side of a target property. + */ + public static String elementToSingleString(Element e) { + if (e.getLiteral() != null) { + return StringUtil.removeQuotes(e.getLiteral()).trim(); + } else if (e.getId() != null) { + return e.getId(); + } + return ""; + } + + /** + * Given the right-hand side of a target property, return a list with all the strings that the + * property lists. + * + *

Arrays are traversed, so strings are collected recursively. Empty strings are ignored; they + * are not added to the list. + * + * @param value The right-hand side of a target property. + */ + public static List elementToListOfStrings(Element value) { + List elements = new ArrayList<>(); + if (value.getArray() != null) { + for (Element element : value.getArray().getElements()) { + elements.addAll(elementToListOfStrings(element)); + } + return elements; + } else { + String v = elementToSingleString(value); + if (!v.isEmpty()) { + elements.add(v); + } + } + return elements; + } + + /** + * Convert key-value pairs in an Element to a map, assuming that both the key and the value are + * strings. + */ + public static Map elementToStringMaps(Element value) { + Map elements = new HashMap<>(); + for (var element : value.getKeyvalue().getPairs()) { + elements.put( + element.getName().trim(), + StringUtil.removeQuotes(elementToSingleString(element.getValue()))); + } + return elements; + } + + // Various utility methods to convert various data types to Elements + + /** Convert a map to key-value pairs in an Element. */ + public static Element toElement(Map map) { + Element e = LfFactory.eINSTANCE.createElement(); + if (map.size() == 0) return null; + else { + var kv = LfFactory.eINSTANCE.createKeyValuePairs(); + for (var entry : map.entrySet()) { + var pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(entry.getKey()); + var element = LfFactory.eINSTANCE.createElement(); + element.setLiteral(StringUtil.addDoubleQuotes(entry.getValue())); + pair.setValue(element); + kv.getPairs().add(pair); + } + e.setKeyvalue(kv); + } + + return e; + } + + /** + * Given a single string, convert it into its AST representation. {@code addQuotes} controls if + * the generated representation should be accompanied by double quotes ("") or not. + */ + private static Element toElement(String str, boolean addQuotes) { + if (str == null) return null; + var strToReturn = addQuotes ? StringUtil.addDoubleQuotes(str) : str; + Element e = LfFactory.eINSTANCE.createElement(); + e.setLiteral(strToReturn); + return e; + } + + /** Given a single string, convert it into its AST representation. */ + public static Element toElement(String str) { + return toElement(str, true); + } + + /** + * Given a list of strings, convert it into its AST representation. Stores the list in the Array + * field of the element, unless the list only has one string, in which case it is stored in the + * Literal field. Returns null if the provided list is empty. + */ + public static Element toElement(List list) { + Element e = LfFactory.eINSTANCE.createElement(); + if (list.size() == 0) return null; + else if (list.size() == 1) { + return toElement(list.get(0)); + } else { + var arr = LfFactory.eINSTANCE.createArray(); + for (String s : list) { + arr.getElements().add(ASTUtils.toElement(s)); + } + e.setArray(arr); + } + return e; + } + + /** + * Convert a TimeValue to its AST representation. The value is type-cast to int in order to fit + * inside an Element. + */ + public static Element toElement(TimeValue tv) { + Element e = LfFactory.eINSTANCE.createElement(); + e.setTime((int) tv.time); + if (tv.unit != null) { + e.setUnit(tv.unit.toString()); + } + return e; + } + + public static Element toElement(boolean val) { + return toElement(Boolean.toString(val), false); + } + + public static Element toElement(int val) { + return toElement(Integer.toString(val), false); + } + + /** + * Translate the given type into its textual representation, but do not append any array + * specifications or type arguments. + * + * @param type AST node to render as string. + * @return Textual representation of the given argument. + */ + public static String baseType(Type type) { + if (type != null) { + if (type.getCode() != null) { + return toText(type.getCode()); + } else { + if (type.isTime()) { + return "time"; + } else { + StringBuilder result = new StringBuilder(type.getId()); - /** - * Report whether the given string literal is a float value or not. - * @param literal AST node to inspect. - * @return True if the given value is a float, false otherwise. - */ - public static boolean isFloat(String literal) { - try { - //noinspection ResultOfMethodCallIgnored - Float.parseFloat(literal); - } catch (NumberFormatException e) { - return false; + for (String s : convertToEmptyListIfNull(type.getStars())) { + result.append(s); + } + return result.toString(); } + } + } + return ""; + } + + /** + * Report whether the given literal is zero or not. + * + * @param literal AST node to inspect. + * @return True if the given literal denotes the constant `0`, false otherwise. + */ + public static boolean isZero(String literal) { + try { + if (literal != null && Integer.parseInt(literal) == 0) { return true; - } - - /** - * Report whether the given code is an integer number or not. - * @param code AST node to inspect. - * @return True if the given code is an integer, false otherwise. - */ - public static boolean isInteger(Code code) { - return isInteger(toText(code)); - } - - /** - * Report whether the given expression is an integer number or not. - * @param expr AST node to inspect. - * @return True if the given value is an integer, false otherwise. - */ - public static boolean isInteger(Expression expr) { - if (expr instanceof Literal) { - return isInteger(((Literal) expr).getLiteral()); - } else if (expr instanceof Code) { - return isInteger((Code) expr); - } - return false; - } - - /** - * Report whether the given expression denotes a valid time or not. - * @param expr AST node to inspect. - * @return True if the argument denotes a valid time, false otherwise. - */ - public static boolean isValidTime(Expression expr) { - if (expr instanceof ParameterReference) { - return isOfTimeType(((ParameterReference)expr).getParameter()); - } else if (expr instanceof Time) { - return isValidTime((Time) expr); - } else if (expr instanceof Literal) { - return isZero(((Literal) expr).getLiteral()); - } - return false; - } - - /** - * Report whether the given time denotes a valid time or not. - * @param t AST node to inspect. - * @return True if the argument denotes a valid time, false otherwise. - */ - public static boolean isValidTime(Time t) { - if (t == null) return false; - String unit = t.getUnit(); - return t.getInterval() == 0 || - TimeUnit.isValidUnit(unit); - } - - /** - * If the initializer contains exactly one expression, - * return it. Otherwise, return null. - */ - public static Expression asSingleExpr(Initializer init) { - if (init == null) { - return null; + } + } catch (NumberFormatException e) { + // Not an int. + } + return false; + } + + /** + * Report whether the given expression is zero or not. + * + * @param expr AST node to inspect. + * @return True if the given value denotes the constant `0`, false otherwise. + */ + public static boolean isZero(Expression expr) { + if (expr instanceof Literal) { + return isZero(((Literal) expr).getLiteral()); + } + return false; + } + + /** + * Report whether the given string literal is an integer number or not. + * + * @param literal AST node to inspect. + * @return True if the given value is an integer, false otherwise. + */ + public static boolean isInteger(String literal) { + try { + //noinspection ResultOfMethodCallIgnored + Integer.decode(literal); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + /** + * Report whether the given string literal is a boolean value or not. + * + * @param literal AST node to inspect. + * @return True if the given value is a boolean, false otherwise. + */ + public static boolean isBoolean(String literal) { + return literal.equalsIgnoreCase("true") || literal.equalsIgnoreCase("false"); + } + + /** + * Report whether the given string literal is a float value or not. + * + * @param literal AST node to inspect. + * @return True if the given value is a float, false otherwise. + */ + public static boolean isFloat(String literal) { + try { + //noinspection ResultOfMethodCallIgnored + Float.parseFloat(literal); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + /** + * Report whether the given code is an integer number or not. + * + * @param code AST node to inspect. + * @return True if the given code is an integer, false otherwise. + */ + public static boolean isInteger(Code code) { + return isInteger(toText(code)); + } + + /** + * Report whether the given expression is an integer number or not. + * + * @param expr AST node to inspect. + * @return True if the given value is an integer, false otherwise. + */ + public static boolean isInteger(Expression expr) { + if (expr instanceof Literal) { + return isInteger(((Literal) expr).getLiteral()); + } else if (expr instanceof Code) { + return isInteger((Code) expr); + } + return false; + } + + /** + * Report whether the given expression denotes a valid time or not. + * + * @param expr AST node to inspect. + * @return True if the argument denotes a valid time, false otherwise. + */ + public static boolean isValidTime(Expression expr) { + if (expr instanceof ParameterReference) { + return isOfTimeType(((ParameterReference) expr).getParameter()); + } else if (expr instanceof Time) { + return isValidTime((Time) expr); + } else if (expr instanceof Literal) { + return isZero(((Literal) expr).getLiteral()); + } + return false; + } + + /** + * Report whether the given time denotes a valid time or not. + * + * @param t AST node to inspect. + * @return True if the argument denotes a valid time, false otherwise. + */ + public static boolean isValidTime(Time t) { + if (t == null) return false; + String unit = t.getUnit(); + return t.getInterval() == 0 || TimeUnit.isValidUnit(unit); + } + + /** If the initializer contains exactly one expression, return it. Otherwise, return null. */ + public static Expression asSingleExpr(Initializer init) { + if (init == null) { + return null; + } + var exprs = init.getExprs(); + return exprs.size() == 1 ? exprs.get(0) : null; + } + + public static boolean isSingleExpr(Initializer init) { + // todo expand that to = initialization + if (init == null) { + return false; + } + var exprs = init.getExprs(); + return exprs.size() == 1; + } + + public static boolean isListInitializer(Initializer init) { + return init != null && !isSingleExpr(init); + } + + /** + * Return the type of a declaration with the given (nullable) explicit type, and the given + * (nullable) initializer. If the explicit type is null, then the type is inferred from the + * initializer. Only two types can be inferred: "time" and "timeList". Return the "undefined" type + * if neither can be inferred. + * + * @param type Explicit type declared on the declaration + * @param init The initializer expression + * @return The inferred type, or "undefined" if none could be inferred. + */ + public static InferredType getInferredType(Type type, Initializer init) { + if (type != null) { + return InferredType.fromAST(type); + } else if (init == null) { + return InferredType.undefined(); + } + + var single = asSingleExpr(init); + if (single != null) { + // If there is a single element in the list, and it is a proper + // time value with units, we infer the type "time". + if (single instanceof ParameterReference) { + return getInferredType(((ParameterReference) single).getParameter()); + } else if (single instanceof Time) { + return InferredType.time(); + } + } else if (init.getExprs().size() > 1) { + // If there are multiple elements in the list, and there is at + // least one proper time value with units, and all other elements + // are valid times (including zero without units), we infer the + // type "time list". + var allValidTime = true; + var foundNonZero = false; + + for (var e : init.getExprs()) { + if (!ASTUtils.isValidTime(e)) { + allValidTime = false; } - var exprs = init.getExprs(); - return exprs.size() == 1 ? exprs.get(0) : null; - } - - public static boolean isSingleExpr(Initializer init) { - // todo expand that to = initialization - if (init == null) { - return false; + if (!ASTUtils.isZero(e)) { + foundNonZero = true; } - var exprs = init.getExprs(); - return exprs.size() == 1; - } - - public static boolean isListInitializer(Initializer init) { - return init != null && !isSingleExpr(init); - } - - /** - * Return the type of a declaration with the given - * (nullable) explicit type, and the given (nullable) - * initializer. If the explicit type is null, then the - * type is inferred from the initializer. Only two types - * can be inferred: "time" and "timeList". Return the - * "undefined" type if neither can be inferred. - * - * @param type Explicit type declared on the declaration - * @param init The initializer expression - * @return The inferred type, or "undefined" if none could be inferred. - */ - public static InferredType getInferredType(Type type, Initializer init) { - if (type != null) { - return InferredType.fromAST(type); - } else if (init == null) { - return InferredType.undefined(); + } + + if (allValidTime && foundNonZero) { + // Conservatively, no bounds are inferred; the returned type + // is a variable-size list. + return InferredType.timeList(); + } + } + return InferredType.undefined(); + } + + /** + * Given a parameter, return an inferred type. Only two types can be inferred: "time" and + * "timeList". Return the "undefined" type if neither can be inferred. + * + * @param p A parameter to infer the type of. + * @return The inferred type, or "undefined" if none could be inferred. + */ + public static InferredType getInferredType(Parameter p) { + return getInferredType(p.getType(), p.getInit()); + } + + /** + * Given a state variable, return an inferred type. Only two types can be inferred: "time" and + * "timeList". Return the "undefined" type if neither can be inferred. + * + * @param s A state variable to infer the type of. + * @return The inferred type, or "undefined" if none could be inferred. + */ + public static InferredType getInferredType(StateVar s) { + return getInferredType(s.getType(), s.getInit()); + } + + /** + * Construct an inferred type from an "action" AST node based on its declared type. If no type is + * declared, return the "undefined" type. + * + * @param a An action to construct an inferred type object for. + * @return The inferred type, or "undefined" if none was declared. + */ + public static InferredType getInferredType(Action a) { + return getInferredType(a.getType(), null); + } + + /** + * Construct an inferred type from a "port" AST node based on its declared type. If no type is + * declared, return the "undefined" type. + * + * @param p A port to construct an inferred type object for. + * @return The inferred type, or "undefined" if none was declared. + */ + public static InferredType getInferredType(Port p) { + return getInferredType(p.getType(), null); + } + + /** + * If the given string can be recognized as a floating-point number that has a leading decimal + * point, prepend the string with a zero and return it. Otherwise, return the original string. + * + * @param literal A string might be recognizable as a floating point number with a leading decimal + * point. + * @return an equivalent representation of literal + * + */ + public static String addZeroToLeadingDot(String literal) { + Matcher m = ABBREVIATED_FLOAT.matcher(literal); + if (m.matches()) { + return literal.replace(".", "0."); + } + return literal; + } + + /** + * Return true if the specified port is a multiport. + * + * @param port The port. + * @return True if the port is a multiport. + */ + public static boolean isMultiport(Port port) { + return port.getWidthSpec() != null; + } + + //////////////////////////////// + //// Utility functions for translating AST nodes into text + // This is a continuation of a large section of ASTUtils.xtend + // with the same name. + + /** + * Generate code for referencing a port, action, or timer. + * + * @param reference The reference to the variable. + */ + public static String generateVarRef(VarRef reference) { + var prefix = ""; + if (reference.getContainer() != null) { + prefix = reference.getContainer().getName() + "."; + } + return prefix + reference.getVariable().getName(); + } + + /** Assuming that the given expression denotes a valid time literal, return a time value. */ + public static TimeValue getLiteralTimeValue(Expression expr) { + if (expr instanceof Time) { + return toTimeValue((Time) expr); + } else if (expr instanceof Literal && isZero(((Literal) expr).getLiteral())) { + return TimeValue.ZERO; + } else { + return null; + } + } + + /** If the parameter is of time type, return its default value. Otherwise, return null. */ + public static TimeValue getDefaultAsTimeValue(Parameter p) { + if (isOfTimeType(p)) { + var init = asSingleExpr(p.getInit()); + if (init != null) { + return getLiteralTimeValue(init); + } + } + return null; + } + + /** Return whether the given state variable is inferred to a time type. */ + public static boolean isOfTimeType(StateVar state) { + InferredType t = getInferredType(state); + return t.isTime && !t.isList; + } + + /** Return whether the given parameter is inferred to a time type. */ + public static boolean isOfTimeType(Parameter param) { + InferredType t = getInferredType(param); + return t.isTime && !t.isList; + } + + /** + * Given a parameter, return its initial value. The initial value is a list of instances of + * Expressions. + * + *

If the instantiations argument is null or an empty list, then the value returned is simply + * the default value given when the parameter is defined. + * + *

If a list of instantiations is given, then the first instantiation is required to be an + * instantiation of the reactor class that is parameterized by the parameter. I.e., ``` + * parameter.eContainer == instantiations.get(0).reactorClass ``` If a second instantiation is + * given, then it is required to be an instantiation of a reactor class that contains the first + * instantiation. That is, ``` instantiations.get(0).eContainer == + * instantiations.get(1).reactorClass ``` More generally, for all 0 <= i < instantiations.size - + * 1, ``` instantiations.get(i).eContainer == instantiations.get(i + 1).reactorClass ``` If any of + * these conditions is not satisfied, then an IllegalArgumentException will be thrown. + * + *

Note that this chain of reactions cannot be inferred from the parameter because in each of + * the predicates above, there may be more than one instantiation that can appear on the right + * hand side of the predicate. + * + *

For example, consider the following program: ``` reactor A(x:int(1)) {} reactor B(y:int(2)) + * { a1 = new A(x = y); a2 = new A(x = -1); } reactor C(z:int(3)) { b1 = new B(y = z); b2 = new + * B(y = -2); } ``` Notice that there are a total of four instances of reactor class A. Then ``` + * initialValue(x, null) returns 1 initialValue(x, [a1]) returns 2 initialValue(x, [a2]) returns + * -1 initialValue(x, [a1, b1]) returns 3 initialValue(x, [a2, b1]) returns -1 initialValue(x, + * [a1, b2]) returns -2 initialValue(x, [a2, b2]) returns -1 ``` (Actually, in each of the above + * cases, the returned value is a list with one entry, a Literal, e.g. ["1"]). + * + *

There are two instances of reactor class B. ``` initialValue(y, null) returns 2 + * initialValue(y, [a1]) throws an IllegalArgumentException initialValue(y, [b1]) returns 3 + * initialValue(y, [b2]) returns -2 ``` + * + * @param parameter The parameter. + * @param instantiations The (optional) list of instantiations. + * @return The value of the parameter. + * @throws IllegalArgumentException If an instantiation provided is not an instantiation of the + * reactor class that is parameterized by the respective parameter or if the chain of + * instantiations is not nested. + */ + public static List initialValue( + Parameter parameter, List instantiations) { + // If instantiations are given, then check to see whether this parameter gets overridden in + // the first of those instantiations. + if (instantiations != null && instantiations.size() > 0) { + // Check to be sure that the instantiation is in fact an instantiation + // of the reactor class for which this is a parameter. + Instantiation instantiation = instantiations.get(0); + + if (!belongsTo(parameter, instantiation)) { + throw new IllegalArgumentException( + "Parameter " + + parameter.getName() + + " is not a parameter of reactor instance " + + instantiation.getName() + + "."); + } + // In case there is more than one assignment to this parameter, we need to + // find the last one. + Assignment lastAssignment = null; + for (Assignment assignment : instantiation.getParameters()) { + if (assignment.getLhs().equals(parameter)) { + lastAssignment = assignment; } - - var single = asSingleExpr(init); - if (single != null) { - // If there is a single element in the list, and it is a proper - // time value with units, we infer the type "time". - if (single instanceof ParameterReference) { - return getInferredType(((ParameterReference) single).getParameter()); - } else if (single instanceof Time) { - return InferredType.time(); - } - } else if (init.getExprs().size() > 1) { - // If there are multiple elements in the list, and there is at - // least one proper time value with units, and all other elements - // are valid times (including zero without units), we infer the - // type "time list". - var allValidTime = true; - var foundNonZero = false; - - for (var e : init.getExprs()) { - if (!ASTUtils.isValidTime(e)) { - allValidTime = false; - } - if (!ASTUtils.isZero(e)) { - foundNonZero = true; - } - } - - if (allValidTime && foundNonZero) { - // Conservatively, no bounds are inferred; the returned type - // is a variable-size list. - return InferredType.timeList(); - } - } - return InferredType.undefined(); - } - - /** - * Given a parameter, return an inferred type. Only two types can be - * inferred: "time" and "timeList". Return the "undefined" type if - * neither can be inferred. - * - * @param p A parameter to infer the type of. - * @return The inferred type, or "undefined" if none could be inferred. - */ - public static InferredType getInferredType(Parameter p) { - return getInferredType(p.getType(), p.getInit()); - } - - /** - * Given a state variable, return an inferred type. Only two types can be - * inferred: "time" and "timeList". Return the "undefined" type if - * neither can be inferred. - * - * @param s A state variable to infer the type of. - * @return The inferred type, or "undefined" if none could be inferred. - */ - public static InferredType getInferredType(StateVar s) { - return getInferredType(s.getType(), s.getInit()); - } - - /** - * Construct an inferred type from an "action" AST node based - * on its declared type. If no type is declared, return the "undefined" - * type. - * - * @param a An action to construct an inferred type object for. - * @return The inferred type, or "undefined" if none was declared. - */ - public static InferredType getInferredType(Action a) { - return getInferredType(a.getType(), null); - } - - /** - * Construct an inferred type from a "port" AST node based on its declared - * type. If no type is declared, return the "undefined" type. - * - * @param p A port to construct an inferred type object for. - * @return The inferred type, or "undefined" if none was declared. - */ - public static InferredType getInferredType(Port p) { - return getInferredType(p.getType(), null); - } - - - - /** - * If the given string can be recognized as a floating-point number that has a leading decimal point, - * prepend the string with a zero and return it. Otherwise, return the original string. - * - * @param literal A string might be recognizable as a floating point number with a leading decimal point. - * @return an equivalent representation of literal - * - */ - public static String addZeroToLeadingDot(String literal) { - Matcher m = ABBREVIATED_FLOAT.matcher(literal); - if (m.matches()) { - return literal.replace(".", "0."); - } - return literal; - } - - /** - * Return true if the specified port is a multiport. - * @param port The port. - * @return True if the port is a multiport. - */ - public static boolean isMultiport(Port port) { - return port.getWidthSpec() != null; - } - - //////////////////////////////// - //// Utility functions for translating AST nodes into text - // This is a continuation of a large section of ASTUtils.xtend - // with the same name. - - /** - * Generate code for referencing a port, action, or timer. - * @param reference The reference to the variable. - */ - public static String generateVarRef(VarRef reference) { - var prefix = ""; - if (reference.getContainer() != null) { - prefix = reference.getContainer().getName() + "."; - } - return prefix + reference.getVariable().getName(); - } - - /** - * Assuming that the given expression denotes a valid time literal, - * return a time value. - */ - public static TimeValue getLiteralTimeValue(Expression expr) { - if (expr instanceof Time) { - return toTimeValue((Time)expr); - } else if (expr instanceof Literal && isZero(((Literal) expr).getLiteral())) { - return TimeValue.ZERO; - } else { - return null; - } - } - - /** - * If the parameter is of time type, return its default value. - * Otherwise, return null. - */ - public static TimeValue getDefaultAsTimeValue(Parameter p) { - if (isOfTimeType(p)) { - var init = asSingleExpr(p.getInit()); - if (init != null) { - return getLiteralTimeValue(init); - } - } - return null; - } - - /** - * Return whether the given state variable is inferred - * to a time type. - */ - public static boolean isOfTimeType(StateVar state) { - InferredType t = getInferredType(state); - return t.isTime && !t.isList; - } - - /** - * Return whether the given parameter is inferred - * to a time type. - */ - public static boolean isOfTimeType(Parameter param) { - InferredType t = getInferredType(param); - return t.isTime && !t.isList; - } - - /** - * Given a parameter, return its initial value. - * The initial value is a list of instances of Expressions. - * - * If the instantiations argument is null or an empty list, then the - * value returned is simply the default value given when the parameter - * is defined. - * - * If a list of instantiations is given, then the first instantiation - * is required to be an instantiation of the reactor class that is - * parameterized by the parameter. I.e., - * ``` - * parameter.eContainer == instantiations.get(0).reactorClass - * ``` - * If a second instantiation is given, then it is required to be an instantiation of a - * reactor class that contains the first instantiation. That is, - * ``` - * instantiations.get(0).eContainer == instantiations.get(1).reactorClass - * ``` - * More generally, for all 0 <= i < instantiations.size - 1, - * ``` - * instantiations.get(i).eContainer == instantiations.get(i + 1).reactorClass - * ``` - * If any of these conditions is not satisfied, then an IllegalArgumentException - * will be thrown. - * - * Note that this chain of reactions cannot be inferred from the parameter because - * in each of the predicates above, there may be more than one instantiation that - * can appear on the right hand side of the predicate. - * - * For example, consider the following program: - * ``` - * reactor A(x:int(1)) {} - * reactor B(y:int(2)) { - * a1 = new A(x = y); - * a2 = new A(x = -1); - * } - * reactor C(z:int(3)) { - * b1 = new B(y = z); - * b2 = new B(y = -2); - * } - * ``` - * Notice that there are a total of four instances of reactor class A. - * Then - * ``` - * initialValue(x, null) returns 1 - * initialValue(x, [a1]) returns 2 - * initialValue(x, [a2]) returns -1 - * initialValue(x, [a1, b1]) returns 3 - * initialValue(x, [a2, b1]) returns -1 - * initialValue(x, [a1, b2]) returns -2 - * initialValue(x, [a2, b2]) returns -1 - * ``` - * (Actually, in each of the above cases, the returned value is a list with - * one entry, a Literal, e.g. ["1"]). - * - * There are two instances of reactor class B. - * ``` - * initialValue(y, null) returns 2 - * initialValue(y, [a1]) throws an IllegalArgumentException - * initialValue(y, [b1]) returns 3 - * initialValue(y, [b2]) returns -2 - * ``` - * - * @param parameter The parameter. - * @param instantiations The (optional) list of instantiations. - * - * @return The value of the parameter. - * - * @throws IllegalArgumentException If an instantiation provided is not an - * instantiation of the reactor class that is parameterized by the - * respective parameter or if the chain of instantiations is not nested. - */ - public static List initialValue(Parameter parameter, List instantiations) { - // If instantiations are given, then check to see whether this parameter gets overridden in - // the first of those instantiations. - if (instantiations != null && instantiations.size() > 0) { - // Check to be sure that the instantiation is in fact an instantiation - // of the reactor class for which this is a parameter. - Instantiation instantiation = instantiations.get(0); - - if (!belongsTo(parameter, instantiation)) { - throw new IllegalArgumentException("Parameter " - + parameter.getName() - + " is not a parameter of reactor instance " - + instantiation.getName() - + "." - ); - } - // In case there is more than one assignment to this parameter, we need to - // find the last one. - Assignment lastAssignment = null; - for (Assignment assignment: instantiation.getParameters()) { - if (assignment.getLhs().equals(parameter)) { - lastAssignment = assignment; - } - } - if (lastAssignment != null) { - // Right hand side can be a list. Collect the entries. - List result = new ArrayList<>(); - for (Expression expr: lastAssignment.getRhs().getExprs()) { - if (expr instanceof ParameterReference) { - if (instantiations.size() > 1 - && instantiation.eContainer() != instantiations.get(1).getReactorClass() - ) { - throw new IllegalArgumentException("Reactor instance " - + instantiation.getName() - + " is not contained by instance " - + instantiations.get(1).getName() - + "." - ); - } - result.addAll(initialValue(((ParameterReference)expr).getParameter(), - instantiations.subList(1, instantiations.size()))); - } else { - result.add(expr); - } - } - return result; - } - } - // If we reach here, then either no instantiation was supplied or - // there was no assignment in the instantiation. So just use the - // parameter's initial value. - return parameter.getInit().getExprs(); - } - - /** - * Return true if the specified object (a Parameter, Port, Action, or Timer) - * belongs to the specified instantiation, meaning that it is defined in - * the reactor class being instantiated or one of its base classes. - * @param eobject The object. - * @param instantiation The instantiation. - */ - public static boolean belongsTo(EObject eobject, Instantiation instantiation) { - Reactor reactor = toDefinition(instantiation.getReactorClass()); - return belongsTo(eobject, reactor); - } - - /** - * Return true if the specified object (a Parameter, Port, Action, or Timer) - * belongs to the specified reactor, meaning that it is defined in - * reactor class or one of its base classes. - * @param eobject The object. - * @param reactor The reactor. - */ - public static boolean belongsTo(EObject eobject, Reactor reactor) { - if (eobject.eContainer() == reactor) return true; - for (ReactorDecl baseClass : reactor.getSuperClasses()) { - if (belongsTo(eobject, toDefinition(baseClass))) { - return true; - } - } - return false; - } - - /** - * Given a parameter return its integer value or null - * if it does not have an integer value. - * If the value of the parameter is a list of integers, - * return the sum of value in the list. - * The instantiations parameter is as in - * {@link #initialValue(Parameter, List)}. - * - * @param parameter The parameter. - * @param instantiations The (optional) list of instantiations. - * - * @return The integer value of the parameter, or null if it does not have an integer value. - * - * @throws IllegalArgumentException If an instantiation provided is not an - * instantiation of the reactor class that is parameterized by the - * respective parameter or if the chain of instantiations is not nested. - */ - public static Integer initialValueInt(Parameter parameter, List instantiations) { - List expressions = initialValue(parameter, instantiations); - int result = 0; - for (Expression expr: expressions) { - if (!(expr instanceof Literal)) { - return null; - } - try { - result += Integer.decode(((Literal) expr).getLiteral()); - } catch (NumberFormatException ex) { - return null; + } + if (lastAssignment != null) { + // Right hand side can be a list. Collect the entries. + List result = new ArrayList<>(); + for (Expression expr : lastAssignment.getRhs().getExprs()) { + if (expr instanceof ParameterReference) { + if (instantiations.size() > 1 + && instantiation.eContainer() != instantiations.get(1).getReactorClass()) { + throw new IllegalArgumentException( + "Reactor instance " + + instantiation.getName() + + " is not contained by instance " + + instantiations.get(1).getName() + + "."); } + result.addAll( + initialValue( + ((ParameterReference) expr).getParameter(), + instantiations.subList(1, instantiations.size()))); + } else { + result.add(expr); + } } return result; - } - - /** - * Given the width specification of port or instantiation - * and an (optional) list of nested instantiations, return - * the width if it can be determined and -1 if not. - * It will not be able to be determined if either the - * width is variable (in which case you should use - * {@link #inferPortWidth(VarRef, Connection, List)} ) - * or the list of instantiations is incomplete or missing. - * If there are parameter references in the width, they are - * evaluated to the extent possible given the instantiations list. - * - * The instantiations list is as in - * {@link #initialValue(Parameter, List)}. - * If the spec belongs to an instantiation (for a bank of reactors), - * then the first element on this list should be the instantiation - * that contains this instantiation. If the spec belongs to a port, - * then the first element on the list should be the instantiation - * of the reactor that contains the port. - * - * @param spec The width specification or null (to return 1). - * @param instantiations The (optional) list of instantiations. - * - * @return The width, or -1 if the width could not be determined. - * - * @throws IllegalArgumentException If an instantiation provided is not as - * given above or if the chain of instantiations is not nested. - */ - public static int width(WidthSpec spec, List instantiations) { - if (spec == null) { - return 1; + } + } + // If we reach here, then either no instantiation was supplied or + // there was no assignment in the instantiation. So just use the + // parameter's initial value. + return parameter.getInit().getExprs(); + } + + /** + * Return true if the specified object (a Parameter, Port, Action, or Timer) belongs to the + * specified instantiation, meaning that it is defined in the reactor class being instantiated or + * one of its base classes. + * + * @param eobject The object. + * @param instantiation The instantiation. + */ + public static boolean belongsTo(EObject eobject, Instantiation instantiation) { + Reactor reactor = toDefinition(instantiation.getReactorClass()); + return belongsTo(eobject, reactor); + } + + /** + * Return true if the specified object (a Parameter, Port, Action, or Timer) belongs to the + * specified reactor, meaning that it is defined in reactor class or one of its base classes. + * + * @param eobject The object. + * @param reactor The reactor. + */ + public static boolean belongsTo(EObject eobject, Reactor reactor) { + if (eobject.eContainer() == reactor) return true; + for (ReactorDecl baseClass : reactor.getSuperClasses()) { + if (belongsTo(eobject, toDefinition(baseClass))) { + return true; + } + } + return false; + } + + /** + * Given a parameter return its integer value or null if it does not have an integer value. If the + * value of the parameter is a list of integers, return the sum of value in the list. The + * instantiations parameter is as in {@link #initialValue(Parameter, List)}. + * + * @param parameter The parameter. + * @param instantiations The (optional) list of instantiations. + * @return The integer value of the parameter, or null if it does not have an integer value. + * @throws IllegalArgumentException If an instantiation provided is not an instantiation of the + * reactor class that is parameterized by the respective parameter or if the chain of + * instantiations is not nested. + */ + public static Integer initialValueInt(Parameter parameter, List instantiations) { + List expressions = initialValue(parameter, instantiations); + int result = 0; + for (Expression expr : expressions) { + if (!(expr instanceof Literal)) { + return null; + } + try { + result += Integer.decode(((Literal) expr).getLiteral()); + } catch (NumberFormatException ex) { + return null; + } + } + return result; + } + + /** + * Given the width specification of port or instantiation and an (optional) list of nested + * instantiations, return the width if it can be determined and -1 if not. It will not be able to + * be determined if either the width is variable (in which case you should use {@link + * #inferPortWidth(VarRef, Connection, List)} ) or the list of instantiations is incomplete or + * missing. If there are parameter references in the width, they are evaluated to the extent + * possible given the instantiations list. + * + *

The instantiations list is as in {@link #initialValue(Parameter, List)}. If the spec belongs + * to an instantiation (for a bank of reactors), then the first element on this list should be the + * instantiation that contains this instantiation. If the spec belongs to a port, then the first + * element on the list should be the instantiation of the reactor that contains the port. + * + * @param spec The width specification or null (to return 1). + * @param instantiations The (optional) list of instantiations. + * @return The width, or -1 if the width could not be determined. + * @throws IllegalArgumentException If an instantiation provided is not as given above or if the + * chain of instantiations is not nested. + */ + public static int width(WidthSpec spec, List instantiations) { + if (spec == null) { + return 1; + } + if (spec.isOfVariableLength() && spec.eContainer() instanceof Instantiation) { + return inferWidthFromConnections(spec, instantiations); + } + var result = 0; + for (WidthTerm term : spec.getTerms()) { + if (term.getParameter() != null) { + Integer termWidth = initialValueInt(term.getParameter(), instantiations); + if (termWidth != null) { + result += termWidth; + } else { + return -1; } - if (spec.isOfVariableLength() && spec.eContainer() instanceof Instantiation) { + } else if (term.getWidth() > 0) { + result += term.getWidth(); + } else { + // If the width cannot be determined because term's width <= 0, which means the term's width + // must be inferred, try to infer the width using connections. + if (spec.eContainer() instanceof Instantiation) { + try { return inferWidthFromConnections(spec, instantiations); + } catch (InvalidSourceException e) { + // If the inference fails, return -1. + return -1; + } } - var result = 0; - for (WidthTerm term: spec.getTerms()) { - if (term.getParameter() != null) { - Integer termWidth = initialValueInt(term.getParameter(), instantiations); - if (termWidth != null) { - result += termWidth; - } else { - return -1; - } - } else if (term.getWidth() > 0) { - result += term.getWidth(); - } else { - // If the width cannot be determined because term's width <= 0, which means the term's width - // must be inferred, try to infer the width using connections. - if (spec.eContainer() instanceof Instantiation) { - try { - return inferWidthFromConnections(spec, instantiations); - } catch (InvalidSourceException e) { - // If the inference fails, return -1. - return -1; - } - } - } + } + } + return result; + } + + /** + * Infer the width of a port reference in a connection. The port reference one or two parts, a + * port and an (optional) container which is an Instantiation that may refer to a bank of + * reactors. The width will be the product of the bank width and the port width. The returned + * value will be 1 if the port is not in a bank and is not a multiport. + * + *

If the width cannot be determined, this will return -1. The width cannot be determined if + * the list of instantiations is missing or incomplete. + * + *

The instantiations list is as in {@link #initialValue(Parameter, List)}. The first element + * on this list should be the instantiation that contains the specified connection. + * + * @param reference A port reference. + * @param connection A connection, or null if not in the context of a connection. + * @param instantiations The (optional) list of instantiations. + * @return The width or -1 if it could not be determined. + * @throws IllegalArgumentException If an instantiation provided is not as given above or if the + * chain of instantiations is not nested. + */ + public static int inferPortWidth( + VarRef reference, Connection connection, List instantiations) { + if (reference.getVariable() instanceof Port) { + // If the port is given as a.b, then we want to prepend a to + // the list of instantiations to determine the width of this port. + List extended = instantiations; + if (reference.getContainer() != null) { + extended = new ArrayList<>(); + extended.add(reference.getContainer()); + if (instantiations != null) { + extended.addAll(instantiations); } - return result; - } - - /** - * Infer the width of a port reference in a connection. - * The port reference one or two parts, a port and an (optional) container - * which is an Instantiation that may refer to a bank of reactors. - * The width will be the product of the bank width and the port width. - * The returned value will be 1 if the port is not in a bank and is not a multiport. - * - * If the width cannot be determined, this will return -1. - * The width cannot be determined if the list of instantiations is - * missing or incomplete. - * - * The instantiations list is as in - * {@link #initialValue(Parameter, List)}. - * The first element on this list should be the instantiation - * that contains the specified connection. - * - * @param reference A port reference. - * @param connection A connection, or null if not in the context of a connection. - * @param instantiations The (optional) list of instantiations. - * - * @return The width or -1 if it could not be determined. - * - * @throws IllegalArgumentException If an instantiation provided is not as - * given above or if the chain of instantiations is not nested. - */ - public static int inferPortWidth( - VarRef reference, Connection connection, List instantiations - ) { - if (reference.getVariable() instanceof Port) { - // If the port is given as a.b, then we want to prepend a to - // the list of instantiations to determine the width of this port. - List extended = instantiations; - if (reference.getContainer() != null) { - extended = new ArrayList<>(); - extended.add(reference.getContainer()); - if (instantiations != null) { - extended.addAll(instantiations); - } - } + } - int portWidth = width(((Port) reference.getVariable()).getWidthSpec(), extended); - if (portWidth < 0) { - // Could not determine port width. - return -1; - } - - // Next determine the bank width. This may be unspecified, in which - // case it has to be inferred using the connection. - int bankWidth = 1; - if (reference.getContainer() != null) { - bankWidth = width(reference.getContainer().getWidthSpec(), instantiations); - if (bankWidth < 0 && connection != null) { - // Try to infer the bank width from the connection. - if (reference.getContainer().getWidthSpec().isOfVariableLength()) { - // This occurs for a bank of delays. - int leftWidth = 0; - int rightWidth = 0; - int leftOrRight = 0; - for (VarRef leftPort : connection.getLeftPorts()) { - if (leftPort == reference) { - if (leftOrRight != 0) { - throw new InvalidSourceException("Multiple ports with variable width on a connection."); - } - // Indicate that this port is on the left. - leftOrRight = -1; - } else { - // The left port is not the same as this reference. - int otherWidth = inferPortWidth(leftPort, connection, instantiations); - if (otherWidth < 0) { - // Cannot determine width. - return -1; - } - leftWidth += otherWidth; - } - } - for (VarRef rightPort : connection.getRightPorts()) { - if (rightPort == reference) { - if (leftOrRight != 0) { - throw new InvalidSourceException("Multiple ports with variable width on a connection."); - } - // Indicate that this port is on the right. - leftOrRight = 1; - } else { - int otherWidth = inferPortWidth(rightPort, connection, instantiations); - if (otherWidth < 0) { - // Cannot determine width. - return -1; - } - rightWidth += otherWidth; - } - } - int discrepancy = 0; - if (leftOrRight < 0) { - // This port is on the left. - discrepancy = rightWidth - leftWidth; - } else if (leftOrRight > 0) { - // This port is on the right. - discrepancy = leftWidth - rightWidth; - } - // Check that portWidth divides the discrepancy. - if (discrepancy % portWidth != 0) { - // This is an error. - return -1; - } - bankWidth = discrepancy / portWidth; - } else { - // Could not determine the bank width. - return -1; - } - } - } - return portWidth * bankWidth; - } - // Argument is not a port. + int portWidth = width(((Port) reference.getVariable()).getWidthSpec(), extended); + if (portWidth < 0) { + // Could not determine port width. return -1; - } - - /** - * Given an instantiation of a reactor or bank of reactors, return - * the width. This will be 1 if this is not a reactor bank. Otherwise, - * this will attempt to determine the width. If the width is declared - * as a literal constant, it will return that constant. If the width - * is specified as a reference to a parameter, this will throw an - * exception. If the width is variable, this will find - * connections in the enclosing reactor and attempt to infer the - * width. If the width cannot be determined, it will throw an exception. - * - * IMPORTANT: This method should not be used you really need to - * determine the width! It will not evaluate parameter values. - * @see #width(WidthSpec, List) - * - * @param instantiation A reactor instantiation. - * - * @return The width, if it can be determined. - * @deprecated - */ - @Deprecated - public static int widthSpecification(Instantiation instantiation) { - int result = width(instantiation.getWidthSpec(), null); - if (result < 0) { - throw new InvalidSourceException("Cannot determine width for the instance " - + instantiation.getName()); - } - return result; - } - - /** - * Report whether a state variable has been initialized or not. - * @param v The state variable to be checked. - * @return True if the variable was initialized, false otherwise. - */ - public static boolean isInitialized(StateVar v) { - return v != null && v.getInit() != null; - } - - /** - * Report whether the given time state variable is initialized using a - * parameter or not. - * @param s A state variable. - * @return True if the argument is initialized using a parameter, false - * otherwise. - */ - public static boolean isParameterized(StateVar s) { - return s.getInit() != null && - IterableExtensions.exists(s.getInit().getExprs(), it -> it instanceof ParameterReference); - } - - /** - * Check if the reactor class uses generics - * @param r the reactor to check - * @return true if the reactor uses generics - */ - public static boolean isGeneric(Reactor r) { - if (r == null) { - return false; - } - return r.getTypeParms().size() != 0; - } - - /** - * If the specified reactor declaration is an import, then - * return the imported reactor class definition. Otherwise, - * just return the argument. - * @param r A Reactor or an ImportedReactor. - * @return The Reactor class definition or null if no definition is found. - */ - public static Reactor toDefinition(ReactorDecl r) { - if (r == null) - return null; - if (r instanceof Reactor) { - return (Reactor) r; - } else if (r instanceof ImportedReactor) { - return ((ImportedReactor) r).getReactorClass(); - } - return null; - } - - /** - * Return all single-line or multi-line comments immediately preceding the - * given EObject. - */ - public static Stream getPrecedingComments( - ICompositeNode compNode, - Predicate filter - ) { - return getPrecedingCommentNodes(compNode, filter).map(INode::getText); - } - - /** - * Return all single-line or multi-line comments immediately preceding the - * given EObject. - */ - public static Stream getPrecedingCommentNodes( - ICompositeNode compNode, - Predicate filter - ) { - if (compNode == null) return Stream.of(); - List ret = new ArrayList<>(); - for (INode node : compNode.getAsTreeIterable()) { - if (!(node instanceof ICompositeNode)) { - if (isComment(node)) { - if (filter.test(node)) ret.add(node); - } else if (!node.getText().isBlank()) { - break; - } - } - } - return ret.stream(); - } - - /** Return whether {@code node} is a comment. */ - public static boolean isComment(INode node) { - return node instanceof HiddenLeafNode hlNode - && hlNode.getGrammarElement() instanceof TerminalRule tRule - && tRule.getName().endsWith("_COMMENT"); - } - - /** - * Return true if the given node starts on the same line as the given other - * node. - */ - public static Predicate sameLine(ICompositeNode compNode) { - return other -> { - for (INode node : compNode.getAsTreeIterable()) { - if (!(node instanceof ICompositeNode) && !node.getText().isBlank() && !isComment(node)) { - return node.getStartLine() == other.getStartLine(); - } - } - return false; - }; - } - - /** - * Find the main reactor and set its name if none was defined. - * @param resource The resource to find the main reactor in. - */ - public static void setMainName(Resource resource, String name) { - Reactor main = IteratorExtensions.findFirst(Iterators.filter(resource.getAllContents(), Reactor.class), - it -> it.isMain() || it.isFederated() - ); - if (main != null && StringExtensions.isNullOrEmpty(main.getName())) { - main.setName(name); - } - } - - /** - * Create a new instantiation node with the given reactor as its defining class. - * @param reactor The reactor class to create an instantiation of. - */ - public static Instantiation createInstantiation(Reactor reactor) { - Instantiation inst = LfFactory.eINSTANCE.createInstantiation(); - inst.setReactorClass(reactor); - // If the reactor is federated or at the top level, then it - // may not have a name. In the generator's doGenerate() - // method, the name gets set using setMainName(). - // But this may be called before that, e.g. during - // diagram synthesis. We assign a temporary name here. - if (reactor.getName() == null) { - if (reactor.isFederated() || reactor.isMain()) { - inst.setName("main"); - } else { - inst.setName(""); - } - - } else { - inst.setName(reactor.getName()); - } - return inst; - } - - /** - * Returns the target declaration in the given model. - * Non-null because it would cause a parse error. - */ - public static TargetDecl targetDecl(Model model) { - return IteratorExtensions.head(Iterators.filter(model.eAllContents(), TargetDecl.class)); - } - - /** - * Returns the target declaration in the given resource. - * Non-null because it would cause a parse error. - */ - public static TargetDecl targetDecl(Resource model) { - return IteratorExtensions.head(Iterators.filter(model.getAllContents(), TargetDecl.class)); - } - - ///////////////////////////////////////////////////////// - //// Private methods - - /** - * Returns the list if it is not null. Otherwise, return an empty list. - */ - public static List convertToEmptyListIfNull(List list) { - return list != null ? list : new ArrayList<>(); - } - - /** - * Return all the superclasses of the specified reactor - * in deepest-first order. For example, if A extends B and C, and - * B and C both extend D, this will return the list [D, B, C, A]. - * Duplicates are removed. If the specified reactor does not extend - * any other reactor, then return an empty list. - * If a cycle is found, where X extends Y and Y extends X, or if - * a superclass is declared that is not found, then return null. - * @param reactor The specified reactor. - * @param extensions A set of reactors extending the specified reactor - * (used to detect circular extensions). - */ - private static LinkedHashSet superClasses(Reactor reactor, Set extensions) { - LinkedHashSet result = new LinkedHashSet<>(); - for (ReactorDecl superDecl : convertToEmptyListIfNull(reactor.getSuperClasses())) { - Reactor r = toDefinition(superDecl); - if (r == reactor || r == null) return null; - // If r is in the extensions, then we have a circular inheritance structure. - if (extensions.contains(r)) return null; - extensions.add(r); - LinkedHashSet baseExtends = superClasses(r, extensions); - extensions.remove(r); - if (baseExtends == null) return null; - result.addAll(baseExtends); - result.add(r); - } - return result; - } - - /** - * We may be able to infer the width by examining the connections of - * the enclosing reactor definition. This works, for example, with - * delays between multiports or banks of reactors. - * Attempt to infer the width from connections and return -1 if the width cannot be inferred. - * - * @param spec The width specification or null (to return 1). - * @param instantiations The (optional) list of instantiations. - * - * @return The width, or -1 if the width could not be inferred from connections. - */ - private static int inferWidthFromConnections(WidthSpec spec, List instantiations) { - for (Connection c : ((Reactor) spec.eContainer().eContainer()).getConnections()) { + } + + // Next determine the bank width. This may be unspecified, in which + // case it has to be inferred using the connection. + int bankWidth = 1; + if (reference.getContainer() != null) { + bankWidth = width(reference.getContainer().getWidthSpec(), instantiations); + if (bankWidth < 0 && connection != null) { + // Try to infer the bank width from the connection. + if (reference.getContainer().getWidthSpec().isOfVariableLength()) { + // This occurs for a bank of delays. int leftWidth = 0; int rightWidth = 0; int leftOrRight = 0; - for (VarRef leftPort : c.getLeftPorts()) { - if (leftPort.getContainer() == spec.eContainer()) { - if (leftOrRight != 0) { - throw new InvalidSourceException("Multiple ports with variable width on a connection."); - } - // Indicate that the port is on the left. - leftOrRight = -1; - } else { - leftWidth += inferPortWidth(leftPort, c, instantiations); + for (VarRef leftPort : connection.getLeftPorts()) { + if (leftPort == reference) { + if (leftOrRight != 0) { + throw new InvalidSourceException( + "Multiple ports with variable width on a connection."); + } + // Indicate that this port is on the left. + leftOrRight = -1; + } else { + // The left port is not the same as this reference. + int otherWidth = inferPortWidth(leftPort, connection, instantiations); + if (otherWidth < 0) { + // Cannot determine width. + return -1; } + leftWidth += otherWidth; + } } - for (VarRef rightPort : c.getRightPorts()) { - if (rightPort.getContainer() == spec.eContainer()) { - if (leftOrRight != 0) { - throw new InvalidSourceException("Multiple ports with variable width on a connection."); - } - // Indicate that the port is on the right. - leftOrRight = 1; - } else { - rightWidth += inferPortWidth(rightPort, c, instantiations); + for (VarRef rightPort : connection.getRightPorts()) { + if (rightPort == reference) { + if (leftOrRight != 0) { + throw new InvalidSourceException( + "Multiple ports with variable width on a connection."); + } + // Indicate that this port is on the right. + leftOrRight = 1; + } else { + int otherWidth = inferPortWidth(rightPort, connection, instantiations); + if (otherWidth < 0) { + // Cannot determine width. + return -1; } + rightWidth += otherWidth; + } } + int discrepancy = 0; if (leftOrRight < 0) { - return rightWidth - leftWidth; + // This port is on the left. + discrepancy = rightWidth - leftWidth; } else if (leftOrRight > 0) { - return leftWidth - rightWidth; + // This port is on the right. + discrepancy = leftWidth - rightWidth; } + // Check that portWidth divides the discrepancy. + if (discrepancy % portWidth != 0) { + // This is an error. + return -1; + } + bankWidth = discrepancy / portWidth; + } else { + // Could not determine the bank width. + return -1; + } } - // A connection was not found with the instantiation. - return -1; - } - - public static void addReactionAttribute(Reaction reaction, String name) { - var fedAttr = factory.createAttribute(); - fedAttr.setAttrName(name); - reaction.getAttributes().add(fedAttr); - } + } + return portWidth * bankWidth; + } + // Argument is not a port. + return -1; + } + + /** + * Given an instantiation of a reactor or bank of reactors, return the width. This will be 1 if + * this is not a reactor bank. Otherwise, this will attempt to determine the width. If the width + * is declared as a literal constant, it will return that constant. If the width is specified as a + * reference to a parameter, this will throw an exception. If the width is variable, this will + * find connections in the enclosing reactor and attempt to infer the width. If the width cannot + * be determined, it will throw an exception. + * + *

IMPORTANT: This method should not be used you really need to determine the width! It will + * not evaluate parameter values. + * + * @see #width(WidthSpec, List) + * @param instantiation A reactor instantiation. + * @return The width, if it can be determined. + * @deprecated + */ + @Deprecated + public static int widthSpecification(Instantiation instantiation) { + int result = width(instantiation.getWidthSpec(), null); + if (result < 0) { + throw new InvalidSourceException( + "Cannot determine width for the instance " + instantiation.getName()); + } + return result; + } + + /** + * Report whether a state variable has been initialized or not. + * + * @param v The state variable to be checked. + * @return True if the variable was initialized, false otherwise. + */ + public static boolean isInitialized(StateVar v) { + return v != null && v.getInit() != null; + } + + /** + * Report whether the given time state variable is initialized using a parameter or not. + * + * @param s A state variable. + * @return True if the argument is initialized using a parameter, false otherwise. + */ + public static boolean isParameterized(StateVar s) { + return s.getInit() != null + && IterableExtensions.exists( + s.getInit().getExprs(), it -> it instanceof ParameterReference); + } + + /** + * Check if the reactor class uses generics + * + * @param r the reactor to check + * @return true if the reactor uses generics + */ + public static boolean isGeneric(Reactor r) { + if (r == null) { + return false; + } + return r.getTypeParms().size() != 0; + } + + /** + * If the specified reactor declaration is an import, then return the imported reactor class + * definition. Otherwise, just return the argument. + * + * @param r A Reactor or an ImportedReactor. + * @return The Reactor class definition or null if no definition is found. + */ + public static Reactor toDefinition(ReactorDecl r) { + if (r == null) return null; + if (r instanceof Reactor) { + return (Reactor) r; + } else if (r instanceof ImportedReactor) { + return ((ImportedReactor) r).getReactorClass(); + } + return null; + } + + /** Return all single-line or multi-line comments immediately preceding the given EObject. */ + public static Stream getPrecedingComments( + ICompositeNode compNode, Predicate filter) { + return getPrecedingCommentNodes(compNode, filter).map(INode::getText); + } + + /** Return all single-line or multi-line comments immediately preceding the given EObject. */ + public static Stream getPrecedingCommentNodes( + ICompositeNode compNode, Predicate filter) { + if (compNode == null) return Stream.of(); + List ret = new ArrayList<>(); + for (INode node : compNode.getAsTreeIterable()) { + if (!(node instanceof ICompositeNode)) { + if (isComment(node)) { + if (filter.test(node)) ret.add(node); + } else if (!node.getText().isBlank()) { + break; + } + } + } + return ret.stream(); + } + + /** Return whether {@code node} is a comment. */ + public static boolean isComment(INode node) { + return node instanceof HiddenLeafNode hlNode + && hlNode.getGrammarElement() instanceof TerminalRule tRule + && tRule.getName().endsWith("_COMMENT"); + } + + /** Return true if the given node starts on the same line as the given other node. */ + public static Predicate sameLine(ICompositeNode compNode) { + return other -> { + for (INode node : compNode.getAsTreeIterable()) { + if (!(node instanceof ICompositeNode) && !node.getText().isBlank() && !isComment(node)) { + return node.getStartLine() == other.getStartLine(); + } + } + return false; + }; + } + + /** + * Find the main reactor and set its name if none was defined. + * + * @param resource The resource to find the main reactor in. + */ + public static void setMainName(Resource resource, String name) { + Reactor main = + IteratorExtensions.findFirst( + Iterators.filter(resource.getAllContents(), Reactor.class), + it -> it.isMain() || it.isFederated()); + if (main != null && StringExtensions.isNullOrEmpty(main.getName())) { + main.setName(name); + } + } + + /** + * Create a new instantiation node with the given reactor as its defining class. + * + * @param reactor The reactor class to create an instantiation of. + */ + public static Instantiation createInstantiation(Reactor reactor) { + Instantiation inst = LfFactory.eINSTANCE.createInstantiation(); + inst.setReactorClass(reactor); + // If the reactor is federated or at the top level, then it + // may not have a name. In the generator's doGenerate() + // method, the name gets set using setMainName(). + // But this may be called before that, e.g. during + // diagram synthesis. We assign a temporary name here. + if (reactor.getName() == null) { + if (reactor.isFederated() || reactor.isMain()) { + inst.setName("main"); + } else { + inst.setName(""); + } + + } else { + inst.setName(reactor.getName()); + } + return inst; + } + + /** + * Returns the target declaration in the given model. Non-null because it would cause a parse + * error. + */ + public static TargetDecl targetDecl(Model model) { + return IteratorExtensions.head(Iterators.filter(model.eAllContents(), TargetDecl.class)); + } + + /** + * Returns the target declaration in the given resource. Non-null because it would cause a parse + * error. + */ + public static TargetDecl targetDecl(Resource model) { + return IteratorExtensions.head(Iterators.filter(model.getAllContents(), TargetDecl.class)); + } + + ///////////////////////////////////////////////////////// + //// Private methods + + /** Returns the list if it is not null. Otherwise, return an empty list. */ + public static List convertToEmptyListIfNull(List list) { + return list != null ? list : new ArrayList<>(); + } + + /** + * Return all the superclasses of the specified reactor in deepest-first order. For example, if A + * extends B and C, and B and C both extend D, this will return the list [D, B, C, A]. Duplicates + * are removed. If the specified reactor does not extend any other reactor, then return an empty + * list. If a cycle is found, where X extends Y and Y extends X, or if a superclass is declared + * that is not found, then return null. + * + * @param reactor The specified reactor. + * @param extensions A set of reactors extending the specified reactor (used to detect circular + * extensions). + */ + private static LinkedHashSet superClasses(Reactor reactor, Set extensions) { + LinkedHashSet result = new LinkedHashSet<>(); + for (ReactorDecl superDecl : convertToEmptyListIfNull(reactor.getSuperClasses())) { + Reactor r = toDefinition(superDecl); + if (r == reactor || r == null) return null; + // If r is in the extensions, then we have a circular inheritance structure. + if (extensions.contains(r)) return null; + extensions.add(r); + LinkedHashSet baseExtends = superClasses(r, extensions); + extensions.remove(r); + if (baseExtends == null) return null; + result.addAll(baseExtends); + result.add(r); + } + return result; + } + + /** + * We may be able to infer the width by examining the connections of the enclosing reactor + * definition. This works, for example, with delays between multiports or banks of reactors. + * Attempt to infer the width from connections and return -1 if the width cannot be inferred. + * + * @param spec The width specification or null (to return 1). + * @param instantiations The (optional) list of instantiations. + * @return The width, or -1 if the width could not be inferred from connections. + */ + private static int inferWidthFromConnections(WidthSpec spec, List instantiations) { + for (Connection c : ((Reactor) spec.eContainer().eContainer()).getConnections()) { + int leftWidth = 0; + int rightWidth = 0; + int leftOrRight = 0; + for (VarRef leftPort : c.getLeftPorts()) { + if (leftPort.getContainer() == spec.eContainer()) { + if (leftOrRight != 0) { + throw new InvalidSourceException("Multiple ports with variable width on a connection."); + } + // Indicate that the port is on the left. + leftOrRight = -1; + } else { + leftWidth += inferPortWidth(leftPort, c, instantiations); + } + } + for (VarRef rightPort : c.getRightPorts()) { + if (rightPort.getContainer() == spec.eContainer()) { + if (leftOrRight != 0) { + throw new InvalidSourceException("Multiple ports with variable width on a connection."); + } + // Indicate that the port is on the right. + leftOrRight = 1; + } else { + rightWidth += inferPortWidth(rightPort, c, instantiations); + } + } + if (leftOrRight < 0) { + return rightWidth - leftWidth; + } else if (leftOrRight > 0) { + return leftWidth - rightWidth; + } + } + // A connection was not found with the instantiation. + return -1; + } + + public static void addReactionAttribute(Reaction reaction, String name) { + var fedAttr = factory.createAttribute(); + fedAttr.setAttrName(name); + reaction.getAttributes().add(fedAttr); + } } diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 1ce6656e96..4e1645e9db 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -87,6 +87,7 @@ Reactor: | (outputs+=Output) | (timers+=Timer) | (actions+=Action) + | (watchdogs+=Watchdog) | (instantiations+=Instantiation) | (connections+=Connection) | (reactions+=Reaction) @@ -174,6 +175,7 @@ Mode: (stateVars+=StateVar) | (timers+=Timer) | (actions+=Action) | + (watchdogs+=Watchdog) | (instantiations+=Instantiation) | (connections+=Connection) | (reactions+=Reaction) @@ -213,6 +215,11 @@ BuiltinTriggerRef: Deadline: 'deadline' '(' delay=Expression ')' code=Code; +Watchdog: + 'watchdog' name=ID '(' timeout=Expression ')' + ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? + code=Code; + STP: 'STP' '(' value=Expression ')' code=Code; @@ -273,7 +280,7 @@ TypedVariable: ; Variable: - TypedVariable | Timer | Mode; + TypedVariable | Timer | Mode | Watchdog; VarRef: (variable=[Variable] | container=[Instantiation] '.' variable=[Variable] @@ -457,7 +464,7 @@ SignedFloat: // Just escaping with \ is not a good idea because then every \ has to be escaped \\. // Perhaps the string EQUALS_BRACE could become '=}'? Code: - //{Code} '{=' (tokens+=Token)* '=}' + // {Code} '{=' (tokens+=Token)* '=}' {Code} '{=' body=Body '=}' ; @@ -504,7 +511,8 @@ Token: 'mutable' | 'input' | 'output' | 'timer' | 'action' | 'reaction' | 'startup' | 'shutdown' | 'after' | 'deadline' | 'mutation' | 'preamble' | 'new' | 'federated' | 'at' | 'as' | 'from' | 'widthof' | 'const' | 'method' | - 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'named' | + 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' | 'named' | + // Other terminals NEGINT | TRUE | FALSE | // Action origins diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 7f2dd072ea..73cd12916a 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -6,9 +6,7 @@ import java.util.function.BiPredicate; import java.util.function.Function; import java.util.stream.Collectors; - import org.eclipse.emf.ecore.EObject; - import org.lflang.TimeUnit; import org.lflang.lf.Action; import org.lflang.lf.Array; @@ -60,616 +58,610 @@ import org.lflang.lf.TypedVariable; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.lf.util.LfSwitch; /** - * Switch class that checks if subtrees of the AST are semantically equivalent - * to each other. Return {@code false} if they are not equivalent; return - * {@code true} or {@code false} (but preferably {@code true}) if they are - * equivalent. + * Switch class that checks if subtrees of the AST are semantically equivalent to each other. Return + * {@code false} if they are not equivalent; return {@code true} or {@code false} (but preferably + * {@code true}) if they are equivalent. */ public class IsEqual extends LfSwitch { - private final EObject otherObject; - - public IsEqual(EObject other) { - this.otherObject = other; - } - - @Override - public Boolean doSwitch(EObject eObject) { - if (otherObject == eObject) return true; - if (eObject == null) return false; - return super.doSwitch(eObject); - } - - @Override - public Boolean caseModel(Model object) { - return new ComparisonMachine<>(object, Model.class) - .equivalent(Model::getTarget) - .listsEquivalent(Model::getImports) - .listsEquivalent(Model::getPreambles) - .listsEquivalent(Model::getReactors).conclusion; - } - - @Override - public Boolean caseImport(Import object) { - return new ComparisonMachine<>(object, Import.class) - .equalAsObjects(Import::getImportURI) - .listsEquivalent(Import::getReactorClasses).conclusion; - } - - @Override - public Boolean caseReactorDecl(ReactorDecl object) { - return new ComparisonMachine<>(object, ReactorDecl.class) - .equalAsObjects(ReactorDecl::getName) - .conclusion; - } - - @Override - public Boolean caseImportedReactor(ImportedReactor object) { - return new ComparisonMachine<>(object, ImportedReactor.class) - .equalAsObjects(ImportedReactor::getName) - .equivalent(ImportedReactor::getReactorClass) - .conclusion; - } - - @Override - public Boolean caseReactor(Reactor object) { - return new ComparisonMachine<>(object, Reactor.class) - .listsEquivalent(Reactor::getAttributes) - .equalAsObjects(Reactor::isFederated) - .equalAsObjects(Reactor::isRealtime) - .equalAsObjects(Reactor::isMain) - .equalAsObjects(Reactor::getName) - .listsEquivalent(Reactor::getTypeParms) - .listsEquivalent(Reactor::getParameters) - .equivalent(Reactor::getHost) - .listsEquivalent(Reactor::getSuperClasses) - .listsEquivalent(Reactor::getPreambles) - .listsEquivalent(Reactor::getInputs) - .listsEquivalent(Reactor::getOutputs) - .listsEquivalent(Reactor::getTimers) - .listsEquivalent(Reactor::getActions) - .listsEquivalent(Reactor::getInstantiations) - .listsEquivalent(Reactor::getConnections) - .listsEquivalent(Reactor::getStateVars) - .listsEquivalent(Reactor::getReactions) - .listsEquivalent(Reactor::getMethods) - .listsEquivalent(Reactor::getModes) - .conclusion; - } - - @Override - public Boolean caseTypeParm(TypeParm object) { - return new ComparisonMachine<>(object, TypeParm.class) - .equalAsObjects(TypeParm::getLiteral) - .equivalent(TypeParm::getCode) - .conclusion; - } - - @Override - public Boolean caseTargetDecl(TargetDecl object) { - return new ComparisonMachine<>(object, TargetDecl.class) - .equalAsObjects(TargetDecl::getName) - .equivalentModulo( - TargetDecl::getConfig, - (KeyValuePairs it) -> it != null && it.getPairs().isEmpty() ? null : it - ) - .conclusion; - } - - @Override - public Boolean caseStateVar(StateVar object) { - return new ComparisonMachine<>(object, StateVar.class) - .listsEquivalent(StateVar::getAttributes) - .equalAsObjects(StateVar::getName) - .equivalent(StateVar::getType) - .equivalent(StateVar::getInit) - .conclusion; - } - - @Override - public Boolean caseInitializer(Initializer object) { - // Empty braces are not equivalent to no init. - return new ComparisonMachine<>(object, Initializer.class) - .equalAsObjects(Initializer::isBraces) - // An initializer with no parens is equivalent to one with parens, - // if the list has a single element. This is probably going to change - // when we introduce equals initializers. - // .equalAsObjects(Initializer::isParens) - .listsEquivalent(Initializer::getExprs) - .conclusion; - } - - @Override - public Boolean caseMethod(Method object) { - return new ComparisonMachine<>(object, Method.class) - .equalAsObjects(Method::isConst) - .equalAsObjects(Method::getName) - .listsEquivalent(Method::getArguments) - .equivalent(Method::getReturn) - .equivalent(Method::getCode) - .conclusion; - } - - @Override - public Boolean caseMethodArgument(MethodArgument object) { - return new ComparisonMachine<>(object, MethodArgument.class) - .equalAsObjects(MethodArgument::getName) - .equivalent(MethodArgument::getType) - .conclusion; - } - - @Override - public Boolean caseInput(Input object) { - return new ComparisonMachine<>(object, Input.class) - .listsEquivalent(Input::getAttributes) - .equalAsObjects(Input::isMutable) - .equivalent(Input::getWidthSpec) - .equivalent(Input::getType) - .conclusion; - } - - @Override - public Boolean caseOutput(Output object) { - return new ComparisonMachine<>(object, Output.class) - .listsEquivalent(Output::getAttributes) - .equivalent(Output::getWidthSpec) - .equalAsObjects(Output::getName) - .equivalent(Output::getType) - .conclusion; - } - - @Override - public Boolean caseTimer(Timer object) { - return new ComparisonMachine<>(object, Timer.class) - .listsEquivalent(Timer::getAttributes) - .equalAsObjects(Timer::getName) - .equivalent(Timer::getOffset) - .equivalent(Timer::getPeriod) - .conclusion; - } - - @Override - public Boolean caseMode(Mode object) { - return new ComparisonMachine<>(object, Mode.class) - .equalAsObjects(Mode::isInitial) - .equalAsObjects(Mode::getName) - .listsEquivalent(Mode::getStateVars) - .listsEquivalent(Mode::getTimers) - .listsEquivalent(Mode::getActions) - .listsEquivalent(Mode::getInstantiations) - .listsEquivalent(Mode::getConnections) - .listsEquivalent(Mode::getReactions) - .conclusion; - } - - @Override - public Boolean caseAction(Action object) { - return new ComparisonMachine<>(object, Action.class) - .listsEquivalent(Action::getAttributes) - .equalAsObjects(Action::getOrigin) // This is an enum - .equalAsObjects(Action::getName) - .equivalent(Action::getMinDelay) - .equivalent(Action::getMinSpacing) - .equalAsObjects(Action::getPolicy) - .equivalent(Action::getType) - .conclusion; - } - - @Override - public Boolean caseAttribute(Attribute object) { - return new ComparisonMachine<>(object, Attribute.class) - .equalAsObjects(Attribute::getAttrName) - .listsEquivalent(Attribute::getAttrParms) - .conclusion; - } - - @Override - public Boolean caseAttrParm(AttrParm object) { - return new ComparisonMachine<>(object, AttrParm.class) - .equalAsObjects(AttrParm::getName) - .equalAsObjects(AttrParm::getValue) - .conclusion; - } - - @Override - public Boolean caseReaction(Reaction object) { - return new ComparisonMachine<>(object, Reaction.class) - .listsEquivalent(Reaction::getAttributes) - .listsEquivalent(Reaction::getTriggers) - .listsEquivalent(Reaction::getSources) - .listsEquivalent(Reaction::getEffects) - .equalAsObjects(Reaction::isMutation) - .equalAsObjects(Reaction::getName) - .equivalent(Reaction::getCode) - .equivalent(Reaction::getStp) - .equivalent(Reaction::getDeadline) - .conclusion; - } - - @Override - public Boolean caseTriggerRef(TriggerRef object) { - throw thereIsAMoreSpecificCase(TriggerRef.class, BuiltinTriggerRef.class, VarRef.class); - } - - @Override - public Boolean caseBuiltinTriggerRef(BuiltinTriggerRef object) { - return new ComparisonMachine<>(object, BuiltinTriggerRef.class) - .equalAsObjects(BuiltinTriggerRef::getType) // This is an enum - .conclusion; - } - - @Override - public Boolean caseDeadline(Deadline object) { - return new ComparisonMachine<>(object, Deadline.class) - .equivalent(Deadline::getDelay) - .equivalent(Deadline::getCode) - .conclusion; - } - - @Override - public Boolean caseSTP(STP object) { - return new ComparisonMachine<>(object, STP.class) - .equivalent(STP::getValue) - .equivalent(STP::getCode) - .conclusion; - } - - - @Override - public Boolean casePreamble(Preamble object) { - return new ComparisonMachine<>(object, Preamble.class) - .equalAsObjects(Preamble::getVisibility) // This is an enum - .equivalent(Preamble::getCode) - .conclusion; - } - - @Override - public Boolean caseInstantiation(Instantiation object) { - return new ComparisonMachine<>(object, Instantiation.class) - .equalAsObjects(Instantiation::getName) - .equivalent(Instantiation::getWidthSpec) - .equivalent(Instantiation::getReactorClass) - .listsEquivalent(Instantiation::getTypeArgs) - .listsEquivalent(Instantiation::getParameters) - .equivalent(Instantiation::getHost) - .conclusion; - } - - @Override - public Boolean caseConnection(Connection object) { - return new ComparisonMachine<>(object, Connection.class) - .listsEquivalent(Connection::getLeftPorts) - .equalAsObjects(Connection::isIterated) - .equalAsObjects(Connection::isPhysical) - .listsEquivalent(Connection::getRightPorts) - .equivalent(Connection::getDelay) - .equivalent(Connection::getSerializer) - .conclusion; - } - - @Override - public Boolean caseSerializer(Serializer object) { - return new ComparisonMachine<>(object, Serializer.class) - .equalAsObjects(Serializer::getType) - .conclusion; - } - - @Override - public Boolean caseKeyValuePairs(KeyValuePairs object) { - return new ComparisonMachine<>(object, KeyValuePairs.class) - .listsEquivalent(KeyValuePairs::getPairs) - .conclusion; - } - - @Override - public Boolean caseKeyValuePair(KeyValuePair object) { - return new ComparisonMachine<>(object, KeyValuePair.class) - .equalAsObjects(KeyValuePair::getName) - .equivalent(KeyValuePair::getValue) - .conclusion; - } - - @Override - public Boolean caseArray(Array object) { - return new ComparisonMachine<>(object, Array.class) - .listsEquivalent(Array::getElements) - .conclusion; - } - - @Override - public Boolean caseElement(Element object) { - return new ComparisonMachine<>(object, Element.class) - .equivalent(Element::getKeyvalue) - .equivalent(Element::getArray) - .equalAsObjects(Element::getLiteral) - .equalAsObjects(Element::getId) - .equalAsObjects(Element::getUnit) - .conclusion; - } - - @Override - public Boolean caseTypedVariable(TypedVariable object) { - throw thereIsAMoreSpecificCase(TypedVariable.class, Port.class, Action.class); - } - - @Override - public Boolean caseVariable(Variable object) { - throw thereIsAMoreSpecificCase(Variable.class, TypedVariable.class, Timer.class, Mode.class); - } - - @Override - public Boolean caseVarRef(VarRef object) { - return new ComparisonMachine<>(object, VarRef.class) - .equalAsObjects(varRef -> varRef.getVariable() instanceof Mode ? null : varRef.getVariable().getName()) - .equivalent(varRef -> varRef.getVariable() instanceof Mode ? null : varRef.getVariable()) - .equalAsObjects(varRef -> varRef.getContainer() == null ? null : varRef.getContainer().getName()) - .equalAsObjects(VarRef::isInterleaved) - .equalAsObjects(VarRef::getTransition) - .conclusion; - } - - @Override - public Boolean caseAssignment(Assignment object) { - return new ComparisonMachine<>(object, Assignment.class) - .equivalent(Assignment::getLhs) - .equivalent(Assignment::getRhs) - .conclusion; - } - - @Override - public Boolean caseParameter(Parameter object) { - return new ComparisonMachine<>(object, Parameter.class) - .listsEquivalent(Parameter::getAttributes) - .equalAsObjects(Parameter::getName) - .equivalent(Parameter::getType) - .equivalent(Parameter::getInit) - .conclusion; - } - - @Override - public Boolean caseExpression(Expression object) { - throw thereIsAMoreSpecificCase( - Expression.class, - Literal.class, - Time.class, - ParameterReference.class, - Code.class, - BracedListExpression.class - ); - } - - @Override - public Boolean caseBracedListExpression(BracedListExpression object) { - return new ComparisonMachine<>(object, BracedListExpression.class) - .listsEquivalent(BracedListExpression::getItems) - .conclusion; - } - - @Override - public Boolean caseParameterReference(ParameterReference object) { - return new ComparisonMachine<>(object, ParameterReference.class) - .equivalent(ParameterReference::getParameter) - .conclusion; - } - - @Override - public Boolean caseTime(Time object) { - return new ComparisonMachine<>(object, Time.class) - .equalAsObjects(Time::getInterval) - .equalAsObjectsModulo( - Time::getUnit, - ((Function) TimeUnit::getCanonicalName) - .compose(TimeUnit::fromName) - ) - .conclusion; - } - - @Override - public Boolean casePort(Port object) { - throw thereIsAMoreSpecificCase(Port.class, Input.class, Output.class); - } - - @Override - public Boolean caseType(Type object) { - return new ComparisonMachine<>(object, Type.class) - .equivalent(Type::getCode) - .equalAsObjects(Type::isTime) - .equivalent(Type::getArraySpec) - .equalAsObjects(Type::getId) - .listsEquivalent(Type::getTypeArgs) - .listsEqualAsObjects(Type::getStars) - .equivalent(Type::getArraySpec) - .equivalent(Type::getCode) - .conclusion; - } - - @Override - public Boolean caseArraySpec(ArraySpec object) { - return new ComparisonMachine<>(object, ArraySpec.class) - .equalAsObjects(ArraySpec::isOfVariableLength) - .equalAsObjects(ArraySpec::getLength) - .conclusion; - } - - @Override - public Boolean caseWidthSpec(WidthSpec object) { - return new ComparisonMachine<>(object, WidthSpec.class) - .equalAsObjects(WidthSpec::isOfVariableLength) - .listsEquivalent(WidthSpec::getTerms) - .conclusion; - } - - @Override - public Boolean caseWidthTerm(WidthTerm object) { - return new ComparisonMachine<>(object, WidthTerm.class) - .equalAsObjects(WidthTerm::getWidth) - .equivalent(WidthTerm::getParameter) - .equivalent(WidthTerm::getPort) - .equivalent(WidthTerm::getCode) - .conclusion; - } - - @Override - public Boolean caseIPV4Host(IPV4Host object) { - return caseHost(object); - } - - @Override - public Boolean caseIPV6Host(IPV6Host object) { - return caseHost(object); - } - - @Override - public Boolean caseNamedHost(NamedHost object) { - return caseHost(object); - } - - @Override - public Boolean caseHost(Host object) { - return new ComparisonMachine<>(object, Host.class) - .equalAsObjects(Host::getUser) - .equalAsObjects(Host::getAddr) - .equalAsObjects(Host::getPort) - .conclusion; - } - - @Override - public Boolean caseCodeExpr(CodeExpr object) { - return new ComparisonMachine<>(object, CodeExpr.class).equivalent(CodeExpr::getCode).conclusion; - } - - @Override - public Boolean caseCode(Code object) { - return new ComparisonMachine<>(object, Code.class) - .equalAsObjectsModulo( - Code::getBody, - s -> s == null ? null : s.strip().stripIndent() - ) - .conclusion; - } - - @Override - public Boolean caseLiteral(Literal object) { - return new ComparisonMachine<>(object, Literal.class) - .equalAsObjects(Literal::getLiteral) - .conclusion; - } - - @Override - public Boolean defaultCase(EObject object) { - return super.defaultCase(object); - } - - @SafeVarargs - private UnsupportedOperationException thereIsAMoreSpecificCase( - Class thisCase, - Class... moreSpecificCases - ) { - return new UnsupportedOperationException(String.format( + private final EObject otherObject; + + public IsEqual(EObject other) { + this.otherObject = other; + } + + @Override + public Boolean doSwitch(EObject eObject) { + if (otherObject == eObject) return true; + if (eObject == null) return false; + return super.doSwitch(eObject); + } + + @Override + public Boolean caseModel(Model object) { + return new ComparisonMachine<>(object, Model.class) + .equivalent(Model::getTarget) + .listsEquivalent(Model::getImports) + .listsEquivalent(Model::getPreambles) + .listsEquivalent(Model::getReactors) + .conclusion; + } + + @Override + public Boolean caseImport(Import object) { + return new ComparisonMachine<>(object, Import.class) + .equalAsObjects(Import::getImportURI) + .listsEquivalent(Import::getReactorClasses) + .conclusion; + } + + @Override + public Boolean caseReactorDecl(ReactorDecl object) { + return new ComparisonMachine<>(object, ReactorDecl.class) + .equalAsObjects(ReactorDecl::getName) + .conclusion; + } + + @Override + public Boolean caseImportedReactor(ImportedReactor object) { + return new ComparisonMachine<>(object, ImportedReactor.class) + .equalAsObjects(ImportedReactor::getName) + .equivalent(ImportedReactor::getReactorClass) + .conclusion; + } + + @Override + public Boolean caseReactor(Reactor object) { + return new ComparisonMachine<>(object, Reactor.class) + .listsEquivalent(Reactor::getAttributes) + .equalAsObjects(Reactor::isFederated) + .equalAsObjects(Reactor::isRealtime) + .equalAsObjects(Reactor::isMain) + .equalAsObjects(Reactor::getName) + .listsEquivalent(Reactor::getTypeParms) + .listsEquivalent(Reactor::getParameters) + .equivalent(Reactor::getHost) + .listsEquivalent(Reactor::getSuperClasses) + .listsEquivalent(Reactor::getPreambles) + .listsEquivalent(Reactor::getInputs) + .listsEquivalent(Reactor::getOutputs) + .listsEquivalent(Reactor::getTimers) + .listsEquivalent(Reactor::getActions) + .listsEquivalent(Reactor::getInstantiations) + .listsEquivalent(Reactor::getConnections) + .listsEquivalent(Reactor::getStateVars) + .listsEquivalent(Reactor::getReactions) + .listsEquivalent(Reactor::getMethods) + .listsEquivalent(Reactor::getModes) + .conclusion; + } + + @Override + public Boolean caseTypeParm(TypeParm object) { + return new ComparisonMachine<>(object, TypeParm.class) + .equalAsObjects(TypeParm::getLiteral) + .equivalent(TypeParm::getCode) + .conclusion; + } + + @Override + public Boolean caseTargetDecl(TargetDecl object) { + return new ComparisonMachine<>(object, TargetDecl.class) + .equalAsObjects(TargetDecl::getName) + .equivalentModulo( + TargetDecl::getConfig, + (KeyValuePairs it) -> it != null && it.getPairs().isEmpty() ? null : it) + .conclusion; + } + + @Override + public Boolean caseStateVar(StateVar object) { + return new ComparisonMachine<>(object, StateVar.class) + .listsEquivalent(StateVar::getAttributes) + .equalAsObjects(StateVar::getName) + .equivalent(StateVar::getType) + .equivalent(StateVar::getInit) + .conclusion; + } + + @Override + public Boolean caseInitializer(Initializer object) { + // Empty braces are not equivalent to no init. + return new ComparisonMachine<>(object, Initializer.class) + .equalAsObjects(Initializer::isBraces) + // An initializer with no parens is equivalent to one with parens, + // if the list has a single element. This is probably going to change + // when we introduce equals initializers. + // .equalAsObjects(Initializer::isParens) + .listsEquivalent(Initializer::getExprs) + .conclusion; + } + + @Override + public Boolean caseMethod(Method object) { + return new ComparisonMachine<>(object, Method.class) + .equalAsObjects(Method::isConst) + .equalAsObjects(Method::getName) + .listsEquivalent(Method::getArguments) + .equivalent(Method::getReturn) + .equivalent(Method::getCode) + .conclusion; + } + + @Override + public Boolean caseMethodArgument(MethodArgument object) { + return new ComparisonMachine<>(object, MethodArgument.class) + .equalAsObjects(MethodArgument::getName) + .equivalent(MethodArgument::getType) + .conclusion; + } + + @Override + public Boolean caseInput(Input object) { + return new ComparisonMachine<>(object, Input.class) + .listsEquivalent(Input::getAttributes) + .equalAsObjects(Input::isMutable) + .equivalent(Input::getWidthSpec) + .equivalent(Input::getType) + .conclusion; + } + + @Override + public Boolean caseOutput(Output object) { + return new ComparisonMachine<>(object, Output.class) + .listsEquivalent(Output::getAttributes) + .equivalent(Output::getWidthSpec) + .equalAsObjects(Output::getName) + .equivalent(Output::getType) + .conclusion; + } + + @Override + public Boolean caseTimer(Timer object) { + return new ComparisonMachine<>(object, Timer.class) + .listsEquivalent(Timer::getAttributes) + .equalAsObjects(Timer::getName) + .equivalent(Timer::getOffset) + .equivalent(Timer::getPeriod) + .conclusion; + } + + @Override + public Boolean caseMode(Mode object) { + return new ComparisonMachine<>(object, Mode.class) + .equalAsObjects(Mode::isInitial) + .equalAsObjects(Mode::getName) + .listsEquivalent(Mode::getStateVars) + .listsEquivalent(Mode::getTimers) + .listsEquivalent(Mode::getActions) + .listsEquivalent(Mode::getInstantiations) + .listsEquivalent(Mode::getConnections) + .listsEquivalent(Mode::getReactions) + .conclusion; + } + + @Override + public Boolean caseAction(Action object) { + return new ComparisonMachine<>(object, Action.class) + .listsEquivalent(Action::getAttributes) + .equalAsObjects(Action::getOrigin) // This is an enum + .equalAsObjects(Action::getName) + .equivalent(Action::getMinDelay) + .equivalent(Action::getMinSpacing) + .equalAsObjects(Action::getPolicy) + .equivalent(Action::getType) + .conclusion; + } + + @Override + public Boolean caseAttribute(Attribute object) { + return new ComparisonMachine<>(object, Attribute.class) + .equalAsObjects(Attribute::getAttrName) + .listsEquivalent(Attribute::getAttrParms) + .conclusion; + } + + @Override + public Boolean caseAttrParm(AttrParm object) { + return new ComparisonMachine<>(object, AttrParm.class) + .equalAsObjects(AttrParm::getName) + .equalAsObjects(AttrParm::getValue) + .conclusion; + } + + @Override + public Boolean caseReaction(Reaction object) { + return new ComparisonMachine<>(object, Reaction.class) + .listsEquivalent(Reaction::getAttributes) + .listsEquivalent(Reaction::getTriggers) + .listsEquivalent(Reaction::getSources) + .listsEquivalent(Reaction::getEffects) + .equalAsObjects(Reaction::isMutation) + .equalAsObjects(Reaction::getName) + .equivalent(Reaction::getCode) + .equivalent(Reaction::getStp) + .equivalent(Reaction::getDeadline) + .conclusion; + } + + @Override + public Boolean caseTriggerRef(TriggerRef object) { + throw thereIsAMoreSpecificCase(TriggerRef.class, BuiltinTriggerRef.class, VarRef.class); + } + + @Override + public Boolean caseBuiltinTriggerRef(BuiltinTriggerRef object) { + return new ComparisonMachine<>(object, BuiltinTriggerRef.class) + .equalAsObjects(BuiltinTriggerRef::getType) // This is an enum + .conclusion; + } + + @Override + public Boolean caseDeadline(Deadline object) { + return new ComparisonMachine<>(object, Deadline.class) + .equivalent(Deadline::getDelay) + .equivalent(Deadline::getCode) + .conclusion; + } + + @Override + public Boolean caseSTP(STP object) { + return new ComparisonMachine<>(object, STP.class) + .equivalent(STP::getValue) + .equivalent(STP::getCode) + .conclusion; + } + + @Override + public Boolean casePreamble(Preamble object) { + return new ComparisonMachine<>(object, Preamble.class) + .equalAsObjects(Preamble::getVisibility) // This is an enum + .equivalent(Preamble::getCode) + .conclusion; + } + + @Override + public Boolean caseInstantiation(Instantiation object) { + return new ComparisonMachine<>(object, Instantiation.class) + .equalAsObjects(Instantiation::getName) + .equivalent(Instantiation::getWidthSpec) + .equivalent(Instantiation::getReactorClass) + .listsEquivalent(Instantiation::getTypeArgs) + .listsEquivalent(Instantiation::getParameters) + .equivalent(Instantiation::getHost) + .conclusion; + } + + @Override + public Boolean caseConnection(Connection object) { + return new ComparisonMachine<>(object, Connection.class) + .listsEquivalent(Connection::getLeftPorts) + .equalAsObjects(Connection::isIterated) + .equalAsObjects(Connection::isPhysical) + .listsEquivalent(Connection::getRightPorts) + .equivalent(Connection::getDelay) + .equivalent(Connection::getSerializer) + .conclusion; + } + + @Override + public Boolean caseSerializer(Serializer object) { + return new ComparisonMachine<>(object, Serializer.class) + .equalAsObjects(Serializer::getType) + .conclusion; + } + + @Override + public Boolean caseKeyValuePairs(KeyValuePairs object) { + return new ComparisonMachine<>(object, KeyValuePairs.class) + .listsEquivalent(KeyValuePairs::getPairs) + .conclusion; + } + + @Override + public Boolean caseKeyValuePair(KeyValuePair object) { + return new ComparisonMachine<>(object, KeyValuePair.class) + .equalAsObjects(KeyValuePair::getName) + .equivalent(KeyValuePair::getValue) + .conclusion; + } + + @Override + public Boolean caseArray(Array object) { + return new ComparisonMachine<>(object, Array.class) + .listsEquivalent(Array::getElements) + .conclusion; + } + + @Override + public Boolean caseElement(Element object) { + return new ComparisonMachine<>(object, Element.class) + .equivalent(Element::getKeyvalue) + .equivalent(Element::getArray) + .equalAsObjects(Element::getLiteral) + .equalAsObjects(Element::getId) + .equalAsObjects(Element::getUnit) + .conclusion; + } + + @Override + public Boolean caseTypedVariable(TypedVariable object) { + throw thereIsAMoreSpecificCase(TypedVariable.class, Port.class, Action.class); + } + + @Override + public Boolean caseVariable(Variable object) { + throw thereIsAMoreSpecificCase( + Variable.class, TypedVariable.class, Timer.class, Mode.class, Watchdog.class); + } + + @Override + public Boolean caseVarRef(VarRef object) { + return new ComparisonMachine<>(object, VarRef.class) + .equalAsObjects( + varRef -> varRef.getVariable() instanceof Mode ? null : varRef.getVariable().getName()) + .equivalent(varRef -> varRef.getVariable() instanceof Mode ? null : varRef.getVariable()) + .equalAsObjects( + varRef -> varRef.getContainer() == null ? null : varRef.getContainer().getName()) + .equalAsObjects(VarRef::isInterleaved) + .equalAsObjects(VarRef::getTransition) + .conclusion; + } + + @Override + public Boolean caseAssignment(Assignment object) { + return new ComparisonMachine<>(object, Assignment.class) + .equivalent(Assignment::getLhs) + .equivalent(Assignment::getRhs) + .conclusion; + } + + @Override + public Boolean caseParameter(Parameter object) { + return new ComparisonMachine<>(object, Parameter.class) + .listsEquivalent(Parameter::getAttributes) + .equalAsObjects(Parameter::getName) + .equivalent(Parameter::getType) + .equivalent(Parameter::getInit) + .conclusion; + } + + @Override + public Boolean caseExpression(Expression object) { + throw thereIsAMoreSpecificCase( + Expression.class, + Literal.class, + Time.class, + ParameterReference.class, + Code.class, + BracedListExpression.class); + } + + @Override + public Boolean caseBracedListExpression(BracedListExpression object) { + return new ComparisonMachine<>(object, BracedListExpression.class) + .listsEquivalent(BracedListExpression::getItems) + .conclusion; + } + + @Override + public Boolean caseParameterReference(ParameterReference object) { + return new ComparisonMachine<>(object, ParameterReference.class) + .equivalent(ParameterReference::getParameter) + .conclusion; + } + + @Override + public Boolean caseTime(Time object) { + return new ComparisonMachine<>(object, Time.class) + .equalAsObjects(Time::getInterval) + .equalAsObjectsModulo( + Time::getUnit, + ((Function) TimeUnit::getCanonicalName).compose(TimeUnit::fromName)) + .conclusion; + } + + @Override + public Boolean casePort(Port object) { + throw thereIsAMoreSpecificCase(Port.class, Input.class, Output.class); + } + + @Override + public Boolean caseType(Type object) { + return new ComparisonMachine<>(object, Type.class) + .equivalent(Type::getCode) + .equalAsObjects(Type::isTime) + .equivalent(Type::getArraySpec) + .equalAsObjects(Type::getId) + .listsEquivalent(Type::getTypeArgs) + .listsEqualAsObjects(Type::getStars) + .equivalent(Type::getArraySpec) + .equivalent(Type::getCode) + .conclusion; + } + + @Override + public Boolean caseArraySpec(ArraySpec object) { + return new ComparisonMachine<>(object, ArraySpec.class) + .equalAsObjects(ArraySpec::isOfVariableLength) + .equalAsObjects(ArraySpec::getLength) + .conclusion; + } + + @Override + public Boolean caseWatchdog(Watchdog object) { + return new ComparisonMachine<>(object, Watchdog.class) + .equalAsObjects(Watchdog::getName) + .equivalent(Watchdog::getTimeout) + .listsEquivalent(Watchdog::getEffects) + .equivalent(Watchdog::getCode) + .conclusion; + } + + @Override + public Boolean caseWidthSpec(WidthSpec object) { + return new ComparisonMachine<>(object, WidthSpec.class) + .equalAsObjects(WidthSpec::isOfVariableLength) + .listsEquivalent(WidthSpec::getTerms) + .conclusion; + } + + @Override + public Boolean caseWidthTerm(WidthTerm object) { + return new ComparisonMachine<>(object, WidthTerm.class) + .equalAsObjects(WidthTerm::getWidth) + .equivalent(WidthTerm::getParameter) + .equivalent(WidthTerm::getPort) + .equivalent(WidthTerm::getCode) + .conclusion; + } + + @Override + public Boolean caseIPV4Host(IPV4Host object) { + return caseHost(object); + } + + @Override + public Boolean caseIPV6Host(IPV6Host object) { + return caseHost(object); + } + + @Override + public Boolean caseNamedHost(NamedHost object) { + return caseHost(object); + } + + @Override + public Boolean caseHost(Host object) { + return new ComparisonMachine<>(object, Host.class) + .equalAsObjects(Host::getUser) + .equalAsObjects(Host::getAddr) + .equalAsObjects(Host::getPort) + .conclusion; + } + + @Override + public Boolean caseCodeExpr(CodeExpr object) { + return new ComparisonMachine<>(object, CodeExpr.class).equivalent(CodeExpr::getCode).conclusion; + } + + @Override + public Boolean caseCode(Code object) { + return new ComparisonMachine<>(object, Code.class) + .equalAsObjectsModulo(Code::getBody, s -> s == null ? null : s.strip().stripIndent()) + .conclusion; + } + + @Override + public Boolean caseLiteral(Literal object) { + return new ComparisonMachine<>(object, Literal.class) + .equalAsObjects(Literal::getLiteral) + .conclusion; + } + + @Override + public Boolean defaultCase(EObject object) { + return super.defaultCase(object); + } + + @SafeVarargs + private UnsupportedOperationException thereIsAMoreSpecificCase( + Class thisCase, Class... moreSpecificCases) { + return new UnsupportedOperationException( + String.format( "%ss are %s, so the methods " + "corresponding to those types should be invoked instead.", thisCase.getName(), Arrays.stream(moreSpecificCases) - .map(Class::getName) - .map(it -> it + (it.endsWith("s") ? "es" : "s")) - .collect(Collectors.joining(" or ")) - )); - } - - /** Fluently compare a pair of parse tree nodes for equivalence. */ - private class ComparisonMachine { - private final E object; - private final E other; - private boolean conclusion; - - ComparisonMachine(E object, Class clz) { - this.object = object; - this.conclusion = clz.isInstance(otherObject); - this.other = conclusion ? clz.cast(otherObject) : null; - } - - /** - * Conclude false if the two given Lists are different as EObject - * sequences. Order matters. - */ - ComparisonMachine listsEquivalent(Function> listGetter) { - if (conclusion) conclusion = listsEqualish(listGetter, (T a, T b) -> new IsEqual(a).doSwitch(b)); - return this; - } - - /** - * Conclude false if the two given Lists are different as object - * sequences. Order matters. - */ - ComparisonMachine listsEqualAsObjects(Function> listGetter) { - if (conclusion) conclusion = listsEqualish(listGetter, Objects::equals); - return this; - } - - boolean listsEqualish(Function> listGetter, BiPredicate equalish) { - if (!conclusion) return false; - List list0 = listGetter.apply(object); - List list1 = listGetter.apply(other); - if (list0 == list1) return true; // e.g., they are both null - if (list0.size() != list1.size()) return false; - for (int i = 0; i < list0.size(); i++) { - if (!equalish.test(list0.get(i), list1.get(i))) { - return false; - } - } - return true; - } - - /** Conclude false if the two properties are not equal as objects. */ - ComparisonMachine equalAsObjects(Function propertyGetter) { - return equalAsObjectsModulo(propertyGetter, Function.identity()); - } - - /** - * Conclude false if the two properties are not equal as objects, - * given that {@code projectionToClassRepresentatives} maps each - * object to some semantically equivalent object. - */ - ComparisonMachine equalAsObjectsModulo( - Function propertyGetter, - Function projectionToClassRepresentatives - ) { - if (propertyGetter.apply(object) instanceof EObject) { - throw new IllegalArgumentException( - "EObjects should be compared for semantic equivalence, not object equality." - ); - } - propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); - if (conclusion) conclusion = Objects.equals(propertyGetter.apply(object), propertyGetter.apply(other)); - return this; - } - - /** - * Conclude false if the two properties are not semantically equivalent - * parse nodes. - */ - ComparisonMachine equivalent(Function propertyGetter) { - return equivalentModulo(propertyGetter, Function.identity()); - } - - /** - * Conclude false if the two properties are not semantically equivalent - * parse nodes, given that {@code projectionToClassRepresentatives} - * maps each parse node to some semantically equivalent node. - */ - ComparisonMachine equivalentModulo( - Function propertyGetter, - Function projectionToClassRepresentatives - ) { - propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); - if (conclusion) conclusion = new IsEqual(propertyGetter.apply(object)) - .doSwitch(propertyGetter.apply(other)); - return this; + .map(Class::getName) + .map(it -> it + (it.endsWith("s") ? "es" : "s")) + .collect(Collectors.joining(" or ")))); + } + + /** Fluently compare a pair of parse tree nodes for equivalence. */ + private class ComparisonMachine { + private final E object; + private final E other; + private boolean conclusion; + + ComparisonMachine(E object, Class clz) { + this.object = object; + this.conclusion = clz.isInstance(otherObject); + this.other = conclusion ? clz.cast(otherObject) : null; + } + + /** Conclude false if the two given Lists are different as EObject sequences. Order matters. */ + ComparisonMachine listsEquivalent(Function> listGetter) { + if (conclusion) + conclusion = listsEqualish(listGetter, (T a, T b) -> new IsEqual(a).doSwitch(b)); + return this; + } + + /** Conclude false if the two given Lists are different as object sequences. Order matters. */ + ComparisonMachine listsEqualAsObjects(Function> listGetter) { + if (conclusion) conclusion = listsEqualish(listGetter, Objects::equals); + return this; + } + + boolean listsEqualish( + Function> listGetter, BiPredicate equalish) { + if (!conclusion) return false; + List list0 = listGetter.apply(object); + List list1 = listGetter.apply(other); + if (list0 == list1) return true; // e.g., they are both null + if (list0.size() != list1.size()) return false; + for (int i = 0; i < list0.size(); i++) { + if (!equalish.test(list0.get(i), list1.get(i))) { + return false; } - } + } + return true; + } + + /** Conclude false if the two properties are not equal as objects. */ + ComparisonMachine equalAsObjects(Function propertyGetter) { + return equalAsObjectsModulo(propertyGetter, Function.identity()); + } + + /** + * Conclude false if the two properties are not equal as objects, given that {@code + * projectionToClassRepresentatives} maps each object to some semantically equivalent object. + */ + ComparisonMachine equalAsObjectsModulo( + Function propertyGetter, Function projectionToClassRepresentatives) { + if (propertyGetter.apply(object) instanceof EObject) { + throw new IllegalArgumentException( + "EObjects should be compared for semantic equivalence, not object equality."); + } + propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); + if (conclusion) + conclusion = Objects.equals(propertyGetter.apply(object), propertyGetter.apply(other)); + return this; + } + + /** Conclude false if the two properties are not semantically equivalent parse nodes. */ + ComparisonMachine equivalent(Function propertyGetter) { + return equivalentModulo(propertyGetter, Function.identity()); + } + + /** + * Conclude false if the two properties are not semantically equivalent parse nodes, given that + * {@code projectionToClassRepresentatives} maps each parse node to some semantically equivalent + * node. + */ + ComparisonMachine equivalentModulo( + Function propertyGetter, Function projectionToClassRepresentatives) { + propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); + if (conclusion) + conclusion = + new IsEqual(propertyGetter.apply(object)).doSwitch(propertyGetter.apply(other)); + return this; + } + } } diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 52a0d302f2..ce1cdca9fe 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -76,6 +76,7 @@ import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.Visibility; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.lf.util.LfSwitch; @@ -393,6 +394,7 @@ public MalleableString caseReactor(Reactor object) { // | (outputs+=Output) // | (timers+=Timer) // | (actions+=Action) + // | (watchdogs+=Watchdog) // | (instantiations+=Instantiation) // | (connections+=Connection) // | (reactions+=Reaction) @@ -410,6 +412,7 @@ public MalleableString caseReactor(Reactor object) { object.getOutputs(), object.getTimers(), object.getActions(), + object.getWatchdogs(), object.getInstantiations(), object.getConnections(), object.getStateVars()), @@ -656,6 +659,40 @@ public MalleableString caseDeadline(Deadline object) { return handler(object, "deadline", Deadline::getDelay, Deadline::getCode); } + @Override + public MalleableString caseWatchdog(Watchdog object) { + // 'watchdog' name=ID '(' timeout=Expression ')' + // ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? + // code=Code; + + Builder msb = new Builder(); + msb.append("watchdog "); + msb.append(object.getName()); + msb.append(list(true, object.getTimeout())); + + if (!object.getEffects().isEmpty()) { + List allModes = ASTUtils.allModes(ASTUtils.getEnclosingReactor(object)); + msb.append(" -> ", " ->\n") + .append( + object.getEffects().stream() + .map( + varRef -> + (allModes.stream() + .anyMatch( + m -> m.getName().equals(varRef.getVariable().getName()))) + ? new Builder() + .append(varRef.getTransition()) + .append("(") + .append(doSwitch(varRef)) + .append(")") + .get() + : doSwitch(varRef)) + .collect(new Joiner(", "))); + } + msb.append(" ").append(doSwitch(object.getCode())); + return msb.get(); + } + @Override public MalleableString caseSTP(STP object) { // 'STP' '(' value=Expression ')' code=Code @@ -844,9 +881,8 @@ public MalleableString caseInitializer(Initializer object) { } /** - * Return true if the initializer should be output with an equals initializer. - * Old-style assignments with parentheses are also output that - * way to help with the transition. + * Return true if the initializer should be output with an equals initializer. Old-style + * assignments with parentheses are also output that way to help with the transition. */ private boolean shouldOutputAsAssignment(Initializer init) { return init.isAssign() @@ -882,7 +918,6 @@ private MalleableString initializer(Initializer init) { return list(", ", prefix, suffix, false, false, init.getExprs()); } - @Override public MalleableString caseParameter(Parameter object) { // name=ID (':' (type=Type))? diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index 7ca5a19bca..349ff0fff7 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -1,31 +1,31 @@ -/** Instance of a federate specification. +/** + * Instance of a federate specification. * -Copyright (c) 2020, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - 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 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ - + *

Copyright (c) 2020, The University of California at Berkeley. + * + *

Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + *

1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + *

2. Redistributions in binary form must reproduce the above copyright notice, 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 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ************* + */ package org.lflang.federated.generator; -import java.io.File; +import com.google.common.base.Objects; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -35,22 +35,16 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.eclipse.emf.ecore.EObject; - 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; @@ -71,598 +65,557 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.VarRef; import org.lflang.lf.Variable; -import com.google.common.base.Objects; - - /** - * 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. + * 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. * * @author Edward A. Lee * @author Soroush Bateni */ public class FederateInstance { // why does this not extend ReactorInstance? - /** - * Construct a new instance with the specified instantiation of - * of a top-level reactor. The federate will be given the specified - * integer ID. - * @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 errorReporter The error reporter - */ - public FederateInstance( - Instantiation instantiation, - int id, - int bankIndex, - TargetConfig targetConfig, - ErrorReporter errorReporter) { - this.instantiation = instantiation; - this.id = id; - 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; - } - } + /** + * Construct a new instance with the specified instantiation of of a top-level reactor. The + * federate will be given the specified integer ID. + * + * @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 errorReporter The error reporter + */ + public FederateInstance( + Instantiation instantiation, + int id, + int bankIndex, + TargetConfig targetConfig, + ErrorReporter errorReporter) { + this.instantiation = instantiation; + this.id = id; + 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; + } } - - ///////////////////////////////////////////// - //// 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. - */ - public String host = "localhost"; - - - /** - * The instantiation of the top-level reactor, or null if there is no federation. - */ - public Instantiation instantiation; - public Instantiation getInstantiation() { - return instantiation; + } + + ///////////////////////////////////////////// + //// 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. */ + public String host = "localhost"; + + /** The instantiation of the top-level reactor, or null if there is no federation. */ + public Instantiation instantiation; + + public Instantiation getInstantiation() { + return instantiation; + } + + /** 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 may include null, meaning that there is a connection 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 may may include null, meaning that there is a connection 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. */ + public int id = 0; + + /** + * 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"; + + /** + * List of networkMessage actions. Each of these handles a message received from another federate + * over the network. The ID of receiving port is simply the position of the action in the list. + * 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 physical connections to federate B. + * The message handler on federate A will be responsible for including the appropriate information + * in the message header (such as port ID) 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 physical connections to + * federate B. The message handler on federate B will be responsible for distinguishing the + * incoming messages by parsing their header and 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 reactions) that belong to this federate instance. + */ + public List networkReactions = new ArrayList<>(); + + /** Parsed target config of the federate. */ + public TargetConfig targetConfig; + + /** Keep a unique list of enabled serializers */ + public HashSet enabledSerializers = new HashSet<>(); + + /** + * Return true if the specified EObject should be included in the code generated for this + * federate. + * + * @param object An {@code EObject} + * @return True if this federate contains the EObject. + */ + 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."); + } + + /** + * Return true if the specified reactor belongs to this federate. + * + * @param instantiation The instantiation to look inside + * @param reactor The reactor declaration to find + */ + private boolean contains(Instantiation instantiation, ReactorDecl reactor) { + if (instantiation.getReactorClass().equals(ASTUtils.toDefinition(reactor))) { + return true; } - /** - * 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 - * may include null, meaning that there is a connection - * 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 - * may may include null, meaning that there is a connection - * 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. - */ - public int id = 0; - - - /** - * 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"; - - /** - * List of networkMessage actions. Each of these handles a message - * received from another federate over the network. The ID of - * receiving port is simply the position of the action in the list. - * 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 - * physical connections to federate B. The message handler on federate A will be - * responsible for including the appropriate information in the message header (such as port ID) - * 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 - * physical connections to federate B. The message handler on federate B will be - * responsible for distinguishing the incoming messages by parsing their header and - * 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 - * reactions) that belong to this federate instance. - */ - public List networkReactions = new ArrayList<>(); - - /** - * Parsed target config of the federate. - */ - public TargetConfig targetConfig; - - /** - * Keep a unique list of enabled serializers - */ - public HashSet enabledSerializers = new HashSet<>(); - - /** - * Return true if the specified EObject should be included in the code - * generated for this federate. - * - * @param object An {@code EObject} - * @return True if this federate contains the EObject. - */ - 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."); + boolean instantiationsCheck = false; + // For a federate, we don't need to look inside imported reactors. + if (instantiation.getReactorClass() instanceof Reactor reactorDef) { + for (Instantiation child : reactorDef.getInstantiations()) { + instantiationsCheck |= contains(child, reactor); + } } - /** - * Return true if the specified reactor belongs to this federate. - * @param instantiation The instantiation to look inside - * @param reactor The reactor declaration to find - */ - private boolean contains( - Instantiation instantiation, - ReactorDecl reactor - ) { - if (instantiation.getReactorClass().equals(ASTUtils.toDefinition(reactor))) { + return instantiationsCheck; + } + + /** + * Return true if the specified import should be included in the code generated for this federate. + * + * @param imp The import + */ + private boolean contains(Import imp) { + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (contains(reactor)) { + return true; + } + } + return false; + } + + /** + * Return true if the specified parameter should be included in the code generated for this + * federate. + * + * @param param The parameter + */ + private boolean contains(Parameter param) { + boolean returnValue = false; + // Check if param is referenced in this federate's instantiation + returnValue = + instantiation.getParameters().stream() + .anyMatch( + assignment -> + assignment.getRhs().getExprs().stream() + .filter(it -> it instanceof ParameterReference) + .map(it -> ((ParameterReference) it).getParameter()) + .toList() + .contains(param)); + // If there are any user-defined top-level reactions, they could access + // the parameters, so we need to include the parameter. + var topLevelUserDefinedReactions = + ((Reactor) instantiation.eContainer()) + .getReactions().stream() + .filter(r -> !networkReactions.contains(r) && contains(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. + * + * @param action The action + * @return True if this federate contains the action. + */ + private boolean contains(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)) { + // Look in triggers + for (TriggerRef trigger : convertToEmptyListIfNull(react.getTriggers())) { + if (trigger instanceof VarRef triggerAsVarRef) { + if (Objects.equal(triggerAsVarRef.getVariable(), action)) { + return true; + } + } + } + // Look in sources + for (VarRef source : convertToEmptyListIfNull(react.getSources())) { + if (Objects.equal(source.getVariable(), action)) { return true; + } } - - boolean instantiationsCheck = false; - // For a federate, we don't need to look inside imported reactors. - if (instantiation.getReactorClass() instanceof Reactor reactorDef) { - for (Instantiation child : reactorDef.getInstantiations()) { - instantiationsCheck |= contains(child, reactor); - } + // Look in effects + for (VarRef effect : convertToEmptyListIfNull(react.getEffects())) { + if (Objects.equal(effect.getVariable(), action)) { + return true; + } } + } + } - return instantiationsCheck; + 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 not at the top level, it returns true. + * + * @param reaction The reaction. + */ + private boolean contains(Reaction reaction) { + Reactor reactor = ASTUtils.getEnclosingReactor(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; } - /** - * Return true if the specified import should be included in the code generated for this federate. - * @param imp The import - */ - private boolean contains(Import imp) { - for (ImportedReactor reactor : imp.getReactorClasses()) { - if (contains(reactor)) { - return true; - } - } - return false; + int reactionBankIndex = FedASTUtils.getReactionBankIndex(reaction); + if (reactionBankIndex >= 0 && this.bankIndex >= 0 && reactionBankIndex != this.bankIndex) { + return false; } - /** - * Return true if the specified parameter should be included in the code generated for this federate. - * @param param The parameter - */ - private boolean contains(Parameter param) { - boolean returnValue = false; - // Check if param is referenced in this federate's instantiation - returnValue = instantiation.getParameters().stream().anyMatch( - assignment -> assignment.getRhs() - .getExprs() - .stream() - .filter( - it -> it instanceof ParameterReference - ) - .map(it -> ((ParameterReference) it).getParameter()) - .toList() - .contains(param) - ); - // If there are any user-defined top-level reactions, they could access - // the parameters, so we need to include the parameter. - var topLevelUserDefinedReactions = ((Reactor) instantiation.eContainer()) - .getReactions().stream().filter( - r -> !networkReactions.contains(r) && contains(r) - ).collect(Collectors.toCollection(ArrayList::new)); - returnValue |= !topLevelUserDefinedReactions.isEmpty(); - return returnValue; + // If this has been called before, then the result of the + // following check is cached. + if (excludeReactions != null) { + return !excludeReactions.contains(reaction); } - /** - * 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. - * - * @param action The action - * @return True if this federate contains the action. - */ - private boolean contains(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)) { - // Look in triggers - for (TriggerRef trigger : convertToEmptyListIfNull(react.getTriggers())) { - if (trigger instanceof VarRef triggerAsVarRef) { - if (Objects.equal(triggerAsVarRef.getVariable(), action)) { - return true; - } - } - } - // Look in sources - for (VarRef source : convertToEmptyListIfNull(react.getSources())) { - if (Objects.equal(source.getVariable(), action)) { - return true; - } - } - // Look in effects - for (VarRef effect : convertToEmptyListIfNull(react.getEffects())) { - if (Objects.equal(effect.getVariable(), action)) { - return true; - } - } + indexExcludedTopLevelReactions(reactor); + + return !excludeReactions.contains(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 True if this federate contains the action in the specified reactor + */ + private boolean contains(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)) { + // Look in triggers + for (TriggerRef trigger : convertToEmptyListIfNull(r.getTriggers())) { + if (trigger instanceof VarRef triggerAsVarRef) { + if (Objects.equal(triggerAsVarRef.getVariable(), timer)) { + return true; } + } } - - 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 - * not at the top level, it returns true. - * - * @param reaction The reaction. - */ - private boolean contains(Reaction reaction) { - Reactor reactor = ASTUtils.getEnclosingReactor(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; - } - - int reactionBankIndex = FedASTUtils.getReactionBankIndex(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); + 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 + */ + public boolean contains(ReactorInstance instance) { + if (instance.getParent() == null) { + return true; // Top-level reactor } - - /** - * 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 True if this federate contains the action in the specified reactor - */ - private boolean contains(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)) { - // Look in triggers - for (TriggerRef trigger : convertToEmptyListIfNull(r.getTriggers())) { - if (trigger instanceof VarRef triggerAsVarRef) { - if (Objects.equal(triggerAsVarRef.getVariable(), timer)) { - return true; - } - } - } - } - } - - return false; + // Start with this instance, then check its parents. + ReactorInstance i = instance; + while (i != null) { + if (i.getDefinition() == instantiation) { + return true; + } + i = i.getParent(); } - - /** - * 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 - */ - public boolean contains(ReactorInstance instance) { - if (instance.getParent() == null) { - return true; // Top-level reactor - } - // Start with this instance, then check its parents. - ReactorInstance i = instance; - while (i != null) { - if (i.getDefinition() == instantiation) { - return true; - } - i = i.getParent(); - } - return false; + return false; + } + + /** + * Build an index of reactions at the top-level (in the 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) { + boolean inFederate = false; + if (excludeReactions != null) { + throw new IllegalStateException( + "The index for excluded reactions at the top level is already built."); } - /** - * Build an index of reactions at the top-level (in the - * 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) { - boolean inFederate = false; - if (excludeReactions != null) { - throw new IllegalStateException("The index for excluded reactions at the top level is already built."); - } - - excludeReactions = new LinkedHashSet<>(); - - // Construct the set of excluded reactions for this federate. - // If a reaction is a network reaction that belongs to this federate, we - // don't need to perform this analysis. - Iterable reactions = ASTUtils.allReactions(federatedReactor).stream().filter(it -> !networkReactions.contains(it)).collect(Collectors.toList()); - for (Reaction react : reactions) { - // Create a collection of all the VarRefs (i.e., triggers, sources, and effects) in the react - // signature that are ports that reference federates. - // We then later check that all these VarRefs reference this federate. If not, we will add this - // react to the list of reactions that have to be excluded (note that mixing VarRefs from - // different federates is not allowed). - List allVarRefsReferencingFederates = new ArrayList<>(); - // Add all the triggers that are outputs - Stream triggersAsVarRef = react.getTriggers().stream().filter(it -> it instanceof VarRef).map(it -> (VarRef) it); - allVarRefsReferencingFederates.addAll( - triggersAsVarRef.filter(it -> it.getVariable() instanceof Output).toList() - ); - // Add all the sources that are outputs - allVarRefsReferencingFederates.addAll( - react.getSources().stream().filter(it -> it.getVariable() instanceof Output).toList() - ); - // Add all the effects that are inputs - allVarRefsReferencingFederates.addAll( - react.getEffects().stream().filter(it -> it.getVariable() instanceof Input).toList() - ); - inFederate = containsAllVarRefs(allVarRefsReferencingFederates); - if (!inFederate) { - excludeReactions.add(react); - } + excludeReactions = new LinkedHashSet<>(); + + // Construct the set of excluded reactions for this federate. + // If a reaction is a network reaction that belongs to this federate, we + // don't need to perform this analysis. + Iterable reactions = + ASTUtils.allReactions(federatedReactor).stream() + .filter(it -> !networkReactions.contains(it)) + .collect(Collectors.toList()); + for (Reaction react : reactions) { + // Create a collection of all the VarRefs (i.e., triggers, sources, and effects) in the react + // signature that are ports that reference federates. + // We then later check that all these VarRefs reference this federate. If not, we will add + // this + // react to the list of reactions that have to be excluded (note that mixing VarRefs from + // different federates is not allowed). + List allVarRefsReferencingFederates = new ArrayList<>(); + // Add all the triggers that are outputs + Stream triggersAsVarRef = + react.getTriggers().stream().filter(it -> it instanceof VarRef).map(it -> (VarRef) it); + allVarRefsReferencingFederates.addAll( + triggersAsVarRef.filter(it -> it.getVariable() instanceof Output).toList()); + // Add all the sources that are outputs + allVarRefsReferencingFederates.addAll( + react.getSources().stream().filter(it -> it.getVariable() instanceof Output).toList()); + // Add all the effects that are inputs + allVarRefsReferencingFederates.addAll( + react.getEffects().stream().filter(it -> it.getVariable() instanceof Input).toList()); + inFederate = containsAllVarRefs(allVarRefsReferencingFederates); + if (!inFederate) { + excludeReactions.add(react); + } + } + } + + /** + * Return true if all members of 'varRefs' belong to this federate. + * + *

As a convenience measure, if some members of 'varRefs' are from different federates, also + * report an error. + * + * @param varRefs A collection of VarRefs + */ + private boolean containsAllVarRefs(Iterable varRefs) { + var referencesFederate = false; + var inFederate = true; + for (VarRef varRef : varRefs) { + if (varRef.getContainer() == this.instantiation) { + referencesFederate = true; + } else { + if (referencesFederate) { + errorReporter.reportError( + varRef, + "Mixed triggers and effects from" + " different federates. This is not permitted"); } + inFederate = false; + } } - - /** - * Return true if all members of 'varRefs' belong to this federate. - * - * As a convenience measure, if some members of 'varRefs' are from - * different federates, also report an error. - * - * @param varRefs A collection of VarRefs - */ - private boolean containsAllVarRefs(Iterable varRefs) { - var referencesFederate = false; - var inFederate = true; - for (VarRef varRef : varRefs) { - if (varRef.getContainer() == this.instantiation) { - referencesFederate = true; - } else { - if (referencesFederate) { - errorReporter.reportError(varRef, - "Mixed triggers and effects from" - + - " different federates. This is not permitted"); - } - inFederate = false; - } + 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 from the nearest physical action. + * + * @param instance The reactor instance containing the output ports + * @return A LinkedHashMap + */ + public LinkedHashMap findOutputsConnectedToPhysicalActions( + ReactorInstance instance) { + LinkedHashMap physicalActionToOutputMinDelay = new LinkedHashMap<>(); + // Find reactions that write to the output port of the reactor + for (PortInstance output : instance.outputs) { + for (ReactionInstance reaction : output.getDependsOnReactions()) { + TimeValue minDelay = findNearestPhysicalActionTrigger(reaction); + if (!Objects.equal(minDelay, TimeValue.MAX_VALUE)) { + physicalActionToOutputMinDelay.put((Output) output.getDefinition(), minDelay); } - 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 - * from the nearest physical action. - * @param instance The reactor instance containing the output ports - * @return A LinkedHashMap - */ - public LinkedHashMap findOutputsConnectedToPhysicalActions(ReactorInstance instance) { - LinkedHashMap physicalActionToOutputMinDelay = new LinkedHashMap<>(); - // Find reactions that write to the output port of the reactor - for (PortInstance output : instance.outputs) { - for (ReactionInstance reaction : output.getDependsOnReactions()) { - TimeValue minDelay = findNearestPhysicalActionTrigger(reaction); - if (!Objects.equal(minDelay, TimeValue.MAX_VALUE)) { - physicalActionToOutputMinDelay.put((Output) output.getDefinition(), minDelay); - } + 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 " + + id + + ": " + + ((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 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 + */ + public TimeValue findNearestPhysicalActionTrigger(ReactionInstance reaction) { + TimeValue minDelay = TimeValue.MAX_VALUE; + for (TriggerInstance trigger : reaction.triggers) { + if (trigger.getDefinition() instanceof Action action) { + ActionInstance actionInstance = (ActionInstance) trigger; + if (action.getOrigin() == ActionOrigin.PHYSICAL) { + if (actionInstance.getMinDelay().isEarlierThan(minDelay)) { + minDelay = actionInstance.getMinDelay(); + } + } else if (action.getOrigin() == ActionOrigin.LOGICAL) { + // Logical action + // Follow it upstream inside the reactor + for (ReactionInstance uReaction : actionInstance.getDependsOnReactions()) { + // Avoid a loop + if (!Objects.equal(uReaction, reaction)) { + TimeValue uMinDelay = + actionInstance.getMinDelay().add(findNearestPhysicalActionTrigger(uReaction)); + if (uMinDelay.isEarlierThan(minDelay)) { + minDelay = uMinDelay; + } } + } } - 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 " + id + ": " - + ((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 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 - */ - public TimeValue findNearestPhysicalActionTrigger(ReactionInstance reaction) { - TimeValue minDelay = TimeValue.MAX_VALUE; - for (TriggerInstance trigger : reaction.triggers) { - if (trigger.getDefinition() instanceof Action action) { - ActionInstance actionInstance = (ActionInstance) trigger; - if (action.getOrigin() == ActionOrigin.PHYSICAL) { - if (actionInstance.getMinDelay().isEarlierThan(minDelay)) { - minDelay = actionInstance.getMinDelay(); - } - } else if (action.getOrigin() == ActionOrigin.LOGICAL) { - // Logical action - // Follow it upstream inside the reactor - for (ReactionInstance uReaction: actionInstance.getDependsOnReactions()) { - // Avoid a loop - if (!Objects.equal(uReaction, reaction)) { - TimeValue uMinDelay = actionInstance.getMinDelay().add(findNearestPhysicalActionTrigger(uReaction)); - if (uMinDelay.isEarlierThan(minDelay)) { - minDelay = uMinDelay; - } - } - } - } - - } else if (trigger.getDefinition() instanceof Output) { - // Outputs of contained reactions - PortInstance outputInstance = (PortInstance) trigger; - for (ReactionInstance uReaction: outputInstance.getDependsOnReactions()) { - TimeValue uMinDelay = findNearestPhysicalActionTrigger(uReaction); - if (uMinDelay.isEarlierThan(minDelay)) { - minDelay = uMinDelay; - } - } - } + } else if (trigger.getDefinition() instanceof Output) { + // Outputs of contained reactions + PortInstance outputInstance = (PortInstance) trigger; + for (ReactionInstance uReaction : outputInstance.getDependsOnReactions()) { + TimeValue uMinDelay = findNearestPhysicalActionTrigger(uReaction); + if (uMinDelay.isEarlierThan(minDelay)) { + minDelay = uMinDelay; + } } - return minDelay; - } - - // TODO: Put this function into a utils file instead - private List convertToEmptyListIfNull(List list) { - return list == null ? new ArrayList<>() : list; + } } + 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/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index ba39a94d9f..9ae46ad357 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -1,16 +1,16 @@ /************* * Copyright (c) 2019-2020, The University of California at Berkeley. - + * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: - + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. - + * * 2. Redistributions in binary form must reproduce the above copyright notice, * 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 @@ -24,23 +24,23 @@ ***************/ package org.lflang.generator; +import com.google.common.base.Objects; +import com.google.common.collect.Iterables; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; - import org.eclipse.core.resources.IMarker; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.IteratorExtensions; - import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; @@ -53,17 +53,12 @@ import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Mode; - import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.validation.AbstractLFValidator; -import com.google.common.base.Objects; -import com.google.common.collect.Iterables; - /** - * Generator base class for specifying core functionality - * that all code generators should have. + * Generator base class for specifying core functionality that all code generators should have. * * @author Edward A. Lee * @author Marten Lohstroh @@ -73,574 +68,595 @@ */ 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 ErrorReporter errorReporter; - - //////////////////////////////////////////// - //// Protected fields. - - /** - * The current target configuration. - */ - protected final TargetConfig targetConfig; - - public TargetConfig getTargetConfig() { return this.targetConfig;} - - public final LFGeneratorContext context; - - /** - * A factory for compiler commands. - */ - protected GeneratorCommandFactory commandFactory; - - public GeneratorCommandFactory getCommandFactory() { return commandFactory; } - - /** - * Definition of the main (top-level) reactor. - * This is an automatically generated AST node for the top-level - * reactor. - */ - protected Instantiation mainDef; - public Instantiation getMainDef() { return mainDef; } - - /** - * A list of Reactor definitions in the main resource, including non-main - * reactors defined in imported resources. These are ordered in the list in - * such a way that each reactor is preceded by any reactor that it instantiates - * using a command like `foo = new Foo();` - */ - 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 instance of A, constructed with a statement - * like `a = new A();` After creating the graph, - * sort the reactors in topological order and assign them to the reactors class variable. - * Hence, after this method returns, `this.reactors` will be a list of Reactors such that any - * reactor is preceded in the list by reactors that it instantiates. - */ - protected InstantiationGraph instantiationGraph; - - /** - * The set of unordered reactions. An unordered reaction is one that does - * not have any dependency on other reactions in the containing reactor, - * and where no other reaction in the containing reactor depends on it. - * There is currently no way in the syntax of LF to make a reaction - * unordered, deliberately, because it can introduce unexpected - * nondeterminacy. However, certain automatically generated reactions are - * known to be safe to be unordered because they do not interact with the - * state of the containing reactor. To make a reaction unordered, when - * the Reaction instance is created, add that instance to this set. - */ - protected Set unorderedReactions = null; - - /** - * Map from reactions to bank indices - */ - protected Map reactionBankIndices = null; - - /** - * Indicates whether the current Lingua Franca program - * contains model reactors. - */ - public boolean hasModalReactors = false; - - /** - * Indicates whether the program has any deadlines and thus - * needs to propagate deadlines through the reaction instance graph - */ - public boolean hasDeadlines = false; - - // ////////////////////////////////////////// - // // Private fields. - - /** - * A list ot AST transformations to apply before code generation - */ - private final List astTransformations = new ArrayList<>(); - - /** - * Create a new GeneratorBase object. - */ - public GeneratorBase(LFGeneratorContext context) { - this.context = context; - this.targetConfig = context.getTargetConfig(); - this.errorReporter = context.getErrorReporter(); - this.commandFactory = new GeneratorCommandFactory(errorReporter, context.getFileConfig()); - } - - /** - * Register an AST transformation to be applied to the AST. - * - * The transformations will be applied in the order that they are registered in. - */ - protected void registerTransformation(AstTransformation transformation) { - astTransformations.add(transformation); - } - - // ////////////////////////////////////////// - // // Code generation functions to override for a concrete code generator. - - /** - * If there is a main or federated reactor, then create a synthetic Instantiation - * for that top-level reactor and set the field mainDef to refer to it. - */ - private void createMainInstantiation() { - // Find the main reactor and create an AST node for its instantiation. - Iterable nodes = IteratorExtensions.toIterable(context.getFileConfig().resource.getAllContents()); - for (Reactor reactor : Iterables.filter(nodes, Reactor.class)) { - if (reactor.isMain()) { - // Creating a definition for the main reactor because there isn't one. - this.mainDef = LfFactory.eINSTANCE.createInstantiation(); - this.mainDef.setName(reactor.getName()); - this.mainDef.setReactorClass(reactor); - } - } - } - - /** - * 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 standalone mode, this object is also used to relay CLI arguments. - */ - public void doGenerate(Resource resource, LFGeneratorContext context) { - - // FIXME: the signature can be reduced to only take context. - // The constructor also need not take a file config because this is tied to the context as well. - cleanIfNeeded(context); - - printInfo(context.getMode()); - - // Clear any IDE markers that may have been created by a previous build. - // Markers mark problems in the Eclipse IDE when running in integrated mode. - errorReporter.clearHistory(); - - ASTUtils.setMainName(context.getFileConfig().resource, context.getFileConfig().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(context.getFileConfig()).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(); - } - - // 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() // 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? - .filter(it -> !Objects.equal(it, context.getFileConfig().resource) || mainDef != null && it == mainDef.getReactorClass().eResource()) - .map(it -> GeneratorUtils.getLFResource(it, context.getFileConfig().getSrcGenBasePath(), context, errorReporter)) - .toList() - ); - GeneratorUtils.accommodatePhysicalActionsIfPresent( - allResources, - getTarget().setsKeepAliveOptionAutomatically(), - targetConfig, - errorReporter - ); - // FIXME: Should the GeneratorBase pull in `files` from imported - // resources? - - for (AstTransformation transformation : astTransformations) { - transformation.applyTransformation(reactors); - } - - // 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); - additionalPostProcessingForModes(); + //////////////////////////////////////////// + //// 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 ErrorReporter errorReporter; + + //////////////////////////////////////////// + //// Protected fields. + + /** The current target configuration. */ + protected final TargetConfig targetConfig; + + public TargetConfig getTargetConfig() { + return this.targetConfig; + } + + public final LFGeneratorContext context; + + /** A factory for compiler commands. */ + protected GeneratorCommandFactory commandFactory; + + public GeneratorCommandFactory getCommandFactory() { + return commandFactory; + } + + /** + * Definition of the main (top-level) reactor. This is an automatically generated AST node for the + * top-level reactor. + */ + protected Instantiation mainDef; + + public Instantiation getMainDef() { + return mainDef; + } + + /** + * A list of Reactor definitions in the main resource, including non-main reactors defined in + * imported resources. These are ordered in the list in such a way that each reactor is preceded + * by any reactor that it instantiates using a command like `foo = new Foo();` + */ + 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 + * instance of A, constructed with a statement like `a = new A();` After creating the graph, sort + * the reactors in topological order and assign them to the reactors class variable. Hence, after + * this method returns, `this.reactors` will be a list of Reactors such that any reactor is + * preceded in the list by reactors that it instantiates. + */ + protected InstantiationGraph instantiationGraph; + + /** + * The set of unordered reactions. An unordered reaction is one that does not have any dependency + * on other reactions in the containing reactor, and where no other reaction in the containing + * reactor depends on it. There is currently no way in the syntax of LF to make a reaction + * unordered, deliberately, because it can introduce unexpected nondeterminacy. However, certain + * automatically generated reactions are known to be safe to be unordered because they do not + * interact with the state of the containing reactor. To make a reaction unordered, when the + * Reaction instance is created, add that instance to this set. + */ + protected Set unorderedReactions = null; + + /** Map from reactions to bank indices */ + protected Map reactionBankIndices = null; + + /** Indicates whether the current Lingua Franca program contains model reactors. */ + public boolean hasModalReactors = false; + + /** + * Indicates whether the program has any deadlines and thus needs to propagate deadlines through + * the reaction instance graph + */ + public boolean hasDeadlines = false; + + /** 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<>(); + + /** Create a new GeneratorBase object. */ + public GeneratorBase(LFGeneratorContext context) { + this.context = context; + this.targetConfig = context.getTargetConfig(); + this.errorReporter = context.getErrorReporter(); + this.commandFactory = new GeneratorCommandFactory(errorReporter, context.getFileConfig()); + } + + /** + * Register an AST transformation to be applied to the AST. + * + *

The transformations will be applied in the order that they are registered in. + */ + protected void registerTransformation(AstTransformation transformation) { + astTransformations.add(transformation); + } + + // ////////////////////////////////////////// + // // Code generation functions to override for a concrete code generator. + + /** + * If there is a main or federated reactor, then create a synthetic Instantiation for that + * top-level reactor and set the field mainDef to refer to it. + */ + private void createMainInstantiation() { + // Find the main reactor and create an AST node for its instantiation. + Iterable nodes = + IteratorExtensions.toIterable(context.getFileConfig().resource.getAllContents()); + for (Reactor reactor : Iterables.filter(nodes, Reactor.class)) { + if (reactor.isMain()) { + // Creating a definition for the main reactor because there isn't one. + this.mainDef = LfFactory.eINSTANCE.createInstantiation(); + this.mainDef.setName(reactor.getName()); + this.mainDef.setReactorClass(reactor); + } } - - /** - * Check if a clean was requested from the standalone compiler and perform - * the clean step. - */ - protected void cleanIfNeeded(LFGeneratorContext context) { - if (context.getArgs().containsKey("clean")) { - try { - context.getFileConfig().doClean(); - } catch (IOException e) { - System.err.println("WARNING: IO Error during clean"); - } - } + } + + /** + * 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 standalone mode, this + * object is also used to relay CLI arguments. + */ + public void doGenerate(Resource resource, LFGeneratorContext context) { + + // FIXME: the signature can be reduced to only take context. + // The constructor also need not take a file config because this is tied to the context as well. + cleanIfNeeded(context); + + printInfo(context.getMode()); + + // Clear any IDE markers that may have been created by a previous build. + // Markers mark problems in the Eclipse IDE when running in integrated mode. + errorReporter.clearHistory(); + + ASTUtils.setMainName(context.getFileConfig().resource, context.getFileConfig().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(context.getFileConfig()).conflicts) { + errorReporter.reportError( + this.mainDef.getReactorClass(), "Conflicting main reactor in " + conflict); + } } - /** - * Create a new instantiation graph. 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 instance of A, constructed with a statement - * like `a = new A();` After creating the graph, - * sort the reactors in topological order and assign them to the reactors class variable. - * Hence, after this method returns, `this.reactors` will be a list of Reactors such that any - * reactor is preceded in the list by reactors that it instantiates. - */ - protected void setReactorsAndInstantiationGraph(LFGeneratorContext.Mode mode) { - // Build the instantiation graph . - instantiationGraph = new InstantiationGraph(context.getFileConfig().resource, false); - - // Topologically sort the reactors such that all of a reactor's instantiation dependencies occur earlier in - // the sorted list of reactors. This helps the code generator output code in the correct order. - // For example if `reactor Foo {bar = new Bar()}` then the definition of `Bar` has to be generated before - // the definition of `Foo`. - reactors = instantiationGraph.nodesInTopologicalOrder(); - - // If there is no main reactor or if all reactors in the file need to be validated, then make sure the reactors - // list includes even reactors that are not instantiated anywhere. - if (mainDef == null || Objects.equal(mode, LFGeneratorContext.Mode.LSP_MEDIUM)) { - Iterable nodes = IteratorExtensions.toIterable(context.getFileConfig().resource.getAllContents()); - for (Reactor r : IterableExtensions.filter(nodes, Reactor.class)) { - if (!reactors.contains(r)) { - reactors.add(r); - } - } - } + // Configure the command factory + commandFactory.setVerbose(); + if (Objects.equal(context.getMode(), LFGeneratorContext.Mode.STANDALONE) + && context.getArgs().containsKey("quiet")) { + commandFactory.setQuiet(); } - /** - * Copy user specific files to the src-gen folder. - * - * This should be overridden by the target generators. - * - * @param targetConfig The targetConfig to read the `files` from. - * @param fileConfig The fileConfig used to make the copy and resolve paths. - */ - protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) {} - - /** - * Return true if errors occurred in the last call to doGenerate(). - * This will return true if any of the reportError methods was called. - * @return True if errors occurred. - */ - public boolean errorsOccurred() { - return errorReporter.getErrorsOccurred(); + // 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() // 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? + .filter( + it -> + !Objects.equal(it, context.getFileConfig().resource) + || mainDef != null && it == mainDef.getReactorClass().eResource()) + .map( + it -> + GeneratorUtils.getLFResource( + it, context.getFileConfig().getSrcGenBasePath(), context, errorReporter)) + .toList()); + GeneratorUtils.accommodatePhysicalActionsIfPresent( + allResources, getTarget().setsKeepAliveOptionAutomatically(), targetConfig, errorReporter); + // FIXME: Should the GeneratorBase pull in `files` from imported + // resources? + + for (AstTransformation transformation : astTransformations) { + transformation.applyTransformation(reactors); } - /* - * Return the TargetTypes instance associated with this. - */ - public abstract TargetTypes getTargetTypes(); - - /** - * Mark the reaction unordered. An unordered reaction is one that does not - * have any dependency on other reactions in the containing reactor, and - * where no other reaction in the containing reactor depends on it. There - * is currently no way in the syntax of LF to make a reaction unordered, - * deliberately, because it can introduce unexpected nondeterminacy. - * However, certain automatically generated reactions are known to be safe - * to be unordered because they do not interact with the state of the - * containing reactor. To make a reaction unordered, when the Reaction - * instance is created, add that instance to this set. - * @param reaction The reaction to make unordered. - */ - public void makeUnordered(Reaction reaction) { - if (unorderedReactions == null) { - unorderedReactions = new LinkedHashSet<>(); - } - unorderedReactions.add(reaction); + // 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); + + // Check for the existence and support of watchdogs + hasWatchdogs = IterableExtensions.exists(reactors, it -> !it.getWatchdogs().isEmpty()); + checkWatchdogSupport(targetConfig.threading && getTarget() == Target.C); + additionalPostProcessingForModes(); + } + + /** Check if a clean was requested from the standalone compiler and perform the clean step. */ + protected void cleanIfNeeded(LFGeneratorContext context) { + if (context.getArgs().containsKey("clean")) { + try { + context.getFileConfig().doClean(); + } catch (IOException e) { + System.err.println("WARNING: IO Error during clean"); + } } - - /** - * Mark the specified reaction to belong to only the specified - * bank index. This is needed because reactions cannot declare - * a specific bank index as an effect or trigger. Reactions that - * send messages between federates, including absent messages, - * need to be specific to a bank member. - * @param reaction The reaction. - * @param bankIndex The bank index, or -1 if there is no bank. - */ - public void setReactionBankIndex(Reaction reaction, int bankIndex) { - if (bankIndex < 0) { - return; + } + + /** + * Create a new instantiation graph. 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 instance of A, + * constructed with a statement like `a = new A();` After creating the graph, sort the reactors in + * topological order and assign them to the reactors class variable. Hence, after this method + * returns, `this.reactors` will be a list of Reactors such that any reactor is preceded in the + * list by reactors that it instantiates. + */ + protected void setReactorsAndInstantiationGraph(LFGeneratorContext.Mode mode) { + // Build the instantiation graph . + instantiationGraph = new InstantiationGraph(context.getFileConfig().resource, false); + + // Topologically sort the reactors such that all of a reactor's instantiation dependencies occur + // earlier in + // the sorted list of reactors. This helps the code generator output code in the correct order. + // For example if `reactor Foo {bar = new Bar()}` then the definition of `Bar` has to be + // generated before + // the definition of `Foo`. + reactors = instantiationGraph.nodesInTopologicalOrder(); + + // If there is no main reactor or if all reactors in the file need to be validated, then make + // sure the reactors + // list includes even reactors that are not instantiated anywhere. + if (mainDef == null || Objects.equal(mode, LFGeneratorContext.Mode.LSP_MEDIUM)) { + Iterable nodes = + IteratorExtensions.toIterable(context.getFileConfig().resource.getAllContents()); + for (Reactor r : IterableExtensions.filter(nodes, Reactor.class)) { + if (!reactors.contains(r)) { + reactors.add(r); } - if (reactionBankIndices == null) { - reactionBankIndices = new LinkedHashMap<>(); - } - reactionBankIndices.put(reaction, bankIndex); + } } - - /** - * Return the reaction bank index. - * @see #setReactionBankIndex(Reaction reaction, int bankIndex) - * @param reaction The reaction. - * @return The reaction bank index, if one has been set, and -1 otherwise. - */ - public int getReactionBankIndex(Reaction reaction) { - if (reactionBankIndices == null) return -1; - if (reactionBankIndices.get(reaction) == null) return -1; - return reactionBankIndices.get(reaction); + } + + /** + * Copy user specific files to the src-gen folder. + * + *

This should be overridden by the target generators. + * + * @param targetConfig The targetConfig to read the `files` from. + * @param fileConfig The fileConfig used to make the copy and resolve paths. + */ + protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) {} + + /** + * Return true if errors occurred in the last call to doGenerate(). This will return true if any + * of the reportError methods was called. + * + * @return True if errors occurred. + */ + public boolean errorsOccurred() { + return errorReporter.getErrorsOccurred(); + } + + /* + * Return the TargetTypes instance associated with this. + */ + public abstract TargetTypes getTargetTypes(); + + /** + * Mark the reaction unordered. An unordered reaction is one that does not have any dependency on + * other reactions in the containing reactor, and where no other reaction in the containing + * reactor depends on it. There is currently no way in the syntax of LF to make a reaction + * unordered, deliberately, because it can introduce unexpected nondeterminacy. However, certain + * automatically generated reactions are known to be safe to be unordered because they do not + * interact with the state of the containing reactor. To make a reaction unordered, when the + * Reaction instance is created, add that instance to this set. + * + * @param reaction The reaction to make unordered. + */ + public void makeUnordered(Reaction reaction) { + if (unorderedReactions == null) { + unorderedReactions = new LinkedHashSet<>(); } - - // ////////////////////////////////////////// - // // Protected methods. - - /** - * Checks whether modal reactors are present and require appropriate code generation. - * This will set the hasModalReactors variable. - * @param isSupported indicates if modes are supported by this code generation. - */ - protected void checkModalReactorSupport(boolean isSupported) { - if (hasModalReactors && !isSupported) { - errorReporter.reportError("The currently selected code generation or " + - "target configuration does not support modal reactors!"); - } + unorderedReactions.add(reaction); + } + + /** + * Mark the specified reaction to belong to only the specified bank index. This is needed because + * reactions cannot declare a specific bank index as an effect or trigger. Reactions that send + * messages between federates, including absent messages, need to be specific to a bank member. + * + * @param reaction The reaction. + * @param bankIndex The bank index, or -1 if there is no bank. + */ + public void setReactionBankIndex(Reaction reaction, int bankIndex) { + if (bankIndex < 0) { + return; } - - /** - * 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); - if (!transform.isEmpty()) { - var factory = LfFactory.eINSTANCE; - for (Connection connection : transform) { - // Currently only simple transformations are supported - if (connection.isPhysical() || connection.getDelay() != null || connection.isIterated() || - connection.getLeftPorts().size() > 1 || connection.getRightPorts().size() > 1 - ) { - errorReporter.reportError(connection, "Cannot transform connection in modal reactor. Connection uses currently not supported features."); - } else { - var reaction = factory.createReaction(); - ((Mode)connection.eContainer()).getReactions().add(reaction); - - var sourceRef = connection.getLeftPorts().get(0); - var destRef = connection.getRightPorts().get(0); - reaction.getTriggers().add(sourceRef); - reaction.getEffects().add(destRef); - - var code = factory.createCode(); - var source = (sourceRef.getContainer() != null ? - sourceRef.getContainer().getName() + "." : "") + sourceRef.getVariable().getName(); - var dest = (destRef.getContainer() != null ? - destRef.getContainer().getName() + "." : "") + destRef.getVariable().getName(); - code.setBody(getConflictingConnectionsInModalReactorsBody(source, dest)); - reaction.setCode(code); - - EcoreUtil.remove(connection); - } - } - } - } + if (reactionBankIndices == null) { + reactionBankIndices = new LinkedHashMap<>(); } - /** - * Return target code for forwarding reactions iff the connections have the - * same destination as other connections or reaction in mutually exclusive modes. - * - * This method needs to be overridden in target specific code generators that - * support modal reactors. - */ - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - errorReporter.reportError("The currently selected code generation " + - "is missing an implementation for conflicting " + - "transforming connections in modal reactors."); - return "MODAL MODELS NOT SUPPORTED"; + reactionBankIndices.put(reaction, bankIndex); + } + + /** + * Return the reaction bank index. + * + * @see #setReactionBankIndex(Reaction reaction, int bankIndex) + * @param reaction The reaction. + * @return The reaction bank index, if one has been set, and -1 otherwise. + */ + public int getReactionBankIndex(Reaction reaction) { + if (reactionBankIndices == null) return -1; + if (reactionBankIndices.get(reaction) == null) return -1; + return reactionBankIndices.get(reaction); + } + + // ////////////////////////////////////////// + // // Protected methods. + + /** + * Checks whether modal reactors are present and require appropriate code generation. This will + * set the hasModalReactors variable. + * + * @param isSupported indicates if modes are supported by this code generation. + */ + protected void checkModalReactorSupport(boolean isSupported) { + if (hasModalReactors && !isSupported) { + errorReporter.reportError( + "The currently selected code generation or " + + "target configuration does not support modal reactors!"); } - - /** - * Hook for additional post-processing of the model. - */ - protected void additionalPostProcessingForModes() { - // Do nothing + } + + /** + * Checks whether watchdogs are present and are supported. + * + * @param isSupported indicates whether or not this is a supported target and whether or not it is + * a threaded runtime. + */ + protected void checkWatchdogSupport(boolean isSupported) { + if (hasWatchdogs && !isSupported) { + errorReporter.reportError( + "Watchdogs are currently only supported for threaded programs in the C target."); } - - /** - * Parsed error message from a compiler is returned here. - */ - public static class ErrorFileAndLine { - public String filepath = null; - public String line = "1"; - public String character = "0"; - public String message = ""; - public boolean isError = true; // false for a warning. - - @Override - public String toString() { - return (isError ? "Error" : "Non-error") + " at " + line + ":" + character + " of file " + filepath + ": " + message; + } + + /** + * 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); + if (!transform.isEmpty()) { + var factory = LfFactory.eINSTANCE; + for (Connection connection : transform) { + // Currently only simple transformations are supported + if (connection.isPhysical() + || connection.getDelay() != null + || connection.isIterated() + || connection.getLeftPorts().size() > 1 + || connection.getRightPorts().size() > 1) { + errorReporter.reportError( + connection, + "Cannot transform connection in modal reactor. Connection uses currently not" + + " supported features."); + } else { + var reaction = factory.createReaction(); + ((Mode) connection.eContainer()).getReactions().add(reaction); + + var sourceRef = connection.getLeftPorts().get(0); + var destRef = connection.getRightPorts().get(0); + reaction.getTriggers().add(sourceRef); + reaction.getEffects().add(destRef); + + var code = factory.createCode(); + var source = + (sourceRef.getContainer() != null ? sourceRef.getContainer().getName() + "." : "") + + sourceRef.getVariable().getName(); + var dest = + (destRef.getContainer() != null ? destRef.getContainer().getName() + "." : "") + + destRef.getVariable().getName(); + code.setBody(getConflictingConnectionsInModalReactorsBody(source, dest)); + reaction.setCode(code); + + EcoreUtil.remove(connection); + } } + } } - - /** - * Given a line of text from the output of a compiler, return - * an instance of ErrorFileAndLine if the line is recognized as - * the first line of an error message. Otherwise, return null. - * This base class simply returns null. - * @param line A line of output from a compiler or other external - * tool that might generate errors. - * @return If the line is recognized as the start of an error message, - * then return a class containing the path to the file on which the - * error occurred (or null if there is none), the line number (or the - * string "1" if there is none), the character position (or the string - * "0" if there is none), and the message (or an empty string if there - * is none). - */ - protected ErrorFileAndLine parseCommandOutput(String line) { - return null; + } + /** + * Return target code for forwarding reactions iff the connections have the same destination as + * other connections or reaction in mutually exclusive modes. + * + *

This method needs to be overridden in target specific code generators that support modal + * reactors. + */ + protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { + errorReporter.reportError( + "The currently selected code generation " + + "is missing an implementation for conflicting " + + "transforming connections in modal reactors."); + return "MODAL MODELS NOT SUPPORTED"; + } + + /** Hook for additional post-processing of the model. */ + protected void additionalPostProcessingForModes() { + // Do nothing + } + + /** Parsed error message from a compiler is returned here. */ + public static class ErrorFileAndLine { + public String filepath = null; + public String line = "1"; + public String character = "0"; + public String message = ""; + public boolean isError = true; // false for a warning. + + @Override + public String toString() { + return (isError ? "Error" : "Non-error") + + " at " + + line + + ":" + + character + + " of file " + + filepath + + ": " + + message; } - - /** - * Parse the specified string for command errors that can be reported - * using marks in the Eclipse IDE. In this class, we attempt to parse - * the messages to look for file and line information, thereby generating - * marks on the appropriate lines. This should not be called in standalone - * mode. - * - * @param stderr The output on standard error of executing a command. - */ - public void reportCommandErrors(String stderr) { - // NOTE: If the VS Code branch passes code review, then this function, - // parseCommandOutput, and ErrorFileAndLine will be deleted soon after. - // First, split the message into lines. - String[] lines = stderr.split("\\r?\\n"); - StringBuilder message = new StringBuilder(); - Integer lineNumber = null; - Path path = context.getFileConfig().srcFile; - // In case errors occur within an imported file, record the original path. - Path originalPath = path; - - int severity = IMarker.SEVERITY_ERROR; - for (String line : lines) { - ErrorFileAndLine parsed = parseCommandOutput(line); - if (parsed != null) { - // Found a new line number designator. - // If there is a previously accumulated message, report it. - if (message.length() > 0) { - if (severity == IMarker.SEVERITY_ERROR) - errorReporter.reportError(path, lineNumber, message.toString()); - else - errorReporter.reportWarning(path, lineNumber, message.toString()); - - if (!Objects.equal(originalPath.toFile(), path.toFile())) { - // Report an error also in the top-level resource. - // FIXME: It should be possible to descend through the import - // statements to find which one matches and mark all the - // import statements down the chain. But what a pain! - if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); - } else { - errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); - } - } - } - if (parsed.isError) { - severity = IMarker.SEVERITY_ERROR; - } else { - severity = IMarker.SEVERITY_WARNING; - } - - // Start accumulating a new message. - message = new StringBuilder(); - // Append the message on the line number designator line. - message.append(parsed.message); - - // Set the new line number. - try { - lineNumber = Integer.decode(parsed.line); - } catch (Exception ex) { - // Set the line number unknown. - lineNumber = null; - } - // FIXME: Ignoring the position within the line. - // Determine the path within which the error occurred. - path = Paths.get(parsed.filepath); - } else { - // No line designator. - if (message.length() > 0) { - message.append("\n"); - } else { - if (!line.toLowerCase().contains("error:")) { - severity = IMarker.SEVERITY_WARNING; - } - } - message.append(line); - } - } + } + + /** + * Given a line of text from the output of a compiler, return an instance of ErrorFileAndLine if + * the line is recognized as the first line of an error message. Otherwise, return null. This base + * class simply returns null. + * + * @param line A line of output from a compiler or other external tool that might generate errors. + * @return If the line is recognized as the start of an error message, then return a class + * containing the path to the file on which the error occurred (or null if there is none), the + * line number (or the string "1" if there is none), the character position (or the string "0" + * if there is none), and the message (or an empty string if there is none). + */ + protected ErrorFileAndLine parseCommandOutput(String line) { + return null; + } + + /** + * Parse the specified string for command errors that can be reported using marks in the Eclipse + * IDE. In this class, we attempt to parse the messages to look for file and line information, + * thereby generating marks on the appropriate lines. This should not be called in standalone + * mode. + * + * @param stderr The output on standard error of executing a command. + */ + public void reportCommandErrors(String stderr) { + // NOTE: If the VS Code branch passes code review, then this function, + // parseCommandOutput, and ErrorFileAndLine will be deleted soon after. + // First, split the message into lines. + String[] lines = stderr.split("\\r?\\n"); + StringBuilder message = new StringBuilder(); + Integer lineNumber = null; + Path path = context.getFileConfig().srcFile; + // In case errors occur within an imported file, record the original path. + Path originalPath = path; + + int severity = IMarker.SEVERITY_ERROR; + for (String line : lines) { + ErrorFileAndLine parsed = parseCommandOutput(line); + if (parsed != null) { + // Found a new line number designator. + // If there is a previously accumulated message, report it. if (message.length() > 0) { + if (severity == IMarker.SEVERITY_ERROR) + errorReporter.reportError(path, lineNumber, message.toString()); + else errorReporter.reportWarning(path, lineNumber, message.toString()); + + if (!Objects.equal(originalPath.toFile(), path.toFile())) { + // Report an error also in the top-level resource. + // FIXME: It should be possible to descend through the import + // statements to find which one matches and mark all the + // import statements down the chain. But what a pain! if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(path, lineNumber, message.toString()); + errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); } else { - errorReporter.reportWarning(path, lineNumber, message.toString()); + errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); } + } + } + if (parsed.isError) { + severity = IMarker.SEVERITY_ERROR; + } else { + severity = IMarker.SEVERITY_WARNING; + } - if (originalPath.toFile() != path.toFile()) { - // Report an error also in the top-level resource. - // FIXME: It should be possible to descend through the import - // statements to find which one matches and mark all the - // import statements down the chain. But what a pain! - if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); - } else { - errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); - } - } + // Start accumulating a new message. + message = new StringBuilder(); + // Append the message on the line number designator line. + message.append(parsed.message); + + // Set the new line number. + try { + lineNumber = Integer.decode(parsed.line); + } catch (Exception ex) { + // Set the line number unknown. + lineNumber = null; + } + // FIXME: Ignoring the position within the line. + // Determine the path within which the error occurred. + path = Paths.get(parsed.filepath); + } else { + // No line designator. + if (message.length() > 0) { + message.append("\n"); + } else { + if (!line.toLowerCase().contains("error:")) { + severity = IMarker.SEVERITY_WARNING; + } } + message.append(line); + } } - - // ////////////////////////////////////////////////// - // // Private functions - - /** - * Print to stdout information about what source file is being generated, - * what mode the generator is in, and where the generated sources are to be put. - */ - public void printInfo(LFGeneratorContext.Mode mode) { - System.out.println("Generating code for: " + context.getFileConfig().resource.getURI().toString()); - System.out.println("******** mode: " + mode); - System.out.println("******** generated sources: " + context.getFileConfig().getSrcGenPath()); + if (message.length() > 0) { + if (severity == IMarker.SEVERITY_ERROR) { + errorReporter.reportError(path, lineNumber, message.toString()); + } else { + errorReporter.reportWarning(path, lineNumber, message.toString()); + } + + if (originalPath.toFile() != path.toFile()) { + // Report an error also in the top-level resource. + // FIXME: It should be possible to descend through the import + // statements to find which one matches and mark all the + // import statements down the chain. But what a pain! + if (severity == IMarker.SEVERITY_ERROR) { + errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); + } else { + errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); + } + } } - - /** - * Get the buffer type used for network messages - */ - public String getNetworkBufferType() { return ""; } - - /** - * Return the Targets enum for the current target - */ - public abstract Target getTarget(); - + } + + // ////////////////////////////////////////////////// + // // Private functions + + /** + * Print to stdout information about what source file is being generated, what mode the generator + * is in, and where the generated sources are to be put. + */ + public void printInfo(LFGeneratorContext.Mode mode) { + System.out.println( + "Generating code for: " + context.getFileConfig().resource.getURI().toString()); + System.out.println("******** mode: " + mode); + System.out.println("******** generated sources: " + context.getFileConfig().getSrcGenPath()); + } + + /** Get the buffer type used for network messages */ + public String getNetworkBufferType() { + return ""; + } + + /** Return the Targets enum for the current target */ + public abstract Target getTarget(); } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index eb5163ade6..b3bf51d836 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -1,32 +1,31 @@ /** A data structure for a reactor instance. */ /************* -Copyright (c) 2019-2022, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - 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 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ + * Copyright (c) 2019-2022, The University of California at Berkeley. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * 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 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ package org.lflang.generator; -import static org.lflang.ASTUtils.belongsTo; import static org.lflang.ASTUtils.getLiteralTimeValue; import java.util.ArrayList; @@ -38,9 +37,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Map; import java.util.Optional; import java.util.Set; - -import org.eclipse.emf.ecore.util.EcoreUtil; - import org.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; @@ -55,7 +51,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Initializer; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; -import org.lflang.lf.LfFactory; import org.lflang.lf.Mode; import org.lflang.lf.Output; import org.lflang.lf.Parameter; @@ -67,1103 +62,1101 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Timer; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; - /** - * Representation of a compile-time instance of a reactor. - * If the reactor is instantiated as a bank of reactors, or if any - * of its parents is instantiated as a bank of reactors, then one instance - * of this ReactorInstance class represents all the runtime instances within - * these banks. The {@link #getTotalWidth()} method returns the number of such - * runtime instances, which is the product of the bank width of this reactor - * instance and the bank widths of all of its parents. - * There is exactly one instance of this ReactorInstance class for each - * graphical rendition of a reactor in the diagram view. + * Representation of a compile-time instance of a reactor. If the reactor is instantiated as a bank + * of reactors, or if any of its parents is instantiated as a bank of reactors, then one instance of + * this ReactorInstance class represents all the runtime instances within these banks. The {@link + * #getTotalWidth()} method returns the number of such runtime instances, which is the product of + * the bank width of this reactor instance and the bank widths of all of its parents. There is + * exactly one instance of this ReactorInstance class for each graphical rendition of a reactor in + * the diagram view. * - * For the main reactor, which has no parent, once constructed, - * this object represents the entire Lingua Franca program. - * If the program has causality loops (a programming error), then - * {@link #hasCycles()} will return true and {@link #getCycles()} will - * return the ports and reaction instances involved in the cycles. + *

For the main reactor, which has no parent, once constructed, this object represents the entire + * Lingua Franca program. If the program has causality loops (a programming error), then {@link + * #hasCycles()} will return true and {@link #getCycles()} will return the ports and reaction + * instances involved in the cycles. * * @author Marten Lohstroh * @author Edward A. Lee */ public class ReactorInstance extends NamedInstance { - /** - * Create a new instantiation hierarchy that starts with the given top-level reactor. - * @param reactor The top-level reactor. - * @param reporter The error reporter. - */ - public ReactorInstance(Reactor reactor, ErrorReporter reporter) { - this(ASTUtils.createInstantiation(reactor), null, reporter, -1); + /** + * Create a new instantiation hierarchy that starts with the given top-level reactor. + * + * @param reactor The top-level reactor. + * @param reporter The error reporter. + */ + public ReactorInstance(Reactor reactor, ErrorReporter reporter) { + this(ASTUtils.createInstantiation(reactor), null, reporter, -1); + } + + /** + * Create a new instantiation hierarchy that starts with the given top-level reactor but only + * creates contained reactors up to the specified depth. + * + * @param reactor The top-level reactor. + * @param reporter The error reporter. + * @param desiredDepth The depth to which to go, or -1 to construct the full hierarchy. + */ + public ReactorInstance(Reactor reactor, ErrorReporter reporter, int desiredDepth) { + this(ASTUtils.createInstantiation(reactor), null, reporter, desiredDepth); + } + + /** + * Create a new instantiation with the specified parent. This constructor is here to allow for + * unit tests. It should not be used for any other purpose. + * + * @param reactor The top-level reactor. + * @param parent The parent reactor instance. + * @param reporter The error reporter. + */ + public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter reporter) { + this(ASTUtils.createInstantiation(reactor), parent, reporter, -1); + } + + ////////////////////////////////////////////////////// + //// Public fields. + + /** The action instances belonging to this reactor instance. */ + public List actions = new ArrayList<>(); + + /** + * The contained reactor instances, in order of declaration. For banks of reactors, this includes + * both the bank definition Reactor (which has bankIndex == -2) followed by each of the bank + * members (which have bankIndex >= 0). + */ + public final List children = new ArrayList<>(); + + /** The input port instances belonging to this reactor instance. */ + public final List inputs = new ArrayList<>(); + + /** The output port instances belonging to this reactor instance. */ + public final List outputs = new ArrayList<>(); + + /** The parameters of this instance. */ + public final List parameters = new ArrayList<>(); + + /** List of reaction instances for this reactor instance. */ + public final List reactions = new ArrayList<>(); + + /** List of watchdog instances for this reactor instance. */ + public final List watchdogs = new ArrayList<>(); + + /** The timer instances belonging to this reactor instance. */ + public final List timers = new ArrayList<>(); + + /** The mode instances belonging to this reactor instance. */ + public final List modes = new ArrayList<>(); + + /** The reactor declaration in the AST. This is either an import or Reactor declaration. */ + public final ReactorDecl reactorDeclaration; + + /** The reactor after imports are resolve. */ + public final Reactor reactorDefinition; + + /** Indicator that this reactor has itself as a parent, an error condition. */ + public final boolean recursive; + + ////////////////////////////////////////////////////// + //// Public methods. + + /** + * Assign levels to all reactions within the same root as this reactor. The level of a reaction r + * is equal to the length of the longest chain of reactions that must have the opportunity to + * execute before r at each logical tag. This fails and returns false if a causality cycle exists. + * + *

This method uses a variant of Kahn's algorithm, which is linear in V + E, where V is the + * number of vertices (reactions) and E is the number of edges (dependencies between reactions). + * + * @return An empty graph if successful and otherwise a graph with runtime reaction instances that + * form cycles. + */ + public ReactionInstanceGraph assignLevels() { + if (depth != 0) return root().assignLevels(); + if (cachedReactionLoopGraph == null) { + cachedReactionLoopGraph = new ReactionInstanceGraph(this); } - - /** - * Create a new instantiation hierarchy that starts with the given top-level reactor - * but only creates contained reactors up to the specified depth. - * @param reactor The top-level reactor. - * @param reporter The error reporter. - * @param desiredDepth The depth to which to go, or -1 to construct the full hierarchy. - */ - public ReactorInstance(Reactor reactor, ErrorReporter reporter, int desiredDepth) { - this(ASTUtils.createInstantiation(reactor), null, reporter, desiredDepth); + return cachedReactionLoopGraph; + } + + /** + * This function assigns/propagates deadlines through the Reaction Instance Graph. It performs + * Kahn`s algorithm in reverse, starting from the leaf nodes and propagates deadlines upstream. To + * reduce cost, it should only be invoked when there are user-specified deadlines in the program. + * + * @return + */ + public ReactionInstanceGraph assignDeadlines() { + if (depth != 0) return root().assignDeadlines(); + if (cachedReactionLoopGraph == null) { + cachedReactionLoopGraph = new ReactionInstanceGraph(this); } - - /** - * Create a new instantiation with the specified parent. - * This constructor is here to allow for unit tests. - * It should not be used for any other purpose. - * @param reactor The top-level reactor. - * @param parent The parent reactor instance. - * @param reporter The error reporter. - */ - public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter reporter) { - this(ASTUtils.createInstantiation(reactor), parent, reporter, -1); + cachedReactionLoopGraph.rebuildAndAssignDeadlines(); + return cachedReactionLoopGraph; + } + + /** + * Return the instance of a child rector created by the specified definition or null if there is + * none. + * + * @param definition The definition of the child reactor ("new" statement). + */ + public ReactorInstance getChildReactorInstance(Instantiation definition) { + for (ReactorInstance child : this.children) { + if (child.definition == definition) { + return child; + } } - - ////////////////////////////////////////////////////// - //// Public fields. - - /** The action instances belonging to this reactor instance. */ - public List actions = new ArrayList<>(); - - /** - * The contained reactor instances, in order of declaration. - * For banks of reactors, this includes both the bank definition - * Reactor (which has bankIndex == -2) followed by each of the - * bank members (which have bankIndex >= 0). - */ - public final List children = new ArrayList<>(); - - /** The input port instances belonging to this reactor instance. */ - public final List inputs = new ArrayList<>(); - - /** The output port instances belonging to this reactor instance. */ - public final List outputs = new ArrayList<>(); - - /** The parameters of this instance. */ - public final List parameters = new ArrayList<>(); - - /** List of reaction instances for this reactor instance. */ - public final List reactions = new ArrayList<>(); - - /** The timer instances belonging to this reactor instance. */ - public final List timers = new ArrayList<>(); - - /** The mode instances belonging to this reactor instance. */ - public final List modes = new ArrayList<>(); - - /** The reactor declaration in the AST. This is either an import or Reactor declaration. */ - public final ReactorDecl reactorDeclaration; - - /** The reactor after imports are resolve. */ - public final Reactor reactorDefinition; - - /** Indicator that this reactor has itself as a parent, an error condition. */ - public final boolean recursive; - - ////////////////////////////////////////////////////// - //// Public methods. - - /** - * Assign levels to all reactions within the same root as this - * reactor. The level of a reaction r is equal to the length of the - * longest chain of reactions that must have the opportunity to - * execute before r at each logical tag. This fails and returns - * false if a causality cycle exists. - * - * This method uses a variant of Kahn's algorithm, which is linear - * in V + E, where V is the number of vertices (reactions) and E - * is the number of edges (dependencies between reactions). - * - * @return An empty graph if successful and otherwise a graph - * with runtime reaction instances that form cycles. - */ - public ReactionInstanceGraph assignLevels() { - if (depth != 0) return root().assignLevels(); - if (cachedReactionLoopGraph == null) { - cachedReactionLoopGraph = new ReactionInstanceGraph(this); - } - return cachedReactionLoopGraph; + return null; + } + + /** + * Clear any cached data in this reactor and its children. This is useful if a mutation has been + * realized. + */ + public void clearCaches() { + clearCaches(true); + } + + /** + * Clear any cached data in this reactor and its children. This is useful if a mutation has been + * realized. + * + * @param includingRuntimes If false, leave the runtime instances of reactions intact. This is + * useful for federated execution where levels are computed using the top-level connections, + * but then those connections are discarded. + */ + public void clearCaches(boolean includingRuntimes) { + if (includingRuntimes) cachedReactionLoopGraph = null; + for (ReactorInstance child : children) { + child.clearCaches(includingRuntimes); } - - /** - * This function assigns/propagates deadlines through the Reaction Instance Graph. - * It performs Kahn`s algorithm in reverse, starting from the leaf nodes and - * propagates deadlines upstream. To reduce cost, it should only be invoked when - * there are user-specified deadlines in the program. - * @return - */ - public ReactionInstanceGraph assignDeadlines() { - if (depth != 0) return root().assignDeadlines(); - if (cachedReactionLoopGraph == null) { - cachedReactionLoopGraph = new ReactionInstanceGraph(this); - } - cachedReactionLoopGraph.rebuildAndAssignDeadlines(); - return cachedReactionLoopGraph; + for (PortInstance port : inputs) { + port.clearCaches(); } - - /** - * Return the instance of a child rector created by the specified - * definition or null if there is none. - * @param definition The definition of the child reactor ("new" statement). - */ - public ReactorInstance getChildReactorInstance(Instantiation definition) { - for (ReactorInstance child : this.children) { - if (child.definition == definition) { - return child; - } - } - return null; + for (PortInstance port : outputs) { + port.clearCaches(); } - - /** - * Clear any cached data in this reactor and its children. - * This is useful if a mutation has been realized. - */ - public void clearCaches() { - clearCaches(true); + for (ReactionInstance reaction : reactions) { + reaction.clearCaches(includingRuntimes); } - - /** - * Clear any cached data in this reactor and its children. - * This is useful if a mutation has been realized. - * @param includingRuntimes If false, leave the runtime instances of reactions intact. - * This is useful for federated execution where levels are computed using - * the top-level connections, but then those connections are discarded. - */ - public void clearCaches(boolean includingRuntimes) { - if (includingRuntimes) cachedReactionLoopGraph = null; - for (ReactorInstance child : children) { - child.clearCaches(includingRuntimes); - } - for (PortInstance port : inputs) { - port.clearCaches(); - } - for (PortInstance port : outputs) { - port.clearCaches(); - } - for (ReactionInstance reaction : reactions) { - reaction.clearCaches(includingRuntimes); - } - cachedCycles = null; + cachedCycles = null; + } + + /** + * Return the specified input by name or null if there is no such input. + * + * @param name The input name. + */ + public PortInstance getInput(String name) { + for (PortInstance port : inputs) { + if (port.getName().equals(name)) { + return port; + } } - - /** - * Return the set of ReactionInstance and PortInstance that form causality - * loops in the topmost parent reactor in the instantiation hierarchy. This will return an - * empty set if there are no causality loops. - */ - public Set> getCycles() { - if (depth != 0) return root().getCycles(); - if (cachedCycles != null) return cachedCycles; - cachedCycles = new LinkedHashSet<>(); - - ReactionInstanceGraph reactionRuntimes = assignLevels(); - if (reactionRuntimes.nodes().size() > 0) { - Set reactions = new LinkedHashSet<>(); - Set ports = new LinkedHashSet<>(); - // There are cycles. But the nodes set includes not - // just the cycles, but also nodes that are downstream of the - // cycles. Use Tarjan's algorithm to get just the cycles. - var cycleNodes = reactionRuntimes.getCycles(); - for (var cycle : cycleNodes) { - for (ReactionInstance.Runtime runtime : cycle) { - reactions.add(runtime.getReaction()); - } - } - // Need to figure out which ports are involved in the cycles. - // It may not be all ports that depend on this reaction. - for (ReactionInstance r : reactions) { - for (TriggerInstance p : r.effects) { - if (p instanceof PortInstance) { - findPaths((PortInstance)p, reactions, ports); - } - } - } - cachedCycles.addAll(reactions); - cachedCycles.addAll(ports); + return null; + } + + /** + * Return the set of ReactionInstance and PortInstance that form causality loops in the topmost + * parent reactor in the instantiation hierarchy. This will return an empty set if there are no + * causality loops. + */ + public Set> getCycles() { + if (depth != 0) return root().getCycles(); + if (cachedCycles != null) return cachedCycles; + cachedCycles = new LinkedHashSet<>(); + + ReactionInstanceGraph reactionRuntimes = assignLevels(); + if (reactionRuntimes.nodes().size() > 0) { + Set reactions = new LinkedHashSet<>(); + Set ports = new LinkedHashSet<>(); + // There are cycles. But the nodes set includes not + // just the cycles, but also nodes that are downstream of the + // cycles. Use Tarjan's algorithm to get just the cycles. + var cycleNodes = reactionRuntimes.getCycles(); + for (var cycle : cycleNodes) { + for (ReactionInstance.Runtime runtime : cycle) { + reactions.add(runtime.getReaction()); } - - return cachedCycles; - } - - /** - * Return the specified input by name or null if there is no such input. - * @param name The input name. - */ - public PortInstance getInput(String name) { - for (PortInstance port: inputs) { - if (port.getName().equals(name)) { - return port; - } + } + // Need to figure out which ports are involved in the cycles. + // It may not be all ports that depend on this reaction. + for (ReactionInstance r : reactions) { + for (TriggerInstance p : r.effects) { + if (p instanceof PortInstance) { + findPaths((PortInstance) p, reactions, ports); + } } - return null; + } + cachedCycles.addAll(reactions); + cachedCycles.addAll(ports); } - /** - * Override the base class to append [i_d], where d is the depth, - * if this reactor is in a bank of reactors. - * @return The name of this instance. - */ - @Override - public String getName() { - return this.definition.getName(); + return cachedCycles; + } + + /** + * Override the base class to append [i_d], where d is the depth, if this reactor is in a bank of + * reactors. + * + * @return The name of this instance. + */ + @Override + public String getName() { + return this.definition.getName(); + } + + /** + * @see NamedInstance#uniqueID() + *

Append `_main` to the name of the main reactor to allow instantiations within that + * reactor to have the same name. + */ + @Override + public String uniqueID() { + if (this.isMainOrFederated()) { + return super.uniqueID() + "_main"; } - - /** - * @see NamedInstance#uniqueID() - * - * Append `_main` to the name of the main reactor to allow instantiations - * within that reactor to have the same name. - */ - @Override - public String uniqueID() { - if (this.isMainOrFederated()) { - return super.uniqueID() + "_main"; - } - return super.uniqueID(); + return super.uniqueID(); + } + + /** + * Return the specified output by name or null if there is no such output. + * + * @param name The output name. + */ + public PortInstance getOutput(String name) { + for (PortInstance port : outputs) { + if (port.getName().equals(name)) { + return port; + } } - - /** - * Return the specified output by name or null if there is no such output. - * @param name The output name. - */ - public PortInstance getOutput(String name) { - for (PortInstance port: outputs) { - if (port.getName().equals(name)) { - return port; - } - } - return null; + return null; + } + + /** + * Return a parameter matching the specified name if the reactor has one and otherwise return + * null. + * + * @param name The parameter name. + */ + public ParameterInstance getParameter(String name) { + for (ParameterInstance parameter : parameters) { + if (parameter.getName().equals(name)) { + return parameter; + } } - - /** - * Return a parameter matching the specified name if the reactor has one - * and otherwise return null. - * @param name The parameter name. - */ - public ParameterInstance getParameter(String name) { - for (ParameterInstance parameter: parameters) { - if (parameter.getName().equals(name)) { - return parameter; - } - } - return null; - } - - /** - * Return the startup trigger or null if not used in any reaction. - */ - public TriggerInstance getStartupTrigger() { - return builtinTriggers.get(BuiltinTrigger.STARTUP); + return null; + } + + /** Return the startup trigger or null if not used in any reaction. */ + public TriggerInstance getStartupTrigger() { + return builtinTriggers.get(BuiltinTrigger.STARTUP); + } + + /** Return the shutdown trigger or null if not used in any reaction. */ + public TriggerInstance getShutdownTrigger() { + return builtinTriggers.get(BuiltinTrigger.SHUTDOWN); + } + + /** + * If this reactor is a bank or any of its parents is a bank, return the total number of runtime + * instances, which is the product of the widths of all the parents. Return -1 if the width cannot + * be determined. + */ + public int getTotalWidth() { + return getTotalWidth(0); + } + + /** + * If this reactor is a bank or any of its parents is a bank, return the total number of runtime + * instances, which is the product of the widths of all the parents. Return -1 if the width cannot + * be determined. + * + * @param atDepth The depth at which to determine the width. Use 0 to get the total number of + * instances. Use 1 to get the number of instances within a single top-level bank member (this + * is useful for federates). + */ + public int getTotalWidth(int atDepth) { + if (width <= 0) return -1; + if (depth <= atDepth) return 1; + int result = width; + ReactorInstance p = parent; + while (p != null && p.depth > atDepth) { + if (p.width <= 0) return -1; + result *= p.width; + p = p.parent; } - - /** - * Return the shutdown trigger or null if not used in any reaction. - */ - public TriggerInstance getShutdownTrigger() { - return builtinTriggers.get(BuiltinTrigger.SHUTDOWN); + return result; + } + + /** + * Return the trigger instances (input ports, timers, and actions that trigger reactions) + * belonging to this reactor instance. + */ + public Set> getTriggers() { + // FIXME: Cache this. + var triggers = new LinkedHashSet>(); + for (ReactionInstance reaction : this.reactions) { + triggers.addAll(reaction.triggers); } - - /** - * If this reactor is a bank or any of its parents is a bank, - * return the total number of runtime instances, which is the product - * of the widths of all the parents. - * Return -1 if the width cannot be determined. - */ - public int getTotalWidth() { - return getTotalWidth(0); + return triggers; + } + + /** + * Return the trigger instances (input ports, timers, and actions that trigger reactions) together + * the ports that the reaction reads but that don't trigger it. + * + * @return The trigger instances belonging to this reactor instance. + */ + public Set> getTriggersAndReads() { + // FIXME: Cache this. + var triggers = new LinkedHashSet>(); + for (ReactionInstance reaction : this.reactions) { + triggers.addAll(reaction.triggers); + triggers.addAll(reaction.reads); } + return triggers; + } + + /** Return true if the top-level parent of this reactor has causality cycles. */ + public boolean hasCycles() { + return assignLevels().nodeCount() != 0; + } + + /** + * Given a parameter definition for this reactor, return the initial integer value of the + * parameter. If the parameter is overridden when instantiating this reactor or any of its + * containing reactors, use that value. Otherwise, use the default value in the reactor + * definition. If the parameter cannot be found or its value is not an integer, return null. + * + * @param parameter The parameter definition (a syntactic object in the AST). + * @return An integer value or null. + */ + public Integer initialIntParameterValue(Parameter parameter) { + return ASTUtils.initialValueInt(parameter, instantiations()); + } + + public Expression resolveParameters(Expression e) { + return LfExpressionVisitor.dispatch(e, this, ParameterInliner.INSTANCE); + } + + private static final class ParameterInliner + extends LfExpressionVisitor.LfExpressionDeepCopyVisitor { + static final ParameterInliner INSTANCE = new ParameterInliner(); - /** - * If this reactor is a bank or any of its parents is a bank, - * return the total number of runtime instances, which is the product - * of the widths of all the parents. - * Return -1 if the width cannot be determined. - * @param atDepth The depth at which to determine the width. - * Use 0 to get the total number of instances. - * Use 1 to get the number of instances within a single top-level - * bank member (this is useful for federates). - */ - public int getTotalWidth(int atDepth) { - if (width <= 0) return -1; - if (depth <= atDepth) return 1; - int result = width; - ReactorInstance p = parent; - while (p != null && p.depth > atDepth) { - if (p.width <= 0) return -1; - result *= p.width; - p = p.parent; + @Override + public Expression visitParameterRef(ParameterReference expr, ReactorInstance instance) { + if (!ASTUtils.belongsTo(expr.getParameter(), instance.definition)) { + throw new IllegalArgumentException( + "Parameter " + + expr.getParameter().getName() + + " is not a parameter of reactor instance " + + instance.getName() + + "."); + } + + Optional assignment = + instance.definition.getParameters().stream() + .filter(it -> it.getLhs().equals(expr.getParameter())) + .findAny(); // There is at most one + + if (assignment.isPresent()) { + // replace the parameter with its value. + Expression value = ASTUtils.asSingleExpr(assignment.get().getRhs()); + // recursively resolve parameters + return instance.getParent().resolveParameters(value); + } else { + // In that case use the default value. Default values + // cannot use parameter values, so they don't need to + // be recursively resolved. + Initializer init = expr.getParameter().getInit(); + Expression defaultValue = ASTUtils.asSingleExpr(init); + if (defaultValue == null) { + // this is a problem + return super.visitParameterRef(expr, instance); } - return result; + return defaultValue; + } } - - /** - * Return the trigger instances (input ports, timers, and actions - * that trigger reactions) belonging to this reactor instance. - */ - public Set> getTriggers() { - // FIXME: Cache this. - var triggers = new LinkedHashSet>(); - for (ReactionInstance reaction : this.reactions) { - triggers.addAll(reaction.triggers); - } - return triggers; + } + + // /** + // * Return the startup trigger or null if not used in any reaction. + // */ + // public TriggerInstance getStartupTrigger() { + // return _instantiations; + // } + + /////////////////////////////////////////////////// + //// Methods for finding instances in this reactor given an AST node. + + /** + * Given a parameter definition, return the parameter instance corresponding to that definition, + * or null if there is no such instance. + * + * @param parameter The parameter definition (a syntactic object in the AST). + * @return A parameter instance, or null if there is none. + */ + public ParameterInstance lookupParameterInstance(Parameter parameter) { + for (ParameterInstance param : parameters) { + if (param.definition == parameter) { + return param; + } } - - /** - * Return the trigger instances (input ports, timers, and actions - * that trigger reactions) together the ports that the reaction reads - * but that don't trigger it. - * - * @return The trigger instances belonging to this reactor instance. - */ - public Set> getTriggersAndReads() { - // FIXME: Cache this. - var triggers = new LinkedHashSet>(); - for (ReactionInstance reaction : this.reactions) { - triggers.addAll(reaction.triggers); - triggers.addAll(reaction.reads); - } - return triggers; + return null; + } + + /** + * Given a port definition, return the port instance corresponding to that definition, or null if + * there is no such instance. + * + * @param port The port definition (a syntactic object in the AST). + * @return A port instance, or null if there is none. + */ + public PortInstance lookupPortInstance(Port port) { + // Search one of the inputs and outputs sets. + List ports = null; + if (port instanceof Input) { + ports = this.inputs; + } else if (port instanceof Output) { + ports = this.outputs; } - - /** - * Return true if the top-level parent of this reactor has causality cycles. - */ - public boolean hasCycles() { - return assignLevels().nodeCount() != 0; + for (PortInstance portInstance : ports) { + if (portInstance.definition == port) { + return portInstance; + } } - - /** - * Given a parameter definition for this reactor, return the initial integer - * value of the parameter. If the parameter is overridden when instantiating - * this reactor or any of its containing reactors, use that value. - * Otherwise, use the default value in the reactor definition. - * If the parameter cannot be found or its value is not an integer, return null. - * - * @param parameter The parameter definition (a syntactic object in the AST). - * - * @return An integer value or null. - */ - public Integer initialIntParameterValue(Parameter parameter) { - return ASTUtils.initialValueInt(parameter, instantiations()); + return null; + } + + /** + * Given a reference to a port belonging to this reactor instance, return the port instance. + * Return null if there is no such instance. + * + * @param reference The port reference. + * @return A port instance, or null if there is none. + */ + public PortInstance lookupPortInstance(VarRef reference) { + if (!(reference.getVariable() instanceof Port)) { + // Trying to resolve something that is not a port + return null; } - - public Expression resolveParameters(Expression e) { - return LfExpressionVisitor.dispatch(e, this, ParameterInliner.INSTANCE); + if (reference.getContainer() == null) { + // Handle local reference + return lookupPortInstance((Port) reference.getVariable()); + } else { + // Handle hierarchical reference + var containerInstance = getChildReactorInstance(reference.getContainer()); + if (containerInstance == null) return null; + return containerInstance.lookupPortInstance((Port) reference.getVariable()); } - - - private static final class ParameterInliner extends LfExpressionVisitor.LfExpressionDeepCopyVisitor { - static final ParameterInliner INSTANCE = new ParameterInliner(); - - @Override - public Expression visitParameterRef(ParameterReference expr, ReactorInstance instance) { - if (!ASTUtils.belongsTo(expr.getParameter(), instance.definition)) { - throw new IllegalArgumentException("Parameter " - + expr.getParameter().getName() - + " is not a parameter of reactor instance " - + instance.getName() - + "." - ); - } - - Optional assignment = - instance.definition.getParameters().stream() - .filter(it -> it.getLhs().equals(expr.getParameter())) - .findAny(); // There is at most one - - if (assignment.isPresent()) { - // replace the parameter with its value. - Expression value = ASTUtils.asSingleExpr(assignment.get().getRhs()); - // recursively resolve parameters - return instance.getParent().resolveParameters(value); - } else { - // In that case use the default value. Default values - // cannot use parameter values, so they don't need to - // be recursively resolved. - Initializer init = expr.getParameter().getInit(); - Expression defaultValue = ASTUtils.asSingleExpr(init); - if (defaultValue == null) { - // this is a problem - return super.visitParameterRef(expr, instance); - } - return defaultValue; - } - } + } + + /** + * Return the reaction instance within this reactor instance corresponding to the specified + * reaction. + * + * @param reaction The reaction as an AST node. + * @return The corresponding reaction instance or null if the reaction does not belong to this + * reactor. + */ + public ReactionInstance lookupReactionInstance(Reaction reaction) { + for (ReactionInstance reactionInstance : reactions) { + if (reactionInstance.definition == reaction) { + return reactionInstance; + } } - - /** - * Return a list of Instantiation objects for evaluating parameter - * values. The first object in the list is the AST Instantiation - * that created this reactor instance, the second is the AST instantiation - * that created the containing reactor instance, and so on until there - * are no more containing reactor instances. This will return an empty - * list if this reactor instance is at the top level (is main). - */ - public List instantiations() { - if (_instantiations == null) { - _instantiations = new ArrayList<>(); - if (definition != null) { - _instantiations.add(definition); - if (parent != null) { - _instantiations.addAll(parent.instantiations()); - } - } - } - return _instantiations; + return null; + } + + /** + * Return the reactor instance within this reactor that has the specified instantiation. Note that + * this may be a bank of reactors. Return null if there is no such reactor instance. + */ + public ReactorInstance lookupReactorInstance(Instantiation instantiation) { + for (ReactorInstance reactorInstance : children) { + if (reactorInstance.definition == instantiation) { + return reactorInstance; + } } - - /** - * Returns true if this is a bank of reactors. - * @return true if a reactor is a bank, false otherwise - */ - public boolean isBank() { - return definition.getWidthSpec() != null; + return null; + } + + /** + * Return the timer instance within this reactor instance corresponding to the specified timer + * reference. + * + * @param timer The timer as an AST node. + * @return The corresponding timer instance or null if the timer does not belong to this reactor. + */ + public TimerInstance lookupTimerInstance(Timer timer) { + for (TimerInstance timerInstance : timers) { + if (timerInstance.definition == timer) { + return timerInstance; + } } - - /** - * Returns whether this is a main or federated reactor. - * @return true if reactor definition is marked as main or federated, false otherwise. - */ - public boolean isMainOrFederated() { - return reactorDefinition != null - && (reactorDefinition.isMain() || reactorDefinition.isFederated()); + return null; + } + + /** + * Returns the mode instance within this reactor instance corresponding to the specified mode + * reference. + * + * @param mode The mode as an AST node. + * @return The corresponding mode instance or null if the mode does not belong to this reactor. + */ + public ModeInstance lookupModeInstance(Mode mode) { + for (ModeInstance modeInstance : modes) { + if (modeInstance.definition == mode) { + return modeInstance; + } } - - /** - * Return true if the specified reactor instance is either equal to this - * reactor instance or a parent of it. - * @param r The reactor instance. - */ - public boolean isParent(ReactorInstance r) { - ReactorInstance p = this; - while (p != null) { - if (p == r) return true; - p = p.getParent(); + return null; + } + + /** Return a descriptive string. */ + @Override + public String toString() { + return "ReactorInstance " + getFullName(); + } + + /** + * Assuming that the given expression denotes a valid time, return a time value. + * + *

If the value is given as a parameter reference, this will look up the precise time value + * assigned to this reactor instance. + */ + public TimeValue getTimeValue(Expression expr) { + Expression resolved = resolveParameters(expr); + return getLiteralTimeValue(resolved); + } + + ////////////////////////////////////////////////////// + //// Protected fields. + + /** The generator that created this reactor instance. */ + protected ErrorReporter reporter; // FIXME: This accumulates a lot of redundant references + + /** The map of used built-in triggers. */ + protected Map> builtinTriggers = + new HashMap<>(); + + /** + * The LF syntax does not currently support declaring reactions unordered, but unordered reactions + * are created in the AST transformations handling federated communication and after delays. + * Unordered reactions can execute in any order and concurrently even though they are in the same + * reactor. FIXME: Remove this when the language provides syntax. + */ + protected Set unorderedReactions = new LinkedHashSet<>(); + + /** The nested list of instantiations that created this reactor instance. */ + protected List _instantiations; + + ////////////////////////////////////////////////////// + //// Protected methods. + + /** + * Create all the reaction instances of this reactor instance and record the dependencies and + * antidependencies between ports, actions, and timers and reactions. This also records the + * dependencies between reactions that follows from the order in which they are defined. + */ + protected void createReactionInstances() { + List reactions = ASTUtils.allReactions(reactorDefinition); + if (reactions != null) { + int count = 0; + + // Check for startup and shutdown triggers. + for (Reaction reaction : reactions) { + if (AttributeUtils.isUnordered(reaction)) { + unorderedReactions.add(reaction); } - return false; + // Create the reaction instance. + var reactionInstance = + new ReactionInstance(reaction, this, unorderedReactions.contains(reaction), count++); + + // Add the reaction instance to the map of reactions for this + // reactor. + this.reactions.add(reactionInstance); + } } - - /////////////////////////////////////////////////// - //// Methods for finding instances in this reactor given an AST node. - - /** - * Return the action instance within this reactor - * instance corresponding to the specified action reference. - * @param action The action as an AST node. - * @return The corresponding action instance or null if the - * action does not belong to this reactor. - */ - public ActionInstance lookupActionInstance(Action action) { - for (ActionInstance actionInstance : actions) { - if (actionInstance.definition == action) { - return actionInstance; - } - } - return null; - } - - /** - * Given a parameter definition, return the parameter instance - * corresponding to that definition, or null if there is - * no such instance. - * @param parameter The parameter definition (a syntactic object in the AST). - * @return A parameter instance, or null if there is none. - */ - public ParameterInstance lookupParameterInstance(Parameter parameter) { - for (ParameterInstance param : parameters) { - if (param.definition == parameter) { - return param; - } - } - return null; - } - - /** - * Given a port definition, return the port instance - * corresponding to that definition, or null if there is - * no such instance. - * @param port The port definition (a syntactic object in the AST). - * @return A port instance, or null if there is none. - */ - public PortInstance lookupPortInstance(Port port) { - // Search one of the inputs and outputs sets. - List ports = null; - if (port instanceof Input) { - ports = this.inputs; - } else if (port instanceof Output) { - ports = this.outputs; - } - for (PortInstance portInstance : ports) { - if (portInstance.definition == port) { - return portInstance; - } - } - return null; + } + + /** Create all the watchdog instances of this reactor instance. */ + protected void createWatchdogInstances() { + List watchdogs = ASTUtils.allWatchdogs(reactorDefinition); + if (watchdogs != null) { + for (Watchdog watchdog : watchdogs) { + // Create the watchdog instance. + var watchdogInstance = new WatchdogInstance(watchdog, this); + + // Add the watchdog instance to the list of watchdogs for this + // reactor. + this.watchdogs.add(watchdogInstance); + } } - - /** - * Given a reference to a port belonging to this reactor - * instance, return the port instance. - * Return null if there is no such instance. - * @param reference The port reference. - * @return A port instance, or null if there is none. - */ - public PortInstance lookupPortInstance(VarRef reference) { - if (!(reference.getVariable() instanceof Port)) { - // Trying to resolve something that is not a port - return null; - } - if (reference.getContainer() == null) { - // Handle local reference - return lookupPortInstance((Port) reference.getVariable()); + } + + //////////////////////////////////////// + //// Private constructors + + /** + * Create a runtime instance from the specified definition and with the specified parent that + * instantiated it. + * + * @param definition The instantiation statement in the AST. + * @param parent The parent, or null for the main rector. + * @param reporter An error reporter. + * @param desiredDepth The depth to which to expand the hierarchy. + */ + private ReactorInstance( + Instantiation definition, ReactorInstance parent, ErrorReporter reporter, int desiredDepth) { + super(definition, parent); + this.reporter = reporter; + this.reactorDeclaration = definition.getReactorClass(); + this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); + + // check for recursive instantiation + var currentParent = parent; + var foundSelfAsParent = false; + do { + if (currentParent != null) { + if (currentParent.reactorDefinition == this.reactorDefinition) { + foundSelfAsParent = true; + currentParent = null; // break } else { - // Handle hierarchical reference - var containerInstance = getChildReactorInstance(reference.getContainer()); - if (containerInstance == null) return null; - return containerInstance.lookupPortInstance((Port) reference.getVariable()); + currentParent = currentParent.parent; } - } + } + } while (currentParent != null); - /** - * Return the reaction instance within this reactor - * instance corresponding to the specified reaction. - * @param reaction The reaction as an AST node. - * @return The corresponding reaction instance or null if the - * reaction does not belong to this reactor. - */ - public ReactionInstance lookupReactionInstance(Reaction reaction) { - for (ReactionInstance reactionInstance : reactions) { - if (reactionInstance.definition == reaction) { - return reactionInstance; - } - } - return null; + this.recursive = foundSelfAsParent; + if (recursive) { + reporter.reportError(definition, "Recursive reactor instantiation."); } - /** - * Return the reactor instance within this reactor - * that has the specified instantiation. Note that this - * may be a bank of reactors. Return null if there - * is no such reactor instance. - */ - public ReactorInstance lookupReactorInstance(Instantiation instantiation) { - for (ReactorInstance reactorInstance : children) { - if (reactorInstance.definition == instantiation) { - return reactorInstance; - } - } - return null; + // If the reactor definition is null, give up here. Otherwise, diagram generation + // will fail an NPE. + if (reactorDefinition == null) { + reporter.reportError(definition, "Reactor instantiation has no matching reactor definition."); + return; } - /** - * Return the timer instance within this reactor - * instance corresponding to the specified timer reference. - * @param timer The timer as an AST node. - * @return The corresponding timer instance or null if the - * timer does not belong to this reactor. - */ - public TimerInstance lookupTimerInstance(Timer timer) { - for (TimerInstance timerInstance : timers) { - if (timerInstance.definition == timer) { - return timerInstance; - } - } - return null; - } + setInitialWidth(); - /** Returns the mode instance within this reactor - * instance corresponding to the specified mode reference. - * @param mode The mode as an AST node. - * @return The corresponding mode instance or null if the - * mode does not belong to this reactor. - */ - public ModeInstance lookupModeInstance(Mode mode) { - for (ModeInstance modeInstance : modes) { - if (modeInstance.definition == mode) { - return modeInstance; - } - } - return null; + // Apply overrides and instantiate parameters for this reactor instance. + for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { + this.parameters.add(new ParameterInstance(parameter, this)); } - /** - * Return a descriptive string. - */ - @Override - public String toString() { - return "ReactorInstance " + getFullName(); + // Instantiate inputs for this reactor instance + for (Input inputDecl : ASTUtils.allInputs(reactorDefinition)) { + this.inputs.add(new PortInstance(inputDecl, this, reporter)); } - /** - * Assuming that the given expression denotes a valid time, return a time value. - * - * If the value is given as a parameter reference, this will look up the - * precise time value assigned to this reactor instance. - */ - public TimeValue getTimeValue(Expression expr) { - Expression resolved = resolveParameters(expr); - return getLiteralTimeValue(resolved); + // Instantiate outputs for this reactor instance + for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { + this.outputs.add(new PortInstance(outputDecl, this, reporter)); } - ////////////////////////////////////////////////////// - //// Protected fields. - - /** The generator that created this reactor instance. */ - protected ErrorReporter reporter; // FIXME: This accumulates a lot of redundant references - - /** The map of used built-in triggers. */ - protected Map> builtinTriggers = new HashMap<>(); - - /** - * The LF syntax does not currently support declaring reactions unordered, - * but unordered reactions are created in the AST transformations handling - * federated communication and after delays. Unordered reactions can execute - * in any order and concurrently even though they are in the same reactor. - * FIXME: Remove this when the language provides syntax. - */ - protected Set unorderedReactions = new LinkedHashSet<>(); - - /** The nested list of instantiations that created this reactor instance. */ - protected List _instantiations; - - ////////////////////////////////////////////////////// - //// Protected methods. - - /** - * Create all the reaction instances of this reactor instance - * and record the dependencies and antidependencies - * between ports, actions, and timers and reactions. - * This also records the dependencies between reactions - * that follows from the order in which they are defined. - */ - protected void createReactionInstances() { - List reactions = ASTUtils.allReactions(reactorDefinition); - if (reactions != null) { - int count = 0; - - // Check for startup and shutdown triggers. - for (Reaction reaction : reactions) { - if (AttributeUtils.isUnordered(reaction)) { - unorderedReactions.add(reaction); - } - // Create the reaction instance. - var reactionInstance = new ReactionInstance(reaction, this, - unorderedReactions.contains(reaction), count++); - - // Add the reaction instance to the map of reactions for this - // reactor. - this.reactions.add(reactionInstance); - } + // Do not process content (except interface above) if recursive + if (!recursive && (desiredDepth < 0 || this.depth < desiredDepth)) { + // Instantiate children for this reactor instance. + // While doing this, assign an index offset to each. + for (Instantiation child : ASTUtils.allInstantiations(reactorDefinition)) { + var childInstance = new ReactorInstance(child, this, reporter, desiredDepth); + this.children.add(childInstance); + } + + // Instantiate timers for this reactor instance + for (Timer timerDecl : ASTUtils.allTimers(reactorDefinition)) { + this.timers.add(new TimerInstance(timerDecl, this)); + } + + // Instantiate actions for this reactor instance + for (Action actionDecl : ASTUtils.allActions(reactorDefinition)) { + this.actions.add(new ActionInstance(actionDecl, this)); + } + + establishPortConnections(); + + // Create the reaction instances in this reactor instance. + // This also establishes all the implied dependencies. + // Note that this can only happen _after_ the children, + // port, action, and timer instances have been created. + createReactionInstances(); + + // Instantiate modes for this reactor instance + // This must come after the child elements (reactions, etc) of this reactor + // are created in order to allow their association with modes + for (Mode modeDecl : ASTUtils.allModes(reactorDefinition)) { + this.modes.add(new ModeInstance(modeDecl, this)); + } + for (ModeInstance mode : this.modes) { + mode.setupTranstions(); + } + } + } + + /** + * Return a list of Instantiation objects for evaluating parameter values. The first object in the + * list is the AST Instantiation that created this reactor instance, the second is the AST + * instantiation that created the containing reactor instance, and so on until there are no more + * containing reactor instances. This will return an empty list if this reactor instance is at the + * top level (is main). + */ + public List instantiations() { + if (_instantiations == null) { + _instantiations = new ArrayList<>(); + if (definition != null) { + _instantiations.add(definition); + if (parent != null) { + _instantiations.addAll(parent.instantiations()); } + } } - - /** - * Returns the built-in trigger or create a new one if none exists. - */ - protected TriggerInstance getOrCreateBuiltinTrigger(BuiltinTriggerRef trigger) { - return builtinTriggers.computeIfAbsent(trigger.getType(), ref -> TriggerInstance.builtinTrigger(trigger, this)); + return _instantiations; + } + + /** + * Returns true if this is a bank of reactors. + * + * @return true if a reactor is a bank, false otherwise + */ + public boolean isBank() { + return definition.getWidthSpec() != null; + } + + /** + * Returns whether this is a main or federated reactor. + * + * @return true if reactor definition is marked as main or federated, false otherwise. + */ + public boolean isMainOrFederated() { + return reactorDefinition != null + && (reactorDefinition.isMain() || reactorDefinition.isFederated()); + } + + /** + * Return true if the specified reactor instance is either equal to this reactor instance or a + * parent of it. + * + * @param r The reactor instance. + */ + public boolean isParent(ReactorInstance r) { + ReactorInstance p = this; + while (p != null) { + if (p == r) return true; + p = p.getParent(); } - - //////////////////////////////////////// - //// Private constructors - - /** - * Create a runtime instance from the specified definition - * and with the specified parent that instantiated it. - * @param definition The instantiation statement in the AST. - * @param parent The parent, or null for the main rector. - * @param reporter An error reporter. - * @param desiredDepth The depth to which to expand the hierarchy. - */ - private ReactorInstance( - Instantiation definition, - ReactorInstance parent, - ErrorReporter reporter, - int desiredDepth) { - super(definition, parent); - this.reporter = reporter; - this.reactorDeclaration = definition.getReactorClass(); - this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); - - // check for recursive instantiation - var currentParent = parent; - var foundSelfAsParent = false; - do { - if (currentParent != null) { - if (currentParent.reactorDefinition == this.reactorDefinition) { - foundSelfAsParent = true; - currentParent = null; // break - } else { - currentParent = currentParent.parent; - } - } - } while(currentParent != null); - - this.recursive = foundSelfAsParent; - if (recursive) { - reporter.reportError(definition, "Recursive reactor instantiation."); - } - - // If the reactor definition is null, give up here. Otherwise, diagram generation - // will fail an NPE. - if (reactorDefinition == null) { - reporter.reportError(definition, "Reactor instantiation has no matching reactor definition."); - return; - } - - setInitialWidth(); - - // Apply overrides and instantiate parameters for this reactor instance. - for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { - this.parameters.add(new ParameterInstance(parameter, this)); - } - - // Instantiate inputs for this reactor instance - for (Input inputDecl : ASTUtils.allInputs(reactorDefinition)) { - this.inputs.add(new PortInstance(inputDecl, this, reporter)); - } - - // Instantiate outputs for this reactor instance - for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { - this.outputs.add(new PortInstance(outputDecl, this, reporter)); + return false; + } + + /////////////////////////////////////////////////// + //// Methods for finding instances in this reactor given an AST node. + + /** + * Return the action instance within this reactor instance corresponding to the specified action + * reference. + * + * @param action The action as an AST node. + * @return The corresponding action instance or null if the action does not belong to this + * reactor. + */ + public ActionInstance lookupActionInstance(Action action) { + for (ActionInstance actionInstance : actions) { + if (actionInstance.definition == action) { + return actionInstance; + } + } + return null; + } + + ////////////////////////////////////////////////////// + //// Private methods. + + /** + * Connect the given left port range to the given right port range. + * + *

NOTE: This method is public to enable its use in unit tests. Otherwise, it should be + * private. This is why it is defined here, in the section labeled "Private methods." + * + * @param src The source range. + * @param dst The destination range. + * @param connection The connection establishing this relationship. + */ + public static void connectPortInstances( + RuntimeRange src, RuntimeRange dst, Connection connection) { + SendRange range = new SendRange(src, dst, src._interleaved, connection); + src.instance.dependentPorts.add(range); + dst.instance.dependsOnPorts.add(src); + } + + ////////////////////////////////////////////////////// + //// Protected methods. + + /** Returns the built-in trigger or create a new one if none exists. */ + protected TriggerInstance getOrCreateBuiltinTrigger( + BuiltinTriggerRef trigger) { + return builtinTriggers.computeIfAbsent( + trigger.getType(), ref -> TriggerInstance.builtinTrigger(trigger, this)); + } + + /** + * Populate connectivity information in the port instances. Note that this can only happen _after_ + * the children and port instances have been created. Unfortunately, we have to do some + * complicated things here to support multiport-to-multiport, multiport-to-bank, and + * bank-to-multiport communication. The principle being followed is: in each connection statement, + * for each port instance on the left, connect to the next available port on the right. + */ + private void establishPortConnections() { + for (Connection connection : ASTUtils.allConnections(reactorDefinition)) { + List> leftPorts = + listPortInstances(connection.getLeftPorts(), connection); + Iterator> srcRanges = leftPorts.iterator(); + List> rightPorts = + listPortInstances(connection.getRightPorts(), connection); + Iterator> dstRanges = rightPorts.iterator(); + + // Check for empty lists. + if (!srcRanges.hasNext()) { + if (dstRanges.hasNext()) { + reporter.reportWarning(connection, "No sources to provide inputs."); } - - // Do not process content (except interface above) if recursive - if (!recursive && (desiredDepth < 0 || this.depth < desiredDepth)) { - // Instantiate children for this reactor instance. - // While doing this, assign an index offset to each. - for (Instantiation child : ASTUtils.allInstantiations(reactorDefinition)) { - var childInstance = new ReactorInstance( - child, - this, - reporter, - desiredDepth - ); - this.children.add(childInstance); - } - - // Instantiate timers for this reactor instance - for (Timer timerDecl : ASTUtils.allTimers(reactorDefinition)) { - this.timers.add(new TimerInstance(timerDecl, this)); - } - - // Instantiate actions for this reactor instance - for (Action actionDecl : ASTUtils.allActions(reactorDefinition)) { - this.actions.add(new ActionInstance(actionDecl, this)); + return; + } else if (!dstRanges.hasNext()) { + reporter.reportWarning(connection, "No destination. Outputs will be lost."); + return; + } + + RuntimeRange src = srcRanges.next(); + RuntimeRange dst = dstRanges.next(); + + while (true) { + if (dst.width == src.width) { + connectPortInstances(src, dst, connection); + if (!dstRanges.hasNext()) { + if (srcRanges.hasNext()) { + // Should not happen (checked by the validator). + reporter.reportWarning( + connection, "Source is wider than the destination. Outputs will be lost."); } - - establishPortConnections(); - - // Create the reaction instances in this reactor instance. - // This also establishes all the implied dependencies. - // Note that this can only happen _after_ the children, - // port, action, and timer instances have been created. - createReactionInstances(); - - // Instantiate modes for this reactor instance - // This must come after the child elements (reactions, etc) of this reactor - // are created in order to allow their association with modes - for (Mode modeDecl : ASTUtils.allModes(reactorDefinition)) { - this.modes.add(new ModeInstance(modeDecl, this)); + break; + } + if (!srcRanges.hasNext()) { + if (connection.isIterated()) { + srcRanges = leftPorts.iterator(); + } else { + if (dstRanges.hasNext()) { + // Should not happen (checked by the validator). + reporter.reportWarning( + connection, "Destination is wider than the source. Inputs will be missing."); + } + break; } - for (ModeInstance mode : this.modes) { - mode.setupTranstions(); + } + dst = dstRanges.next(); + src = srcRanges.next(); + } else if (dst.width < src.width) { + // Split the left (src) range in two. + connectPortInstances(src.head(dst.width), dst, connection); + src = src.tail(dst.width); + if (!dstRanges.hasNext()) { + // Should not happen (checked by the validator). + reporter.reportWarning( + connection, "Source is wider than the destination. Outputs will be lost."); + break; + } + dst = dstRanges.next(); + } else if (src.width < dst.width) { + // Split the right (dst) range in two. + connectPortInstances(src, dst.head(src.width), connection); + dst = dst.tail(src.width); + if (!srcRanges.hasNext()) { + if (connection.isIterated()) { + srcRanges = leftPorts.iterator(); + } else { + reporter.reportWarning( + connection, "Destination is wider than the source. Inputs will be missing."); + break; } + } + src = srcRanges.next(); } + } } - - ////////////////////////////////////////////////////// - //// Private methods. - - /** - * Connect the given left port range to the given right port range. - * - * NOTE: This method is public to enable its use in unit tests. - * Otherwise, it should be private. This is why it is defined here, - * in the section labeled "Private methods." - * - * @param src The source range. - * @param dst The destination range. - * @param connection The connection establishing this relationship. - */ - public static void connectPortInstances( - RuntimeRange src, - RuntimeRange dst, - Connection connection - ) { - SendRange range = new SendRange(src, dst, src._interleaved, connection); - src.instance.dependentPorts.add(range); - dst.instance.dependsOnPorts.add(src); + } + + /** + * If path exists from the specified port to any reaction in the specified set of reactions, then + * add the specified port and all ports along the path to the specified set of ports. + * + * @return True if the specified port was added. + */ + private boolean findPaths( + PortInstance port, Set reactions, Set ports) { + if (ports.contains(port)) return false; + boolean result = false; + for (ReactionInstance d : port.getDependentReactions()) { + if (reactions.contains(d)) ports.add(port); + result = true; } - - /** - * Populate connectivity information in the port instances. - * Note that this can only happen _after_ the children and port instances have been created. - * Unfortunately, we have to do some complicated things here - * to support multiport-to-multiport, multiport-to-bank, - * and bank-to-multiport communication. The principle being followed is: - * in each connection statement, for each port instance on the left, - * connect to the next available port on the right. - */ - private void establishPortConnections() { - for (Connection connection : ASTUtils.allConnections(reactorDefinition)) { - List> leftPorts = listPortInstances(connection.getLeftPorts(), connection); - Iterator> srcRanges = leftPorts.iterator(); - List> rightPorts = listPortInstances(connection.getRightPorts(), connection); - Iterator> dstRanges = rightPorts.iterator(); - - // Check for empty lists. - if (!srcRanges.hasNext()) { - if (dstRanges.hasNext()) { - reporter.reportWarning(connection, "No sources to provide inputs."); - } - return; - } else if (!dstRanges.hasNext()) { - reporter.reportWarning(connection, "No destination. Outputs will be lost."); - return; - } - - RuntimeRange src = srcRanges.next(); - RuntimeRange dst = dstRanges.next(); - - while(true) { - if (dst.width == src.width) { - connectPortInstances(src, dst, connection); - if (!dstRanges.hasNext()) { - if (srcRanges.hasNext()) { - // Should not happen (checked by the validator). - reporter.reportWarning(connection, - "Source is wider than the destination. Outputs will be lost."); - } - break; - } - if (!srcRanges.hasNext()) { - if (connection.isIterated()) { - srcRanges = leftPorts.iterator(); - } else { - if (dstRanges.hasNext()) { - // Should not happen (checked by the validator). - reporter.reportWarning(connection, - "Destination is wider than the source. Inputs will be missing."); - } - break; - } - } - dst = dstRanges.next(); - src = srcRanges.next(); - } else if (dst.width < src.width) { - // Split the left (src) range in two. - connectPortInstances(src.head(dst.width), dst, connection); - src = src.tail(dst.width); - if (!dstRanges.hasNext()) { - // Should not happen (checked by the validator). - reporter.reportWarning(connection, - "Source is wider than the destination. Outputs will be lost."); - break; - } - dst = dstRanges.next(); - } else if (src.width < dst.width) { - // Split the right (dst) range in two. - connectPortInstances(src, dst.head(src.width), connection); - dst = dst.tail(src.width); - if (!srcRanges.hasNext()) { - if (connection.isIterated()) { - srcRanges = leftPorts.iterator(); - } else { - reporter.reportWarning(connection, - "Destination is wider than the source. Inputs will be missing."); - break; - } - } - src = srcRanges.next(); - } - } + // Perform a depth-first search. + for (SendRange r : port.dependentPorts) { + for (RuntimeRange p : r.destinations) { + boolean added = findPaths(p.instance, reactions, ports); + if (added) { + result = true; + ports.add(port); } + } } - - /** - * If path exists from the specified port to any reaction in the specified - * set of reactions, then add the specified port and all ports along the path - * to the specified set of ports. - * @return True if the specified port was added. - */ - private boolean findPaths( - PortInstance port, - Set reactions, - Set ports - ) { - if (ports.contains(port)) return false; - boolean result = false; - for (ReactionInstance d : port.getDependentReactions()) { - if (reactions.contains(d)) ports.add(port); - result = true; - } - // Perform a depth-first search. - for (SendRange r : port.dependentPorts) { - for (RuntimeRange p : r.destinations) { - boolean added = findPaths(p.instance, reactions, ports); - if (added) { - result = true; - ports.add(port); - } - } - } + return result; + } + + /** + * Given a list of port references, as found on either side of a connection, return a list of the + * port instance ranges referenced. These may be multiports, and may be ports of a contained bank + * (a port representing ports of the bank members) so the returned list includes ranges of banks + * and channels. + * + *

If a given port reference has the form `interleaved(b.m)`, where `b` is a bank and `m` is a + * multiport, then the corresponding range in the returned list is marked interleaved. + * + *

For example, if `b` and `m` have width 2, without the interleaved keyword, the returned + * range represents the sequence `[b0.m0, b0.m1, b1.m0, b1.m1]`. With the interleaved marking, the + * returned range represents the sequence `[b0.m0, b1.m0, b0.m1, b1.m1]`. Both ranges will have + * width 4. + * + * @param references The variable references on one side of the connection. + * @param connection The connection. + */ + private List> listPortInstances( + List references, Connection connection) { + List> result = new ArrayList<>(); + List> tails = new LinkedList<>(); + int count = 0; + for (VarRef portRef : references) { + // Simple error checking first. + if (!(portRef.getVariable() instanceof Port)) { + reporter.reportError(portRef, "Not a port."); return result; - } - - /** - * Given a list of port references, as found on either side of a connection, - * return a list of the port instance ranges referenced. These may be multiports, - * and may be ports of a contained bank (a port representing ports of the bank - * members) so the returned list includes ranges of banks and channels. - * - * If a given port reference has the form `interleaved(b.m)`, where `b` is - * a bank and `m` is a multiport, then the corresponding range in the returned - * list is marked interleaved. - * - * For example, if `b` and `m` have width 2, without the interleaved keyword, - * the returned range represents the sequence `[b0.m0, b0.m1, b1.m0, b1.m1]`. - * With the interleaved marking, the returned range represents the sequence - * `[b0.m0, b1.m0, b0.m1, b1.m1]`. Both ranges will have width 4. - * - * @param references The variable references on one side of the connection. - * @param connection The connection. - */ - private List> listPortInstances( - List references, Connection connection - ) { - List> result = new ArrayList<>(); - List> tails = new LinkedList<>(); - int count = 0; - for (VarRef portRef : references) { - // Simple error checking first. - if (!(portRef.getVariable() instanceof Port)) { - reporter.reportError(portRef, "Not a port."); - return result; - } - // First, figure out which reactor we are dealing with. - // The reactor we want is the container of the port. - // If the port reference has no container, then the reactor is this one. - var reactor = this; - if (portRef.getContainer() != null) { - reactor = getChildReactorInstance(portRef.getContainer()); - } - // The reactor can be null only if there is an error in the code. - // Skip this portRef so that diagram synthesis can complete. - if (reactor != null) { - PortInstance portInstance = reactor.lookupPortInstance( - (Port) portRef.getVariable()); - - Set interleaved = new LinkedHashSet<>(); - if (portRef.isInterleaved()) { - // NOTE: Here, we are assuming that the interleaved() - // keyword is only allowed on the multiports contained by - // contained reactors. - interleaved.add(portInstance.parent); - } - RuntimeRange range = new RuntimeRange.Port( - portInstance, interleaved); - // If this portRef is not the last one in the references list - // then we have to check whether the range can be incremented at - // the lowest two levels (port and container). If not, - // split the range and add the tail to list to iterate over again. - // The reason for this is that the connection has only local visibility, - // but the range width may be reflective of bank structure higher - // in the hierarchy. - if (count < references.size() - 1) { - int portWidth = portInstance.width; - int portParentWidth = portInstance.parent.width; - // If the port is being connected on the inside and there is - // more than one port in the list, then we can only connect one - // bank member at a time. - if (reactor == this && references.size() > 1) { - portParentWidth = 1; - } - int widthBound = portWidth * portParentWidth; - - // If either of these widths cannot be determined, assume infinite. - if (portWidth < 0) widthBound = Integer.MAX_VALUE; - if (portParentWidth < 0) widthBound = Integer.MAX_VALUE; - - if (widthBound < range.width) { - // Need to split the range. - tails.add(range.tail(widthBound)); - range = range.head(widthBound); - } - } - result.add(range); - } + } + // First, figure out which reactor we are dealing with. + // The reactor we want is the container of the port. + // If the port reference has no container, then the reactor is this one. + var reactor = this; + if (portRef.getContainer() != null) { + reactor = getChildReactorInstance(portRef.getContainer()); + } + // The reactor can be null only if there is an error in the code. + // Skip this portRef so that diagram synthesis can complete. + if (reactor != null) { + PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); + + Set interleaved = new LinkedHashSet<>(); + if (portRef.isInterleaved()) { + // NOTE: Here, we are assuming that the interleaved() + // keyword is only allowed on the multiports contained by + // contained reactors. + interleaved.add(portInstance.parent); } - // Iterate over the tails. - while(tails.size() > 0) { - List> moreTails = new LinkedList<>(); - count = 0; - for (RuntimeRange tail : tails) { - if (count < tails.size() - 1) { - int widthBound = tail.instance.width; - if (tail._interleaved.contains(tail.instance.parent)) { - widthBound = tail.instance.parent.width; - } - // If the width cannot be determined, assume infinite. - if (widthBound < 0) widthBound = Integer.MAX_VALUE; - - if (widthBound < tail.width) { - // Need to split the range again - moreTails.add(tail.tail(widthBound)); - tail = tail.head(widthBound); - } - } - result.add(tail); - } - tails = moreTails; + RuntimeRange range = new RuntimeRange.Port(portInstance, interleaved); + // If this portRef is not the last one in the references list + // then we have to check whether the range can be incremented at + // the lowest two levels (port and container). If not, + // split the range and add the tail to list to iterate over again. + // The reason for this is that the connection has only local visibility, + // but the range width may be reflective of bank structure higher + // in the hierarchy. + if (count < references.size() - 1) { + int portWidth = portInstance.width; + int portParentWidth = portInstance.parent.width; + // If the port is being connected on the inside and there is + // more than one port in the list, then we can only connect one + // bank member at a time. + if (reactor == this && references.size() > 1) { + portParentWidth = 1; + } + int widthBound = portWidth * portParentWidth; + + // If either of these widths cannot be determined, assume infinite. + if (portWidth < 0) widthBound = Integer.MAX_VALUE; + if (portParentWidth < 0) widthBound = Integer.MAX_VALUE; + + if (widthBound < range.width) { + // Need to split the range. + tails.add(range.tail(widthBound)); + range = range.head(widthBound); + } } - return result; + result.add(range); + } } - - /** - * If this is a bank of reactors, set the width. - * It will be set to -1 if it cannot be determined. - */ - private void setInitialWidth() { - WidthSpec widthSpec = definition.getWidthSpec(); - if (widthSpec != null) { - // We need the instantiations list of the containing reactor, - // not this one. - width = ASTUtils.width(widthSpec, parent.instantiations()); + // Iterate over the tails. + while (tails.size() > 0) { + List> moreTails = new LinkedList<>(); + count = 0; + for (RuntimeRange tail : tails) { + if (count < tails.size() - 1) { + int widthBound = tail.instance.width; + if (tail._interleaved.contains(tail.instance.parent)) { + widthBound = tail.instance.parent.width; + } + // If the width cannot be determined, assume infinite. + if (widthBound < 0) widthBound = Integer.MAX_VALUE; + + if (widthBound < tail.width) { + // Need to split the range again + moreTails.add(tail.tail(widthBound)); + tail = tail.head(widthBound); + } } + result.add(tail); + } + tails = moreTails; } - - ////////////////////////////////////////////////////// - //// Private fields. - - /** - * Cached set of reactions and ports that form a causality loop. - */ - private Set> cachedCycles; - - /** - * Cached reaction graph containing reactions that form a causality loop. - */ - private ReactionInstanceGraph cachedReactionLoopGraph = null; - - /** - * Return true if this is a generated delay reactor that originates from - * an "after" delay on a connection. - * - * @return True if this is a generated delay, false otherwise. - */ - public boolean isGeneratedDelay() { - // FIXME: hacky string matching again... - if (this.definition.getReactorClass().getName().contains(DelayBodyGenerator.GEN_DELAY_CLASS_NAME)) { - return true; - } - return false; + return result; + } + + /** + * If this is a bank of reactors, set the width. It will be set to -1 if it cannot be determined. + */ + private void setInitialWidth() { + WidthSpec widthSpec = definition.getWidthSpec(); + if (widthSpec != null) { + // We need the instantiations list of the containing reactor, + // not this one. + width = ASTUtils.width(widthSpec, parent.instantiations()); + } + } + + ////////////////////////////////////////////////////// + //// Private fields. + + /** Cached set of reactions and ports that form a causality loop. */ + private Set> cachedCycles; + + /** Cached reaction graph containing reactions that form a causality loop. */ + private ReactionInstanceGraph cachedReactionLoopGraph = null; + + /** + * Return true if this is a generated delay reactor that originates from an "after" delay on a + * connection. + * + * @return True if this is a generated delay, false otherwise. + */ + public boolean isGeneratedDelay() { + if (this.definition + .getReactorClass() + .getName() + .contains(DelayBodyGenerator.GEN_DELAY_CLASS_NAME)) { + return true; } + return false; + } } diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java new file mode 100644 index 0000000000..ca5cb3d192 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -0,0 +1,63 @@ +package org.lflang.generator; + +import org.lflang.TimeValue; +import org.lflang.lf.Watchdog; + +/** + * Instance of a watchdog. Upon creation the actual delay is converted into a proper time value. If + * a parameter is referenced, it is looked up in the given (grand)parent reactor instance. + * + * @author{Benjamin Asch } + */ +public class WatchdogInstance { + + /** Create a new watchdog instance associated with the given reactor instance. */ + public WatchdogInstance(Watchdog definition, ReactorInstance reactor) { + if (definition.getTimeout() != null) { + // Get the timeout value given in the watchdog declaration. + this.timeout = reactor.getTimeValue(definition.getTimeout()); + } else { + // NOTE: The grammar does not allow the timeout to be omitted, so this should not occur. + this.timeout = TimeValue.ZERO; + } + + this.name = definition.getName().toString(); + this.definition = definition; + this.reactor = reactor; + } + + ////////////////////////////////////////////////////// + //// Public methods. + + public String getName() { + return this.name; + } + + public Watchdog getDefinition() { + return this.definition; + } + + public TimeValue getTimeout() { + return (TimeValue) this.timeout; + } + + public ReactorInstance getReactor() { + return this.reactor; + } + + @Override + public String toString() { + return "WatchdogInstance " + name + "(" + timeout.toString() + ")"; + } + + ////////////////////////////////////////////////////// + //// Private fields. + + private final TimeValue timeout; + + private final String name; + + private final Watchdog definition; + + private final ReactorInstance reactor; +} diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index f598219ddd..96a230acc4 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1,26 +1,22 @@ /************* -Copyright (c) 2019-2021, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - 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 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ + * Copyright (c) 2019-2021, The University of California at Berkeley. + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * 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 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ package org.lflang.generator.c; @@ -35,6 +31,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import static org.lflang.ASTUtils.toText; import static org.lflang.util.StringUtil.addDoubleQuotes; +import com.google.common.base.Objects; +import com.google.common.collect.Iterables; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -45,33 +43,27 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.StringExtensions; - import org.lflang.ASTUtils; -import org.lflang.generator.DockerComposeGenerator; import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; - -import org.lflang.federated.extensions.CExtensionUtils; - +import org.lflang.TimeValue; import org.lflang.ast.DelayedConnectionTransformation; - +import org.lflang.federated.extensions.CExtensionUtils; import org.lflang.generator.ActionInstance; import org.lflang.generator.CodeBuilder; +import org.lflang.generator.DelayBodyGenerator; +import org.lflang.generator.DockerComposeGenerator; import org.lflang.generator.DockerGenerator; import org.lflang.generator.GeneratorBase; import org.lflang.generator.GeneratorResult; import org.lflang.generator.GeneratorUtils; - -import org.lflang.generator.DelayBodyGenerator; - import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.LFResource; import org.lflang.generator.ParameterInstance; @@ -81,6 +73,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TargetTypes; import org.lflang.generator.TimerInstance; import org.lflang.generator.TriggerInstance; +import org.lflang.generator.WatchdogInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Input; @@ -94,205 +87,172 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.ReactorDecl; import org.lflang.lf.StateVar; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.util.ArduinoUtil; import org.lflang.util.FileUtil; -import com.google.common.base.Objects; -import com.google.common.collect.Iterables; - /** - * Generator for C target. This class generates C code defining each reactor - * class given in the input .lf file and imported .lf files. The generated code - * has the following components: + * Generator for C target. This class generates C code defining each reactor class given in the + * input .lf file and imported .lf files. The generated code has the following components: * - * * A typedef for inputs, outputs, and actions of each reactor class. These - * define the types of the variables that reactions use to access inputs and - * action values and to set output values. + *

* A typedef for inputs, outputs, and actions of each reactor class. These define the types of + * the variables that reactions use to access inputs and action values and to set output values. * - * * A typedef for a "self" struct for each reactor class. One instance of this - * struct will be created for each reactor instance. See below for details. + *

* A typedef for a "self" struct for each reactor class. One instance of this struct will be + * created for each reactor instance. See below for details. * - * * A function definition for each reaction in each reactor class. These - * functions take an instance of the self struct as an argument. + *

* A function definition for each reaction in each reactor class. These functions take an + * instance of the self struct as an argument. * - * * A constructor function for each reactor class. This is used to create - * a new instance of the reactor. + *

* A constructor function for each reactor class. This is used to create a new instance of the + * reactor. * - * After these, the main generated function is `_lf_initialize_trigger_objects()`. - * This function creates the instances of reactors (using their constructors) - * and makes connections between them. + *

After these, the main generated function is `_lf_initialize_trigger_objects()`. This function + * creates the instances of reactors (using their constructors) and makes connections between them. * - * A few other smaller functions are also generated. + *

A few other smaller functions are also generated. * - * ## Self Struct + *

## Self Struct * - * The "self" struct has fields for each of the following: + *

The "self" struct has fields for each of the following: * - * * parameter: the field name and type match the parameter. - * * state: the field name and type match the state. - * * action: the field name prepends the action name with "_lf_". - * A second field for the action is also created to house the trigger_t object. - * That second field prepends the action name with "_lf__". - * * output: the field name prepends the output name with "_lf_". - * * input: the field name prepends the output name with "_lf_". - * A second field for the input is also created to house the trigger_t object. - * That second field prepends the input name with "_lf__". + *

* parameter: the field name and type match the parameter. * state: the field name and type + * match the state. * action: the field name prepends the action name with "_lf_". A second field + * for the action is also created to house the trigger_t object. That second field prepends the + * action name with "_lf__". * output: the field name prepends the output name with "_lf_". * input: + * the field name prepends the output name with "_lf_". A second field for the input is also created + * to house the trigger_t object. That second field prepends the input name with "_lf__". * - * If, in addition, the reactor contains other reactors and reacts to their outputs, - * then there will be a struct within the self struct for each such contained reactor. - * The name of that self struct will be the name of the contained reactor prepended with "_lf_". - * That inside struct will contain pointers the outputs of the contained reactors - * that are read together with pointers to booleans indicating whether those outputs are present. + *

If, in addition, the reactor contains other reactors and reacts to their outputs, then there + * will be a struct within the self struct for each such contained reactor. The name of that self + * struct will be the name of the contained reactor prepended with "_lf_". That inside struct will + * contain pointers the outputs of the contained reactors that are read together with pointers to + * booleans indicating whether those outputs are present. * - * If, in addition, the reactor has a reaction to shutdown, then there will be a pointer to - * trigger_t object (see reactor.h) for the shutdown event and an action struct named - * _lf_shutdown on the self struct. + *

If, in addition, the reactor has a reaction to shutdown, then there will be a pointer to + * trigger_t object (see reactor.h) for the shutdown event and an action struct named _lf_shutdown + * on the self struct. * - * ## Reaction Functions + *

## Reaction Functions * - * For each reaction in a reactor class, this generator will produce a C function - * that expects a pointer to an instance of the "self" struct as an argument. - * This function will contain verbatim the C code specified in the reaction, but - * before that C code, the generator inserts a few lines of code that extract from the - * self struct the variables that that code has declared it will use. For example, if - * the reaction declares that it is triggered by or uses an input named "x" of type - * int, the function will contain a line like this: - * ``` - * r_x_t* x = self->_lf_x; - * ``` - * where `r` is the full name of the reactor class and the struct type `r_x_t` - * has fields `is_present` and `value`, where the type of `value` matches the port type. - * If the programmer fails to declare that it uses x, then the absence of the - * above code will trigger a compile error when the verbatim code attempts to read `x`. + *

For each reaction in a reactor class, this generator will produce a C function that expects a + * pointer to an instance of the "self" struct as an argument. This function will contain verbatim + * the C code specified in the reaction, but before that C code, the generator inserts a few lines + * of code that extract from the self struct the variables that that code has declared it will use. + * For example, if the reaction declares that it is triggered by or uses an input named "x" of type + * int, the function will contain a line like this: ``` r_x_t* x = self->_lf_x; ``` where `r` is the + * full name of the reactor class and the struct type `r_x_t` has fields `is_present` and `value`, + * where the type of `value` matches the port type. If the programmer fails to declare that it uses + * x, then the absence of the above code will trigger a compile error when the verbatim code + * attempts to read `x`. * - * ## Constructor + *

## Constructor * - * For each reactor class, this generator will create a constructor function named - * `new_r`, where `r` is the reactor class name. This function will malloc and return - * a pointer to an instance of the "self" struct. This struct initially represents - * an unconnected reactor. To establish connections between reactors, additional - * information needs to be inserted (see below). The self struct is made visible - * to the body of a reaction as a variable named "self". The self struct contains the - * following: + *

For each reactor class, this generator will create a constructor function named `new_r`, where + * `r` is the reactor class name. This function will malloc and return a pointer to an instance of + * the "self" struct. This struct initially represents an unconnected reactor. To establish + * connections between reactors, additional information needs to be inserted (see below). The self + * struct is made visible to the body of a reaction as a variable named "self". The self struct + * contains the following: * - * * Parameters: For each parameter `p` of the reactor, there will be a field `p` - * with the type and value of the parameter. So C code in the body of a reaction - * can access parameter values as `self->p`. + *

* Parameters: For each parameter `p` of the reactor, there will be a field `p` with the type + * and value of the parameter. So C code in the body of a reaction can access parameter values as + * `self->p`. * - * * State variables: For each state variable `s` of the reactor, there will be a field `s` - * with the type and value of the state variable. So C code in the body of a reaction - * can access state variables as `self->s`. + *

* State variables: For each state variable `s` of the reactor, there will be a field `s` with + * the type and value of the state variable. So C code in the body of a reaction can access state + * variables as `self->s`. * - * The self struct also contains various fields that the user is not intended to - * use. The names of these fields begin with at least two underscores. They are: + *

The self struct also contains various fields that the user is not intended to use. The names + * of these fields begin with at least two underscores. They are: * - * * Outputs: For each output named `out`, there will be a field `_lf_out` that is - * a struct containing a value field whose type matches that of the output. - * The output value is stored here. That struct also has a field `is_present` - * that is a boolean indicating whether the output has been set. - * This field is reset to false at the start of every time - * step. There is also a field `num_destinations` whose value matches the - * number of downstream reactors that use this variable. This field must be - * set when connections are made or changed. It is used to determine for - * a mutable input destination whether a copy needs to be made. + *

* Outputs: For each output named `out`, there will be a field `_lf_out` that is a struct + * containing a value field whose type matches that of the output. The output value is stored here. + * That struct also has a field `is_present` that is a boolean indicating whether the output has + * been set. This field is reset to false at the start of every time step. There is also a field + * `num_destinations` whose value matches the number of downstream reactors that use this variable. + * This field must be set when connections are made or changed. It is used to determine for a + * mutable input destination whether a copy needs to be made. * - * * Inputs: For each input named `in` of type T, there is a field named `_lf_in` - * that is a pointer struct with a value field of type T. The struct pointed - * to also has an `is_present` field of type bool that indicates whether the - * input is present. + *

* Inputs: For each input named `in` of type T, there is a field named `_lf_in` that is a + * pointer struct with a value field of type T. The struct pointed to also has an `is_present` field + * of type bool that indicates whether the input is present. * - * * Outputs of contained reactors: If a reactor reacts to outputs of a - * contained reactor `r`, then the self struct will contain a nested struct - * named `_lf_r` that has fields pointing to those outputs. For example, - * if `r` has an output `out` of type T, then there will be field in `_lf_r` - * named `out` that points to a struct containing a value field - * of type T and a field named `is_present` of type bool. + *

* Outputs of contained reactors: If a reactor reacts to outputs of a contained reactor `r`, + * then the self struct will contain a nested struct named `_lf_r` that has fields pointing to those + * outputs. For example, if `r` has an output `out` of type T, then there will be field in `_lf_r` + * named `out` that points to a struct containing a value field of type T and a field named + * `is_present` of type bool. * - * * Inputs of contained reactors: If a reactor sends to inputs of a - * contained reactor `r`, then the self struct will contain a nested struct - * named `_lf_r` that has fields for storing the values provided to those - * inputs. For example, if R has an input `in` of type T, then there will - * be field in _lf_R named `in` that is a struct with a value field - * of type T and a field named `is_present` of type bool. + *

* Inputs of contained reactors: If a reactor sends to inputs of a contained reactor `r`, then + * the self struct will contain a nested struct named `_lf_r` that has fields for storing the values + * provided to those inputs. For example, if R has an input `in` of type T, then there will be field + * in _lf_R named `in` that is a struct with a value field of type T and a field named `is_present` + * of type bool. * - * * Actions: If the reactor has an action a (logical or physical), then there - * will be a field in the self struct named `_lf_a` and another named `_lf__a`. - * The type of the first is specific to the action and contains a `value` - * field with the type and value of the action (if it has a value). That - * struct also has a `has_value` field, an `is_present` field, and a - * `token` field (which is NULL if the action carries no value). - * The `_lf__a` field is of type trigger_t. - * That struct contains various things, including an array of reactions - * sensitive to this trigger and a lf_token_t struct containing the value of - * the action, if it has a value. See reactor.h in the C library for - * details. + *

* Actions: If the reactor has an action a (logical or physical), then there will be a field in + * the self struct named `_lf_a` and another named `_lf__a`. The type of the first is specific to + * the action and contains a `value` field with the type and value of the action (if it has a + * value). That struct also has a `has_value` field, an `is_present` field, and a `token` field + * (which is NULL if the action carries no value). The `_lf__a` field is of type trigger_t. That + * struct contains various things, including an array of reactions sensitive to this trigger and a + * lf_token_t struct containing the value of the action, if it has a value. See reactor.h in the C + * library for details. * - * * Reactions: Each reaction will have several fields in the self struct. - * Each of these has a name that begins with `_lf__reaction_i`, where i is - * the number of the reaction, starting with 0. The fields are: - * * _lf__reaction_i: The struct that is put onto the reaction queue to - * execute the reaction (see reactor.h in the C library). + *

* Reactions: Each reaction will have several fields in the self struct. Each of these has a + * name that begins with `_lf__reaction_i`, where i is the number of the reaction, starting with 0. + * The fields are: * _lf__reaction_i: The struct that is put onto the reaction queue to execute the + * reaction (see reactor.h in the C library). * - * * Timers: For each timer t, there is are two fields in the self struct: - * * _lf__t: The trigger_t struct for this timer (see reactor.h). - * * _lf__t_reactions: An array of reactions (pointers to the - * reaction_t structs on this self struct) sensitive to this timer. + *

* Timers: For each timer t, there is are two fields in the self struct: * _lf__t: The + * trigger_t struct for this timer (see reactor.h). * _lf__t_reactions: An array of reactions + * (pointers to the reaction_t structs on this self struct) sensitive to this timer. * - * * Triggers: For each Timer, Action, Input, and Output of a contained - * reactor that triggers reactions, there will be a trigger_t struct - * on the self struct with name `_lf__t`, where t is the name of the trigger. + *

* Triggers: For each Timer, Action, Input, and Output of a contained reactor that triggers + * reactions, there will be a trigger_t struct on the self struct with name `_lf__t`, where t is the + * name of the trigger. * - * ## Connections Between Reactors + *

## Connections Between Reactors * - * Establishing connections between reactors involves two steps. - * First, each destination (e.g. an input port) must have pointers to - * the source (the output port). As explained above, for an input named - * `in`, the field `_lf_in->value` is a pointer to the output data being read. - * In addition, `_lf_in->is_present` is a pointer to the corresponding - * `out->is_present` field of the output reactor's self struct. + *

Establishing connections between reactors involves two steps. First, each destination (e.g. an + * input port) must have pointers to the source (the output port). As explained above, for an input + * named `in`, the field `_lf_in->value` is a pointer to the output data being read. In addition, + * `_lf_in->is_present` is a pointer to the corresponding `out->is_present` field of the output + * reactor's self struct. * - * In addition, the `reaction_i` struct on the self struct has a `triggers` - * field that records all the trigger_t structs for ports and actions - * that are triggered by the i-th reaction. The triggers field is - * an array of arrays of pointers to trigger_t structs. - * The length of the outer array is the number of output channels - * (single ports plus multiport widths) that the reaction effects - * plus the number of input port channels of contained - * reactors that it effects. Each inner array has a length equal to the - * number of final destinations of that output channel or input channel. - * The reaction_i struct has an array triggered_sizes that indicates - * the sizes of these inner arrays. The num_outputs field of the - * reaction_i struct gives the length of the triggered_sizes and - * (outer) triggers arrays. The num_outputs field is equal to the - * total number of single ports and multiport channels that the reaction - * writes to. + *

In addition, the `reaction_i` struct on the self struct has a `triggers` field that records + * all the trigger_t structs for ports and actions that are triggered by the i-th reaction. The + * triggers field is an array of arrays of pointers to trigger_t structs. The length of the outer + * array is the number of output channels (single ports plus multiport widths) that the reaction + * effects plus the number of input port channels of contained reactors that it effects. Each inner + * array has a length equal to the number of final destinations of that output channel or input + * channel. The reaction_i struct has an array triggered_sizes that indicates the sizes of these + * inner arrays. The num_outputs field of the reaction_i struct gives the length of the + * triggered_sizes and (outer) triggers arrays. The num_outputs field is equal to the total number + * of single ports and multiport channels that the reaction writes to. * - * ## Runtime Tables + *

## Runtime Tables * - * This generator creates an populates the following tables used at run time. - * These tables may have to be resized and adjusted when mutations occur. + *

This generator creates an populates the following tables used at run time. These tables may + * have to be resized and adjusted when mutations occur. * - * * _lf_is_present_fields: An array of pointers to booleans indicating whether an - * event is present. The _lf_start_time_step() function in reactor_common.c uses - * this to mark every event absent at the start of a time step. The size of this - * table is contained in the variable _lf_is_present_fields_size. - * * This table is accompanied by another list, _lf_is_present_fields_abbreviated, - * which only contains the is_present fields that have been set to true in the - * current tag. This list can allow a performance improvement if most ports are - * seldom present because only fields that have been set to true need to be - * reset to false. + *

* _lf_is_present_fields: An array of pointers to booleans indicating whether an event is + * present. The _lf_start_time_step() function in reactor_common.c uses this to mark every event + * absent at the start of a time step. The size of this table is contained in the variable + * _lf_is_present_fields_size. * This table is accompanied by another list, + * _lf_is_present_fields_abbreviated, which only contains the is_present fields that have been set + * to true in the current tag. This list can allow a performance improvement if most ports are + * seldom present because only fields that have been set to true need to be reset to false. * - * * _lf_shutdown_triggers: An array of pointers to trigger_t structs for shutdown - * reactions. The length of this table is in the _lf_shutdown_triggers_size - * variable. + *

* _lf_shutdown_triggers: An array of pointers to trigger_t structs for shutdown reactions. The + * length of this table is in the _lf_shutdown_triggers_size variable. * - * * _lf_timer_triggers: An array of pointers to trigger_t structs for timers that - * need to be started when the program runs. The length of this table is in the - * _lf_timer_triggers_size variable. + *

* _lf_timer_triggers: An array of pointers to trigger_t structs for timers that need to be + * started when the program runs. The length of this table is in the _lf_timer_triggers_size + * variable. * - * * _lf_action_table: For a federated execution, each federate will have this table - * that maps port IDs to the corresponding action struct, which can be cast to - * action_base_t. + *

* _lf_action_table: For a federated execution, each federate will have this table that maps + * port IDs to the corresponding action struct, which can be cast to action_base_t. * * @author Edward A. Lee * @author Marten Lohstroh @@ -306,1123 +266,1133 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ @SuppressWarnings("StaticPseudoFunctionalStyleMethod") public class CGenerator extends GeneratorBase { - // Regular expression pattern for compiler error messages with resource - // and line number information. The first match will a resource URI in the - // form of "file:/path/file.lf". The second match will be a line number. - // The third match is a character position within the line. - // The fourth match will be the error message. - static final Pattern compileErrorPattern = Pattern.compile( - "^(?.*):(?\\d+):(?\\d+):(?.*)$" - ); - - public static int UNDEFINED_MIN_SPACING = -1; - - //////////////////////////////////////////// - //// Protected fields - - /** The main place to put generated code. */ - protected CodeBuilder code = new CodeBuilder(); - - /** Place to collect code to initialize the trigger objects for all reactor instances. */ - protected CodeBuilder initializeTriggerObjects = new CodeBuilder(); - - protected final CFileConfig fileConfig; - - /** - * Count of the number of is_present fields of the self struct that - * need to be reinitialized in _lf_start_time_step(). - */ - protected int startTimeStepIsPresentCount = 0; - - //////////////////////////////////////////// - //// Private fields - /** - * Extra lines that need to go into the generated CMakeLists.txt. - */ - private String cMakeExtras = ""; - - /** Place to collect code to execute at the start of a time step. */ - private CodeBuilder startTimeStep = new CodeBuilder(); - - /** Count of the number of token pointers that need to have their - * reference count decremented in _lf_start_time_step(). - */ - private int timerCount = 0; - private int startupReactionCount = 0; - private int shutdownReactionCount = 0; - private int resetReactionCount = 0; - private int modalReactorCount = 0; - private int modalStateResetCount = 0; - - // Indicate whether the generator is in Cpp mode or not - private final boolean CCppMode; - - private final CTypes types; - - private final CCmakeGenerator cmakeGenerator; - - protected CGenerator( - LFGeneratorContext context, - boolean CCppMode, - CTypes types, - CCmakeGenerator cmakeGenerator, - DelayBodyGenerator delayBodyGenerator - ) { - super(context); - this.fileConfig = (CFileConfig) context.getFileConfig(); - this.CCppMode = CCppMode; - this.types = types; - this.cmakeGenerator = cmakeGenerator; - - // Register the delayed connection transformation to be applied by GeneratorBase. - // transform both after delays and physical connections - registerTransformation(new DelayedConnectionTransformation(delayBodyGenerator, types, fileConfig.resource, true, true)); + // Regular expression pattern for compiler error messages with resource + // and line number information. The first match will a resource URI in the + // form of "file:/path/file.lf". The second match will be a line number. + // The third match is a character position within the line. + // The fourth match will be the error message. + static final Pattern compileErrorPattern = + Pattern.compile("^(?.*):(?\\d+):(?\\d+):(?.*)$"); + + public static int UNDEFINED_MIN_SPACING = -1; + + //////////////////////////////////////////// + //// Protected fields + + /** The main place to put generated code. */ + protected CodeBuilder code = new CodeBuilder(); + + /** Place to collect code to initialize the trigger objects for all reactor instances. */ + protected CodeBuilder initializeTriggerObjects = new CodeBuilder(); + + protected final CFileConfig fileConfig; + + /** + * Count of the number of is_present fields of the self struct that need to be reinitialized in + * _lf_start_time_step(). + */ + protected int startTimeStepIsPresentCount = 0; + + //////////////////////////////////////////// + //// Private fields + /** Extra lines that need to go into the generated CMakeLists.txt. */ + private String cMakeExtras = ""; + + /** Place to collect code to execute at the start of a time step. */ + private CodeBuilder startTimeStep = new CodeBuilder(); + + /** + * Count of the number of token pointers that need to have their reference count decremented in + * _lf_start_time_step(). + */ + private int timerCount = 0; + + private int startupReactionCount = 0; + private int shutdownReactionCount = 0; + private int resetReactionCount = 0; + private int modalReactorCount = 0; + private int modalStateResetCount = 0; + private int watchdogCount = 0; + + // Indicate whether the generator is in Cpp mode or not + private final boolean CCppMode; + + private final CTypes types; + + private final CCmakeGenerator cmakeGenerator; + + protected CGenerator( + LFGeneratorContext context, + boolean CCppMode, + CTypes types, + CCmakeGenerator cmakeGenerator, + DelayBodyGenerator delayBodyGenerator) { + super(context); + this.fileConfig = (CFileConfig) context.getFileConfig(); + this.CCppMode = CCppMode; + this.types = types; + this.cmakeGenerator = cmakeGenerator; + + // Register the delayed connection transformation to be applied by GeneratorBase. + // transform both after delays and physical connections + registerTransformation( + new DelayedConnectionTransformation( + delayBodyGenerator, types, fileConfig.resource, true, true)); + } + + public CGenerator(LFGeneratorContext context, boolean ccppMode) { + this( + context, + ccppMode, + new CTypes(), + new CCmakeGenerator(context.getFileConfig(), List.of()), + new CDelayBodyGenerator(new CTypes())); + } + + /** + * Look for physical actions in all resources. If found, set threads to be at least one to allow + * asynchronous schedule calls. + */ + public void accommodatePhysicalActionsIfPresent() { + // If there are any physical actions, ensure the threaded engine is used and that + // keepalive is set to true, unless the user has explicitly set it to false. + for (Resource resource : GeneratorUtils.getResources(reactors)) { + for (Action action : ASTUtils.allElementsOfClass(resource, Action.class)) { + if (Objects.equal(action.getOrigin(), ActionOrigin.PHYSICAL)) { + // If the unthreaded runtime is not requested by the user, use the threaded runtime + // instead + // because it is the only one currently capable of handling asynchronous events. + if (!targetConfig.threading + && !targetConfig.setByUser.contains(TargetProperty.THREADING)) { + targetConfig.threading = true; + errorReporter.reportWarning( + action, + "Using the threaded C runtime to allow for asynchronous handling of physical action" + + " " + + action.getName()); + return; + } + } + } } - - public CGenerator(LFGeneratorContext context, boolean ccppMode) { - this( - context, - ccppMode, - new CTypes(), - new CCmakeGenerator(context.getFileConfig(), List.of()), - new CDelayBodyGenerator(new CTypes()) - ); + } + + /** + * Return true if the host operating system is compatible and otherwise report an error and return + * false. + */ + protected boolean isOSCompatible() { + if (GeneratorUtils.isHostWindows()) { + if (CCppMode) { + errorReporter.reportError( + "LF programs with a CCpp target are currently not supported on Windows. " + + "Exiting code generation."); + // FIXME: The incompatibility between our C runtime code and the + // Visual Studio compiler is extensive. + return false; + } } + return true; + } + + /** + * Generate C code from the Lingua Franca model contained by the specified resource. This is the + * main entry point for code generation. + * + * @param resource The resource containing the source code. + * @param context The context in which the generator is invoked, including whether it is cancelled + * and whether it is a standalone context + */ + @Override + public void doGenerate(Resource resource, LFGeneratorContext context) { + super.doGenerate(resource, context); + if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return; + if (!isOSCompatible()) return; // Incompatible OS and configuration - /** - * Look for physical actions in all resources. - * If found, set threads to be at least one to allow asynchronous schedule calls. - */ - public void accommodatePhysicalActionsIfPresent() { - // If there are any physical actions, ensure the threaded engine is used and that - // keepalive is set to true, unless the user has explicitly set it to false. - for (Resource resource : GeneratorUtils.getResources(reactors)) { - for (Action action : ASTUtils.allElementsOfClass(resource, Action.class)) { - if (Objects.equal(action.getOrigin(), ActionOrigin.PHYSICAL)) { - // If the unthreaded runtime is not requested by the user, use the threaded runtime instead - // because it is the only one currently capable of handling asynchronous events. - if (!targetConfig.threading && !targetConfig.setByUser.contains(TargetProperty.THREADING)) { - targetConfig.threading = true; - errorReporter.reportWarning( - action, - "Using the threaded C runtime to allow for asynchronous handling of physical action " + - action.getName() - ); - return; - } - } - } - } + // Perform set up that does not generate code + setUpGeneralParameters(); + + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getIncludePath().toFile()); + handleProtoFiles(); + + // Derive target filename from the .lf filename. + var lfModuleName = fileConfig.name; + var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); + var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; + try { + generateCodeFor(lfModuleName); + copyTargetFiles(); + generateHeaders(); + code.writeToFile(targetFile); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); } - /** - * Return true if the host operating system is compatible and - * otherwise report an error and return false. - */ - protected boolean isOSCompatible() { - if (GeneratorUtils.isHostWindows()) { - if (CCppMode) { - errorReporter.reportError( - "LF programs with a CCpp target are currently not supported on Windows. " + - "Exiting code generation." - ); - // FIXME: The incompatibility between our C runtime code and the - // Visual Studio compiler is extensive. - return false; - } - } - return true; + // Create docker file. + if (targetConfig.dockerOptions != null && mainDef != null) { + try { + var dockerData = getDockerGenerator(context).generateDockerData(); + dockerData.writeDockerFile(); + (new DockerComposeGenerator(context)).writeDockerComposeFile(List.of(dockerData)); + } catch (IOException e) { + throw new RuntimeException("Error while writing Docker files", e); + } } - /** - * Generate C code from the Lingua Franca model contained by the - * specified resource. This is the main entry point for code - * generation. - * @param resource The resource containing the source code. - * @param context The context in which the generator is - * invoked, including whether it is cancelled and - * whether it is a standalone context - */ - @Override - public void doGenerate(Resource resource, LFGeneratorContext context) { - super.doGenerate(resource, context); - if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return; - if (!isOSCompatible()) return; // Incompatible OS and configuration - - // Perform set up that does not generate code - setUpGeneralParameters(); - - FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); - FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); - FileUtil.createDirectoryIfDoesNotExist(fileConfig.getIncludePath().toFile()); - handleProtoFiles(); - - // Derive target filename from the .lf filename. - var lfModuleName = fileConfig.name; - var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); - var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; - try { - generateCodeFor(lfModuleName); - copyTargetFiles(); - generateHeaders(); - code.writeToFile(targetFile); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - - // Create docker file. - if (targetConfig.dockerOptions != null && mainDef != null) { - try { - var dockerData = getDockerGenerator(context).generateDockerData(); - dockerData.writeDockerFile(); - (new DockerComposeGenerator(context)).writeDockerComposeFile(List.of(dockerData)); - } catch (IOException e) { - throw new RuntimeException("Error while writing Docker files", e); - } - } - - // If cmake is requested, generate the CMakeLists.txt - if (targetConfig.platformOptions.platform != Platform.ARDUINO) { - var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; - var sources = new HashSet<>(ASTUtils.recursiveChildren(main)).stream() - .map(CUtil::getName).map(it -> it + (CCppMode ? ".cpp" : ".c")) - .collect(Collectors.toList()); - sources.add(cFilename); - var cmakeCode = cmakeGenerator.generateCMakeCode( - sources, - lfModuleName, - errorReporter, - CCppMode, - mainDef != null, - cMakeExtras, - targetConfig - ); - try { - cmakeCode.writeToFile(cmakeFile); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - } else { - try { - Path include = fileConfig.getSrcGenPath().resolve("include/"); - Path src = fileConfig.getSrcGenPath().resolve("src/"); - FileUtil.arduinoDeleteHelper(src, targetConfig.threading); - FileUtil.relativeIncludeHelper(src, include); - FileUtil.relativeIncludeHelper(include, include); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - - if (!targetConfig.noCompile) { - ArduinoUtil arduinoUtil = new ArduinoUtil(context, commandFactory, errorReporter); - arduinoUtil.buildArduino(fileConfig, targetConfig); - context.finish( - GeneratorResult.Status.COMPILED, null - ); - } else { - System.out.println("********"); - System.out.println("To compile your program, run the following command to see information about the board you plugged in:\n\n\tarduino-cli board list\n\nGrab the FQBN and PORT from the command and run the following command in the generated sources directory:\n\n\tarduino-cli compile -b --build-property compiler.c.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' --build-property compiler.cpp.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' .\n\nTo flash/upload your generated sketch to the board, run the following command in the generated sources directory:\n\n\tarduino-cli upload -b -p \n"); - // System.out.println("For a list of all boards installed on your computer, you can use the following command:\n\n\tarduino-cli board listall\n"); - context.finish( - GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null) - ); - } - GeneratorUtils.refreshProject(resource, context.getMode()); - return; - } - - // Dump the additional compile definitions to a file to keep the generated project - // self-contained. In this way, third-party build tools like PlatformIO, west, arduino-cli can - // take over and do the rest of compilation. - try { - String compileDefs = targetConfig.compileDefinitions.keySet().stream() - .map(key -> key + "=" + targetConfig.compileDefinitions.get(key)) - .collect(Collectors.joining("\n")); - FileUtil.writeToFile( - compileDefs, - Path.of(fileConfig.getSrcGenPath() + File.separator + "CompileDefinitions.txt") - ); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } - - // If this code generator is directly compiling the code, compile it now so that we - // clean it up after, removing the #line directives after errors have been reported. - if ( - !targetConfig.noCompile && targetConfig.dockerOptions == null - && IterableExtensions.isNullOrEmpty(targetConfig.buildCommands) - // This code is unreachable in LSP_FAST mode, so that check is omitted. - && context.getMode() != LFGeneratorContext.Mode.LSP_MEDIUM - ) { - // FIXME: Currently, a lack of main is treated as a request to not produce - // a binary and produce a .o file instead. There should be a way to control - // this. - // Create an anonymous Runnable class and add it to the compileThreadPool - // so that compilation can happen in parallel. - var cleanCode = code.removeLines("#line"); - - var execName = lfModuleName; - var threadFileConfig = fileConfig; - var generator = this; // FIXME: currently only passed to report errors with line numbers in the Eclipse IDE - var CppMode = CCppMode; - // generatingContext.reportProgress( - // String.format("Generated code for %d/%d executables. Compiling...", federateCount, federates.size()), - // 100 * federateCount / federates.size() - // ); // FIXME: Move to FedGenerator - // Create the compiler to be used later - - var cCompiler = new CCompiler(targetConfig, threadFileConfig, errorReporter, CppMode); - try { - if (!cCompiler.runCCompiler(generator, context)) { - // If compilation failed, remove any bin files that may have been created. - CUtil.deleteBinFiles(threadFileConfig); - // If finish has already been called, it is illegal and makes no sense. However, - // if finish has already been called, then this must be a federated execution. - context.unsuccessfulFinish(); - } else { - context.finish( - GeneratorResult.Status.COMPILED, null - ); - } - cleanCode.writeToFile(targetFile); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } + // If cmake is requested, generate the CMakeLists.txt + if (targetConfig.platformOptions.platform != Platform.ARDUINO) { + var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; + var sources = + new HashSet<>(ASTUtils.recursiveChildren(main)) + .stream() + .map(CUtil::getName) + .map(it -> it + (CCppMode ? ".cpp" : ".c")) + .collect(Collectors.toList()); + sources.add(cFilename); + var cmakeCode = + cmakeGenerator.generateCMakeCode( + sources, + lfModuleName, + errorReporter, + CCppMode, + mainDef != null, + cMakeExtras, + targetConfig); + try { + cmakeCode.writeToFile(cmakeFile); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); + } + } else { + try { + Path include = fileConfig.getSrcGenPath().resolve("include/"); + Path src = fileConfig.getSrcGenPath().resolve("src/"); + FileUtil.arduinoDeleteHelper(src, targetConfig.threading); + FileUtil.relativeIncludeHelper(src, include); + FileUtil.relativeIncludeHelper(include, include); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); + } + + if (!targetConfig.noCompile) { + ArduinoUtil arduinoUtil = new ArduinoUtil(context, commandFactory, errorReporter); + arduinoUtil.buildArduino(fileConfig, targetConfig); + context.finish(GeneratorResult.Status.COMPILED, null); + } else { + System.out.println("********"); + System.out.println( + "To compile your program, run the following command to see information about the board" + + " you plugged in:\n\n" + + "\tarduino-cli board list\n\n" + + "Grab the FQBN and PORT from the command and run the following command in the" + + " generated sources directory:\n\n" + + "\tarduino-cli compile -b --build-property" + + " compiler.c.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" + + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' --build-property" + + " compiler.cpp.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" + + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' .\n\n" + + "To flash/upload your generated sketch to the board, run the following command in" + + " the generated sources directory:\n\n" + + "\tarduino-cli upload -b -p \n"); + // System.out.println("For a list of all boards installed on your computer, you can use the + // following command:\n\n\tarduino-cli board listall\n"); + context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + } + GeneratorUtils.refreshProject(resource, context.getMode()); + return; + } - } + // Dump the additional compile definitions to a file to keep the generated project + // self-contained. In this way, third-party build tools like PlatformIO, west, arduino-cli can + // take over and do the rest of compilation. + try { + String compileDefs = + targetConfig.compileDefinitions.keySet().stream() + .map(key -> key + "=" + targetConfig.compileDefinitions.get(key)) + .collect(Collectors.joining("\n")); + FileUtil.writeToFile( + compileDefs, + Path.of(fileConfig.getSrcGenPath() + File.separator + "CompileDefinitions.txt")); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } - // If a build directive has been given, invoke it now. - // Note that the code does not get cleaned in this case. - if (!targetConfig.noCompile) { - if (!IterableExtensions.isNullOrEmpty(targetConfig.buildCommands)) { - CUtil.runBuildCommand( - fileConfig, - targetConfig, - commandFactory, - errorReporter, - this::reportCommandErrors, - context.getMode() - ); - context.finish( - GeneratorResult.Status.COMPILED, null - ); - } - System.out.println("Compiled binary is in " + fileConfig.binPath); + // If this code generator is directly compiling the code, compile it now so that we + // clean it up after, removing the #line directives after errors have been reported. + if (!targetConfig.noCompile + && targetConfig.dockerOptions == null + && IterableExtensions.isNullOrEmpty(targetConfig.buildCommands) + // This code is unreachable in LSP_FAST mode, so that check is omitted. + && context.getMode() != LFGeneratorContext.Mode.LSP_MEDIUM) { + // FIXME: Currently, a lack of main is treated as a request to not produce + // a binary and produce a .o file instead. There should be a way to control + // this. + // Create an anonymous Runnable class and add it to the compileThreadPool + // so that compilation can happen in parallel. + var cleanCode = code.removeLines("#line"); + + var execName = lfModuleName; + var threadFileConfig = fileConfig; + var generator = + this; // FIXME: currently only passed to report errors with line numbers in the Eclipse + // IDE + var CppMode = CCppMode; + // generatingContext.reportProgress( + // String.format("Generated code for %d/%d executables. Compiling...", federateCount, + // federates.size()), + // 100 * federateCount / federates.size() + // ); // FIXME: Move to FedGenerator + // Create the compiler to be used later + + var cCompiler = new CCompiler(targetConfig, threadFileConfig, errorReporter, CppMode); + try { + if (!cCompiler.runCCompiler(generator, context)) { + // If compilation failed, remove any bin files that may have been created. + CUtil.deleteBinFiles(threadFileConfig); + // If finish has already been called, it is illegal and makes no sense. However, + // if finish has already been called, then this must be a federated execution. + context.unsuccessfulFinish(); } else { - context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + context.finish(GeneratorResult.Status.COMPILED, null); } - - // In case we are in Eclipse, make sure the generated code is visible. - GeneratorUtils.refreshProject(resource, context.getMode()); + cleanCode.writeToFile(targetFile); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } } - private void generateCodeFor( - String lfModuleName - ) throws IOException { - startTimeStepIsPresentCount = 0; - code.pr(generateDirectives()); - code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); - // Generate code for each reactor. - generateReactorDefinitions(); - - // Generate main instance, if there is one. - // Note that any main reactors in imported files are ignored. - // Skip generation if there are cycles. - if (main != null) { - initializeTriggerObjects.pr(String.join("\n", - "int _lf_startup_reactions_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_startup_reactions_count);", - "int _lf_shutdown_reactions_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_shutdown_reactions_count);", - "int _lf_reset_reactions_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_reset_reactions_count);", - "int _lf_timer_triggers_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_timer_triggers_count);", - "int bank_index;", - "SUPPRESS_UNUSED_WARNING(bank_index);" - )); - // Add counters for modal initialization - initializeTriggerObjects.pr(CModesGenerator.generateModalInitalizationCounters(hasModalReactors)); - - // Create an array of arrays to store all self structs. - // This is needed because connections cannot be established until - // all reactor instances have self structs because ports that - // receive data reference the self structs of the originating - // reactors, which are arbitarily far away in the program graph. - generateSelfStructs(main); - generateReactorInstance(main); - - if (targetConfig.fedSetupPreamble != null) { - if (targetLanguageIsCpp()) code.pr("extern \"C\" {"); - code.pr("#include \"" + targetConfig.fedSetupPreamble + "\""); - if (targetLanguageIsCpp()) code.pr("}"); - } + // If a build directive has been given, invoke it now. + // Note that the code does not get cleaned in this case. + if (!targetConfig.noCompile) { + if (!IterableExtensions.isNullOrEmpty(targetConfig.buildCommands)) { + CUtil.runBuildCommand( + fileConfig, + targetConfig, + commandFactory, + errorReporter, + this::reportCommandErrors, + context.getMode()); + context.finish(GeneratorResult.Status.COMPILED, null); + } + System.out.println("Compiled binary is in " + fileConfig.binPath); + } else { + context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + } - // If there are timers, create a table of timers to be initialized. - code.pr(CTimerGenerator.generateDeclarations(timerCount)); - - // If there are startup reactions, create a table of triggers. - code.pr(CReactionGenerator.generateBuiltinTriggersTable(startupReactionCount, "startup")); - - // If there are shutdown reactions, create a table of triggers. - code.pr(CReactionGenerator.generateBuiltinTriggersTable(shutdownReactionCount, "shutdown")); - - // If there are reset reactions, create a table of triggers. - code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); - - // If there are modes, create a table of mode state to be checked for transitions. - code.pr(CModesGenerator.generateModeStatesTable( - hasModalReactors, - modalReactorCount, - modalStateResetCount - )); - - // Generate function to initialize the trigger objects for all reactors. - code.pr(CTriggerObjectsGenerator.generateInitializeTriggerObjects( - main, - targetConfig, - initializeTriggerObjects, - startTimeStep, - types, - lfModuleName, - startTimeStepIsPresentCount - )); - - // Generate function to trigger startup reactions for all reactors. - code.pr(CReactionGenerator.generateLfTriggerStartupReactions(startupReactionCount, hasModalReactors)); - - // Generate function to schedule timers for all reactors. - code.pr(CTimerGenerator.generateLfInitializeTimer(timerCount)); - - // Generate a function that will either do nothing - // (if there is only one federate or the coordination - // is set to decentralized) or, if there are - // downstream federates, will notify the RTI - // that the specified logical time is complete. - if (CCppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) code.pr("extern \"C\""); - code.pr(String.join("\n", - "void logical_tag_complete(tag_t tag_to_send) {", - CExtensionUtils.surroundWithIfFederatedCentralized( - " _lf_logical_tag_complete(tag_to_send);" - ), - "}" - )); - - // Generate function to schedule shutdown reactions if any - // reactors have reactions to shutdown. - code.pr(CReactionGenerator.generateLfTriggerShutdownReactions(shutdownReactionCount, hasModalReactors)); - - // Generate an empty termination function for non-federated - // execution. For federated execution, an implementation is - // provided in federate.c. That implementation will resign - // from the federation and close any open sockets. - code.pr(""" + // In case we are in Eclipse, make sure the generated code is visible. + GeneratorUtils.refreshProject(resource, context.getMode()); + } + + private void generateCodeFor(String lfModuleName) throws IOException { + startTimeStepIsPresentCount = 0; + code.pr(generateDirectives()); + code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); + // Generate code for each reactor. + generateReactorDefinitions(); + + // Generate main instance, if there is one. + // Note that any main reactors in imported files are ignored. + // Skip generation if there are cycles. + if (main != null) { + initializeTriggerObjects.pr( + String.join( + "\n", + "int _lf_startup_reactions_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_startup_reactions_count);", + "int _lf_shutdown_reactions_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_shutdown_reactions_count);", + "int _lf_reset_reactions_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_reset_reactions_count);", + "int _lf_timer_triggers_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_timer_triggers_count);", + "int bank_index;", + "SUPPRESS_UNUSED_WARNING(bank_index);", + "int _lf_watchdog_number_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_watchdog_number_count);")); + // Add counters for modal initialization + initializeTriggerObjects.pr( + CModesGenerator.generateModalInitalizationCounters(hasModalReactors)); + + // Create an array of arrays to store all self structs. + // This is needed because connections cannot be established until + // all reactor instances have self structs because ports that + // receive data reference the self structs of the originating + // reactors, which are arbitarily far away in the program graph. + generateSelfStructs(main); + generateReactorInstance(main); + + if (targetConfig.fedSetupPreamble != null) { + if (targetLanguageIsCpp()) code.pr("extern \"C\" {"); + code.pr("#include \"" + targetConfig.fedSetupPreamble + "\""); + if (targetLanguageIsCpp()) code.pr("}"); + } + + // If there are timers, create a table of timers to be initialized. + code.pr(CTimerGenerator.generateDeclarations(timerCount)); + + // If there are startup reactions, create a table of triggers. + code.pr(CReactionGenerator.generateBuiltinTriggersTable(startupReactionCount, "startup")); + + // If there are shutdown reactions, create a table of triggers. + code.pr(CReactionGenerator.generateBuiltinTriggersTable(shutdownReactionCount, "shutdown")); + + // If there are reset reactions, create a table of triggers. + code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); + + // If there are watchdogs, create a table of triggers. + code.pr(CWatchdogGenerator.generateBuiltinTriggersTable(watchdogCount, "watchdog")); + + // If there are modes, create a table of mode state to be checked for transitions. + code.pr( + CModesGenerator.generateModeStatesTable( + hasModalReactors, modalReactorCount, modalStateResetCount)); + + // Generate function to initialize the trigger objects for all reactors. + code.pr( + CTriggerObjectsGenerator.generateInitializeTriggerObjects( + main, + targetConfig, + initializeTriggerObjects, + startTimeStep, + types, + lfModuleName, + startTimeStepIsPresentCount)); + + // Generate function to trigger startup reactions for all reactors. + code.pr( + CReactionGenerator.generateLfTriggerStartupReactions( + startupReactionCount, hasModalReactors)); + + // Generate function to schedule timers for all reactors. + code.pr(CTimerGenerator.generateLfInitializeTimer(timerCount)); + + // Generate function to initialize mutexes for all reactors with watchdogs. + code.pr(CWatchdogGenerator.generateLfInitializeWatchdogMutexes(watchdogCount)); + + // Generate a function that will either do nothing + // (if there is only one federate or the coordination + // is set to decentralized) or, if there are + // downstream federates, will notify the RTI + // that the specified logical time is complete. + if (CCppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) + code.pr("extern \"C\""); + code.pr( + String.join( + "\n", + "void logical_tag_complete(tag_t tag_to_send) {", + CExtensionUtils.surroundWithIfFederatedCentralized( + " _lf_logical_tag_complete(tag_to_send);"), + "}")); + + // Generate function to schedule shutdown reactions if any + // reactors have reactions to shutdown. + code.pr( + CReactionGenerator.generateLfTriggerShutdownReactions( + shutdownReactionCount, hasModalReactors)); + + // Generate an empty termination function for non-federated + // execution. For federated execution, an implementation is + // provided in federate.c. That implementation will resign + // from the federation and close any open sockets. + code.pr( + """ #ifndef FEDERATED void terminate_execution() {} - #endif""" - ); - - - // Generate functions for modes - code.pr(CModesGenerator.generateLfInitializeModes( - hasModalReactors - )); - code.pr(CModesGenerator.generateLfHandleModeChanges( - hasModalReactors, - modalStateResetCount - )); - code.pr(CReactionGenerator.generateLfModeTriggeredReactions( - startupReactionCount, - resetReactionCount, - hasModalReactors - )); + #endif"""); + + // Generate functions for modes + code.pr(CModesGenerator.generateLfInitializeModes(hasModalReactors)); + code.pr(CModesGenerator.generateLfHandleModeChanges(hasModalReactors, modalStateResetCount)); + code.pr( + CReactionGenerator.generateLfModeTriggeredReactions( + startupReactionCount, resetReactionCount, hasModalReactors)); + } + } + + @Override + public void checkModalReactorSupport(boolean __) { + // Modal reactors are currently only supported for non federated applications + super.checkModalReactorSupport(true); + } + + @Override + protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { + return String.join( + "\n", + "// Generated forwarding reaction for connections with the same destination", + "// but located in mutually exclusive modes.", + "lf_set(" + dest + ", " + source + "->value);"); + } + + /** Set the scheduler type in the target config as needed. */ + private void pickScheduler() { + // Don't use a scheduler that does not prioritize reactions based on deadlines + // if the program contains a deadline (handler). Use the GEDF_NP scheduler instead. + if (!targetConfig.schedulerType.prioritizesDeadline()) { + // Check if a deadline is assigned to any reaction + if (hasDeadlines(reactors)) { + if (!targetConfig.setByUser.contains(TargetProperty.SCHEDULER)) { + targetConfig.schedulerType = TargetProperty.SchedulerOption.GEDF_NP; } + } } + } - @Override - public void checkModalReactorSupport(boolean __) { - // Modal reactors are currently only supported for non federated applications - super.checkModalReactorSupport(true); + private boolean hasDeadlines(List reactors) { + for (Reactor reactor : reactors) { + for (Reaction reaction : allReactions(reactor)) { + if (reaction.getDeadline() != null) { + return true; + } + } } + return false; + } - @Override - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - return String.join("\n", - "// Generated forwarding reaction for connections with the same destination", - "// but located in mutually exclusive modes.", - "lf_set("+dest+", "+source+"->value);" - ); + private boolean hasWatchdogs() { + for (Reactor reactor : reactors) { + List watchdogs = ASTUtils.allWatchdogs(reactor); + if (watchdogs != null && !watchdogs.isEmpty()) { + return true; + } } - - /** Set the scheduler type in the target config as needed. */ - private void pickScheduler() { - // Don't use a scheduler that does not prioritize reactions based on deadlines - // if the program contains a deadline (handler). Use the GEDF_NP scheduler instead. - if (!targetConfig.schedulerType.prioritizesDeadline()) { - // Check if a deadline is assigned to any reaction - if (hasDeadlines(reactors)) { - if (!targetConfig.setByUser.contains(TargetProperty.SCHEDULER)) { - targetConfig.schedulerType = TargetProperty.SchedulerOption.GEDF_NP; - } - } + return false; + } + + /** + * Look at the 'reactor' eResource. If it is an imported .lf file, incorporate it into the current + * program in the following manner: - Merge its target property with `targetConfig` - If there are + * any preambles, add them to the preambles of the reactor. + */ + private void inspectReactorEResource(ReactorDecl reactor) { + // If the reactor is imported, look at the + // target definition of the .lf file in which the reactor is imported from and + // append any cmake-include. + // 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; } + } + // Copy the user files and cmake-includes to the src-gen path of the main .lf file + if (lfResource != null) { + copyUserFiles(lfResource.getTargetConfig(), lfResource.getFileConfig()); + } } - - private boolean hasDeadlines(List reactors) { - for (Reactor reactor : reactors) { - for (Reaction reaction : allReactions(reactor)) { - if (reaction.getDeadline() != null) { - return true; - } - } - } - return false; + } + + /** + * Copy all files or directories listed in the target property `files`, `cmake-include`, and + * `_fed_setup` into the src-gen folder of the main .lf file + * + * @param targetConfig The targetConfig to read the target properties from. + * @param fileConfig The fileConfig used to make the copy and resolve paths. + */ + @Override + public void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { + super.copyUserFiles(targetConfig, fileConfig); + // Make sure the target directory exists. + var targetDir = this.fileConfig.getSrcGenPath(); + try { + Files.createDirectories(targetDir); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); } - /** - * Look at the 'reactor' eResource. - * If it is an imported .lf file, incorporate it into the current - * program in the following manner: - * - Merge its target property with `targetConfig` - * - If there are any preambles, add them to the preambles of the reactor. - */ - private void inspectReactorEResource(ReactorDecl reactor) { - // If the reactor is imported, look at the - // target definition of the .lf file in which the reactor is imported from and - // append any cmake-include. - // 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; - } - } - // Copy the user files and cmake-includes to the src-gen path of the main .lf file - if (lfResource != null) { - copyUserFiles(lfResource.getTargetConfig(), lfResource.getFileConfig()); - } - } + for (String filename : targetConfig.fileNames) { + var relativeFileName = + CUtil.copyFileOrResource(filename, fileConfig.srcFile.getParent(), targetDir); + if (StringExtensions.isNullOrEmpty(relativeFileName)) { + errorReporter.reportError( + "Failed to find file " + filename + " specified in the" + " files target property."); + } else { + targetConfig.filesNamesWithoutPath.add(relativeFileName); + } } - /** - * Copy all files or directories listed in the target property `files`, `cmake-include`, - * and `_fed_setup` into the src-gen folder of the main .lf file - * - * @param targetConfig The targetConfig to read the target properties from. - * @param fileConfig The fileConfig used to make the copy and resolve paths. - */ - @Override - public void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { - super.copyUserFiles(targetConfig, fileConfig); - // Make sure the target directory exists. - var targetDir = this.fileConfig.getSrcGenPath(); - try { - Files.createDirectories(targetDir); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - - for (String filename : targetConfig.fileNames) { - var relativeFileName = CUtil.copyFileOrResource( - filename, - fileConfig.srcFile.getParent(), - targetDir); - if (StringExtensions.isNullOrEmpty(relativeFileName)) { - errorReporter.reportError( - "Failed to find file " + filename + " specified in the" + - " files target property." - ); - } else { - targetConfig.filesNamesWithoutPath.add( - relativeFileName - ); - } - } - - for (String filename : targetConfig.cmakeIncludes) { - var relativeCMakeIncludeFileName = - CUtil.copyFileOrResource( - filename, - fileConfig.srcFile.getParent(), - targetDir); - // Check if the file exists - if (StringExtensions.isNullOrEmpty(relativeCMakeIncludeFileName)) { - errorReporter.reportError( - "Failed to find cmake-include file " + filename - ); - } else { - this.targetConfig.cmakeIncludesWithoutPath.add( - relativeCMakeIncludeFileName - ); - } - } - - if (!StringExtensions.isNullOrEmpty(targetConfig.fedSetupPreamble)) { - try { - FileUtil.copyFile(fileConfig.srcFile.getParent().resolve(targetConfig.fedSetupPreamble), - targetDir.resolve(targetConfig.fedSetupPreamble)); - } catch (IOException e) { - errorReporter.reportError("Failed to find _fed_setup file " + targetConfig.fedSetupPreamble); - } - } + for (String filename : targetConfig.cmakeIncludes) { + var relativeCMakeIncludeFileName = + CUtil.copyFileOrResource(filename, fileConfig.srcFile.getParent(), targetDir); + // Check if the file exists + if (StringExtensions.isNullOrEmpty(relativeCMakeIncludeFileName)) { + errorReporter.reportError("Failed to find cmake-include file " + filename); + } else { + this.targetConfig.cmakeIncludesWithoutPath.add(relativeCMakeIncludeFileName); + } } - /** - * Generate code for defining all reactors that belong to the federate, - * including all the child reactors down the hierarchy. Duplicate - * Duplicates are avoided. - * - * Imported reactors' original .lf file is - * incorporated in the following manner: - * - If there are any cmake-include files, add them to the current list - * of cmake-include files. - * - If there are any preambles, add them to the preambles of the reactor. - */ - private void generateReactorDefinitions() throws IOException { - var generatedReactors = new LinkedHashSet(); - if (this.main != null) { - generateReactorChildren(this.main, generatedReactors); - } - - if (this.mainDef != null) { - generateReactorClass(ASTUtils.toDefinition(this.mainDef.getReactorClass())); - } - - if (mainDef == null) { - // Generate code for each reactor that was not instantiated in main or its children. - for (Reactor r : reactors) { - // Get the declarations for reactors that are instantiated somewhere. - // A declaration is either a reactor definition or an import statement.; - var declarations = this.instantiationGraph.getDeclarations(r); - // If the reactor has no instantiations and there is no main reactor, then - // generate code for it anyway (at a minimum, this means that the compiler is invoked - // so that reaction bodies are checked). - if (declarations.isEmpty()) { - generateReactorClass(r); - } - } - } + if (!StringExtensions.isNullOrEmpty(targetConfig.fedSetupPreamble)) { + try { + FileUtil.copyFile( + fileConfig.srcFile.getParent().resolve(targetConfig.fedSetupPreamble), + targetDir.resolve(targetConfig.fedSetupPreamble)); + } catch (IOException e) { + errorReporter.reportError( + "Failed to find _fed_setup file " + targetConfig.fedSetupPreamble); + } } - - /** Generate user-visible header files for all reactors instantiated. */ - private void generateHeaders() throws IOException { - FileUtil.deleteDirectory(fileConfig.getIncludePath()); - FileUtil.copyDirectoryFromClassPath( - fileConfig.getRuntimeIncludePath(), - fileConfig.getIncludePath(), - false - ); - for (Reactor r : reactors) { - CReactorHeaderFileGenerator.doGenerate( - types, r, fileConfig, - (builder, rr, userFacing) -> { - generateAuxiliaryStructs(builder, rr, userFacing); - if (userFacing) { - ASTUtils.allInstantiations(r).stream().map(Instantiation::getReactorClass).collect(Collectors.toSet()).forEach(it -> { - ASTUtils.allPorts(ASTUtils.toDefinition(it)) - .forEach(p -> builder.pr(CPortGenerator.generateAuxiliaryStruct( - ASTUtils.toDefinition(it), p, getTarget(), errorReporter, types, new CodeBuilder(), true, it - ))); - }); - } - }, - this::generateTopLevelPreambles); - } - FileUtil.copyDirectory(fileConfig.getIncludePath(), fileConfig.getSrcGenPath().resolve("include"), false); + } + + /** + * Generate code for defining all reactors that belong to the federate, including all the child + * reactors down the hierarchy. Duplicate Duplicates are avoided. + * + *

Imported reactors' original .lf file is incorporated in the following manner: - If there are + * any cmake-include files, add them to the current list of cmake-include files. - If there are + * any preambles, add them to the preambles of the reactor. + */ + private void generateReactorDefinitions() throws IOException { + var generatedReactors = new LinkedHashSet(); + if (this.main != null) { + generateReactorChildren(this.main, generatedReactors); } - /** - * Generate code for the children of 'reactor' that belong to 'federate'. - * Duplicates are avoided. - * - * Imported reactors' original .lf file is - * incorporated in the following manner: - * - If there are any cmake-include files, add them to the current list - * of cmake-include files. - * - If there are any preambles, add them to the preambles of the reactor. - * - * @param reactor Used to extract children from - */ - private void generateReactorChildren( - ReactorInstance reactor, - LinkedHashSet generatedReactors - ) throws IOException { - for (ReactorInstance r : reactor.children) { - if (r.reactorDeclaration != null && - !generatedReactors.contains(r.reactorDefinition)) { - generatedReactors.add(r.reactorDefinition); - generateReactorChildren(r, generatedReactors); - inspectReactorEResource(r.reactorDeclaration); - generateReactorClass(r.reactorDefinition); - } - } + if (this.mainDef != null) { + generateReactorClass(ASTUtils.toDefinition(this.mainDef.getReactorClass())); } - /** - * Choose which platform files to compile with according to the OS. - * If there is no main reactor, then compilation will produce a .o file requiring further linking. - * Also, if useCmake is set to true, we don't need to add platform files. The CMakeLists.txt file - * will detect and use the appropriate platform file based on the platform that cmake is invoked on. - */ - private void pickCompilePlatform() { - var osName = System.getProperty("os.name").toLowerCase(); - // if platform target was set, use given platform instead - if (targetConfig.platformOptions.platform != Platform.AUTO) { - osName = targetConfig.platformOptions.platform.toString(); - } else if (Stream.of("mac", "darwin", "win", "nux").noneMatch(osName::contains)) { - errorReporter.reportError("Platform " + osName + " is not supported"); + if (mainDef == null) { + // Generate code for each reactor that was not instantiated in main or its children. + for (Reactor r : reactors) { + // Get the declarations for reactors that are instantiated somewhere. + // A declaration is either a reactor definition or an import statement.; + var declarations = this.instantiationGraph.getDeclarations(r); + // If the reactor has no instantiations and there is no main reactor, then + // generate code for it anyway (at a minimum, this means that the compiler is invoked + // so that reaction bodies are checked). + if (declarations.isEmpty()) { + generateReactorClass(r); } + } } - - - /** - * Copy target-specific header file to the src-gen directory. - */ - protected void copyTargetFiles() throws IOException { - // Copy the core lib - String coreLib = LFGeneratorContext.BuildParm.EXTERNAL_RUNTIME_PATH.getValue(context); - Path dest = fileConfig.getSrcGenPath(); - if (targetConfig.platformOptions.platform == Platform.ARDUINO) dest = dest.resolve("src"); - if (coreLib != null) { - FileUtil.copyDirectory(Path.of(coreLib), dest, true); - } else { - FileUtil.copyDirectoryFromClassPath( - "/lib/c/reactor-c/core", - dest.resolve("core"), - true - ); - FileUtil.copyDirectoryFromClassPath( - "/lib/c/reactor-c/lib", - dest.resolve("lib"), - true - ); - } - - // For the Zephyr target, copy default config and board files. - if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - FileUtil.copyDirectoryFromClassPath( - "/lib/platform/zephyr/boards", - fileConfig.getSrcGenPath().resolve("boards"), - false - ); - FileUtil.copyFileFromClassPath( - "/lib/platform/zephyr/prj_lf.conf", - fileConfig.getSrcGenPath().resolve("prj_lf.conf"), - true - ); - - FileUtil.copyFileFromClassPath( - "/lib/platform/zephyr/Kconfig", - fileConfig.getSrcGenPath().resolve("Kconfig"), - true - ); - } + } + + /** Generate user-visible header files for all reactors instantiated. */ + private void generateHeaders() throws IOException { + FileUtil.deleteDirectory(fileConfig.getIncludePath()); + FileUtil.copyDirectoryFromClassPath( + fileConfig.getRuntimeIncludePath(), fileConfig.getIncludePath(), false); + for (Reactor r : reactors) { + CReactorHeaderFileGenerator.doGenerate( + types, + r, + fileConfig, + (builder, rr, userFacing) -> { + generateAuxiliaryStructs(builder, rr, userFacing); + if (userFacing) { + ASTUtils.allInstantiations(r).stream() + .map(Instantiation::getReactorClass) + .collect(Collectors.toSet()) + .forEach( + it -> { + ASTUtils.allPorts(ASTUtils.toDefinition(it)) + .forEach( + p -> + builder.pr( + CPortGenerator.generateAuxiliaryStruct( + ASTUtils.toDefinition(it), + p, + getTarget(), + errorReporter, + types, + new CodeBuilder(), + true, + it))); + }); + } + }, + this::generateTopLevelPreambles); } - - //////////////////////////////////////////// - //// Code generators. - /** - * Generate a reactor class definition for the specified federate. - * A class definition has four parts: - * - * * Preamble code, if any, specified in the Lingua Franca file. - * * A "self" struct type definition (see the class documentation above). - * * A function for each reaction. - * * A constructor for creating an instance. - * for deleting an instance. - * - * If the reactor is the main reactor, then - * the generated code may be customized. Specifically, - * if the main reactor has reactions, these reactions - * will not be generated if they are triggered by or send - * data to contained reactors that are not in the federate. - * @param reactor The parsed reactor data structure. - */ - private void generateReactorClass(Reactor reactor) throws IOException { - // FIXME: Currently we're not reusing definitions for declarations that point to the same definition. - CodeBuilder header = new CodeBuilder(); - CodeBuilder src = new CodeBuilder(); - final String headerName = CUtil.getName(reactor) + ".h"; - var guardMacro = headerName.toUpperCase().replace(".", "_"); - header.pr("#ifndef " + guardMacro); - header.pr("#define " + guardMacro); - generateReactorClassHeaders(reactor, headerName, header, src); - header.pr(generateTopLevelPreambles(reactor)); - generateUserPreamblesForReactor(reactor, src); - generateReactorClassBody(reactor, header, src); - header.pr("#endif // " + guardMacro); - FileUtil.writeToFile(header.toString(), fileConfig.getSrcGenPath().resolve(headerName), true); - var extension = targetConfig.platformOptions.platform == Platform.ARDUINO ? ".ino" : - CCppMode ? ".cpp" : ".c"; - FileUtil.writeToFile(src.toString(), fileConfig.getSrcGenPath().resolve(CUtil.getName(reactor) + extension), true); + FileUtil.copyDirectory( + fileConfig.getIncludePath(), fileConfig.getSrcGenPath().resolve("include"), false); + } + + /** + * Generate code for the children of 'reactor' that belong to 'federate'. Duplicates are avoided. + * + *

Imported reactors' original .lf file is incorporated in the following manner: - If there are + * any cmake-include files, add them to the current list of cmake-include files. - If there are + * any preambles, add them to the preambles of the reactor. + * + * @param reactor Used to extract children from + */ + private void generateReactorChildren( + ReactorInstance reactor, LinkedHashSet generatedReactors) throws IOException { + for (ReactorInstance r : reactor.children) { + if (r.reactorDeclaration != null && !generatedReactors.contains(r.reactorDefinition)) { + generatedReactors.add(r.reactorDefinition); + generateReactorChildren(r, generatedReactors); + inspectReactorEResource(r.reactorDeclaration); + generateReactorClass(r.reactorDefinition); + } } - - protected void generateReactorClassHeaders(Reactor reactor, String headerName, CodeBuilder header, CodeBuilder src) { - if (CCppMode) { - src.pr("extern \"C\" {"); - header.pr("extern \"C\" {"); - } - header.pr("#include \"include/core/reactor.h\""); - src.pr("#include \"include/api/api.h\""); - src.pr("#include \"include/api/set.h\""); - generateIncludes(reactor); - if (CCppMode) { - src.pr("}"); - header.pr("}"); - } - src.pr("#include \"include/" + CReactorHeaderFileGenerator.outputPath(reactor) + "\""); - src.pr("#include \"" + headerName + "\""); - ASTUtils.allNestedClasses(reactor).map(CUtil::getName) - .map(name -> "#include \"" + name + ".h\"") - .forEach(header::pr); + } + + /** + * Choose which platform files to compile with according to the OS. If there is no main reactor, + * then compilation will produce a .o file requiring further linking. Also, if useCmake is set to + * true, we don't need to add platform files. The CMakeLists.txt file will detect and use the + * appropriate platform file based on the platform that cmake is invoked on. + */ + private void pickCompilePlatform() { + var osName = System.getProperty("os.name").toLowerCase(); + // if platform target was set, use given platform instead + if (targetConfig.platformOptions.platform != Platform.AUTO) { + osName = targetConfig.platformOptions.platform.toString(); + } else if (Stream.of("mac", "darwin", "win", "nux").noneMatch(osName::contains)) { + errorReporter.reportError("Platform " + osName + " is not supported"); } - - private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeBuilder src) { - // Some of the following methods create lines of code that need to - // go into the constructor. Collect those lines of code here: - var constructorCode = new CodeBuilder(); - generateAuxiliaryStructs(header, reactor, false); - generateSelfStruct(header, reactor, constructorCode); - generateMethods(src, reactor); - generateReactions(src, reactor); - generateConstructor(src, header, reactor, constructorCode); + } + + /** Copy target-specific header file to the src-gen directory. */ + protected void copyTargetFiles() throws IOException { + // Copy the core lib + String coreLib = LFGeneratorContext.BuildParm.EXTERNAL_RUNTIME_PATH.getValue(context); + Path dest = fileConfig.getSrcGenPath(); + if (targetConfig.platformOptions.platform == Platform.ARDUINO) dest = dest.resolve("src"); + if (coreLib != null) { + FileUtil.copyDirectory(Path.of(coreLib), dest, true); + } else { + FileUtil.copyDirectoryFromClassPath("/lib/c/reactor-c/core", dest.resolve("core"), true); + FileUtil.copyDirectoryFromClassPath("/lib/c/reactor-c/lib", dest.resolve("lib"), true); } - /** - * Generate methods for {@code reactor}. - */ - protected void generateMethods(CodeBuilder src, ReactorDecl reactor) { - CMethodGenerator.generateMethods(reactor, src, types); + // For the Zephyr target, copy default config and board files. + if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { + FileUtil.copyDirectoryFromClassPath( + "/lib/platform/zephyr/boards", fileConfig.getSrcGenPath().resolve("boards"), false); + FileUtil.copyFileFromClassPath( + "/lib/platform/zephyr/prj_lf.conf", + fileConfig.getSrcGenPath().resolve("prj_lf.conf"), + true); + + FileUtil.copyFileFromClassPath( + "/lib/platform/zephyr/Kconfig", fileConfig.getSrcGenPath().resolve("Kconfig"), true); } - - /** - * Generates preambles defined by user for a given reactor - * @param reactor The given reactor - */ - protected void generateUserPreamblesForReactor(Reactor reactor, CodeBuilder src) { - for (Preamble p : convertToEmptyListIfNull(reactor.getPreambles())) { - src.pr("// *********** From the preamble, verbatim:"); - src.prSourceLineNumber(p.getCode()); - src.pr(toText(p.getCode())); - src.pr("\n// *********** End of preamble."); - } + } + + //////////////////////////////////////////// + //// Code generators. + /** + * Generate a reactor class definition for the specified federate. A class definition has four + * parts: + * + *

* Preamble code, if any, specified in the Lingua Franca file. * A "self" struct type + * definition (see the class documentation above). * A function for each reaction. * A constructor + * for creating an instance. for deleting an instance. + * + *

If the reactor is the main reactor, then the generated code may be customized. Specifically, + * if the main reactor has reactions, these reactions will not be generated if they are triggered + * by or send data to contained reactors that are not in the federate. + * + * @param reactor The parsed reactor data structure. + */ + private void generateReactorClass(Reactor reactor) throws IOException { + // FIXME: Currently we're not reusing definitions for declarations that point to the same + // definition. + CodeBuilder header = new CodeBuilder(); + CodeBuilder src = new CodeBuilder(); + final String headerName = CUtil.getName(reactor) + ".h"; + var guardMacro = headerName.toUpperCase().replace(".", "_"); + header.pr("#ifndef " + guardMacro); + header.pr("#define " + guardMacro); + generateReactorClassHeaders(reactor, headerName, header, src); + header.pr(generateTopLevelPreambles(reactor)); + generateUserPreamblesForReactor(reactor, src); + generateReactorClassBody(reactor, header, src); + header.pr("#endif // " + guardMacro); + FileUtil.writeToFile(header.toString(), fileConfig.getSrcGenPath().resolve(headerName), true); + var extension = + targetConfig.platformOptions.platform == Platform.ARDUINO + ? ".ino" + : CCppMode ? ".cpp" : ".c"; + FileUtil.writeToFile( + src.toString(), + fileConfig.getSrcGenPath().resolve(CUtil.getName(reactor) + extension), + true); + } + + protected void generateReactorClassHeaders( + Reactor reactor, String headerName, CodeBuilder header, CodeBuilder src) { + if (CCppMode) { + src.pr("extern \"C\" {"); + header.pr("extern \"C\" {"); } - - /** - * Generate a constructor for the specified reactor in the specified federate. - * @param reactor The parsed reactor data structure. - * @param constructorCode Lines of code previously generated that need to - * go into the constructor. - */ - protected void generateConstructor( - CodeBuilder src, CodeBuilder header, Reactor reactor, CodeBuilder constructorCode - ) { - header.pr(CConstructorGenerator.generateConstructorPrototype(reactor)); - src.pr(CConstructorGenerator.generateConstructor( - reactor, - constructorCode.toString() - )); + header.pr("#include \"include/core/reactor.h\""); + src.pr("#include \"include/api/api.h\""); + src.pr("#include \"include/api/set.h\""); + generateIncludes(reactor); + if (CCppMode) { + src.pr("}"); + header.pr("}"); } - - protected void generateIncludes(Reactor r) { - code.pr("#include \"" + CUtil.getName(r) + ".h\""); + src.pr("#include \"include/" + CReactorHeaderFileGenerator.outputPath(reactor) + "\""); + src.pr("#include \"" + headerName + "\""); + ASTUtils.allNestedClasses(reactor) + .map(CUtil::getName) + .map(name -> "#include \"" + name + ".h\"") + .forEach(header::pr); + } + + private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeBuilder src) { + // Some of the following methods create lines of code that need to + // go into the constructor. Collect those lines of code here: + var constructorCode = new CodeBuilder(); + generateAuxiliaryStructs(header, reactor, false); + generateSelfStruct(header, reactor, constructorCode); + generateMethods(src, reactor); + generateWatchdogs(src, reactor); + generateReactions(src, reactor); + generateConstructor(src, header, reactor, constructorCode); + } + + /** Generate methods for {@code reactor}. */ + protected void generateMethods(CodeBuilder src, ReactorDecl reactor) { + CMethodGenerator.generateMethods(reactor, src, types); + } + + /** + * Generates preambles defined by user for a given reactor + * + * @param reactor The given reactor + */ + protected void generateUserPreamblesForReactor(Reactor reactor, CodeBuilder src) { + for (Preamble p : convertToEmptyListIfNull(reactor.getPreambles())) { + src.pr("// *********** From the preamble, verbatim:"); + src.prSourceLineNumber(p.getCode()); + src.pr(toText(p.getCode())); + src.pr("\n// *********** End of preamble."); } - - /** - * Generate the struct type definitions for inputs, outputs, and - * actions of the specified reactor. - */ - protected void generateAuxiliaryStructs(CodeBuilder builder, Reactor r, boolean userFacing) { - // In the case where there are incoming - // p2p logical connections in decentralized - // federated execution, there will be an - // intended_tag field added to accommodate - // the case where a reaction triggered by a - // port or action is late due to network - // latency, etc.. - var federatedExtension = new CodeBuilder(); - federatedExtension.pr(""" + } + + /** + * Generate a constructor for the specified reactor in the specified federate. + * + * @param reactor The parsed reactor data structure. + * @param constructorCode Lines of code previously generated that need to go into the constructor. + */ + protected void generateConstructor( + CodeBuilder src, CodeBuilder header, Reactor reactor, CodeBuilder constructorCode) { + header.pr(CConstructorGenerator.generateConstructorPrototype(reactor)); + src.pr(CConstructorGenerator.generateConstructor(reactor, constructorCode.toString())); + } + + protected void generateIncludes(Reactor r) { + code.pr("#include \"" + CUtil.getName(r) + ".h\""); + } + + /** + * Generate the struct type definitions for inputs, outputs, and actions of the specified reactor. + */ + protected void generateAuxiliaryStructs(CodeBuilder builder, Reactor r, boolean userFacing) { + // In the case where there are incoming + // p2p logical connections in decentralized + // federated execution, there will be an + // intended_tag field added to accommodate + // the case where a reaction triggered by a + // port or action is late due to network + // latency, etc.. + var federatedExtension = new CodeBuilder(); + federatedExtension.pr( + """ #ifdef FEDERATED #ifdef FEDERATED_DECENTRALIZED %s intended_tag; #endif %s physical_time_of_arrival; #endif - """.formatted(types.getTargetTagType(), types.getTargetTimeType()) - ); - for (Port p : allPorts(r)) { - builder.pr(CPortGenerator.generateAuxiliaryStruct( - r, - p, - getTarget(), - errorReporter, - types, - federatedExtension, - userFacing, - null - )); - } - // The very first item on this struct needs to be - // a trigger_t* because the struct will be cast to (trigger_t*) - // by the lf_schedule() functions to get to the trigger. - for (Action action : allActions(r)) { - builder.pr(CActionGenerator.generateAuxiliaryStruct( - r, - action, - getTarget(), - types, - federatedExtension, - userFacing - )); - } + """ + .formatted(types.getTargetTagType(), types.getTargetTimeType())); + for (Port p : allPorts(r)) { + builder.pr( + CPortGenerator.generateAuxiliaryStruct( + r, p, getTarget(), errorReporter, types, federatedExtension, userFacing, null)); } - - /** - * Generate the self struct type definition for the specified reactor - * in the specified federate. - * @param decl The parsed reactor data structure. - * @param constructorCode Place to put lines of code that need to - * go into the constructor. - */ - private void generateSelfStruct(CodeBuilder builder, ReactorDecl decl, CodeBuilder constructorCode) { - var reactor = toDefinition(decl); - var selfType = CUtil.selfType(ASTUtils.toDefinition(decl)); - - // Construct the typedef for the "self" struct. - // Create a type name for the self struct. - var body = new CodeBuilder(); - - // Extensions can add functionality to the CGenerator - generateSelfStructExtension(body, decl, constructorCode); - - // Next handle parameters. - body.pr(CParameterGenerator.generateDeclarations(reactor, types)); - - // Next handle states. - body.pr(CStateGenerator.generateDeclarations(reactor, types)); - - // Next handle actions. - CActionGenerator.generateDeclarations(reactor, body, constructorCode); - - // Next handle inputs and outputs. - CPortGenerator.generateDeclarations(reactor, decl, body, constructorCode); - - // If there are contained reactors that either receive inputs - // from reactions of this reactor or produce outputs that trigger - // reactions of this reactor, then we need to create a struct - // inside the self struct for each contained reactor. That - // struct has a place to hold the data produced by this reactor's - // reactions and a place to put pointers to data produced by - // the contained reactors. - generateInteractingContainedReactors(reactor, body, constructorCode); - - // Next, generate the fields needed for each reaction. - CReactionGenerator.generateReactionAndTriggerStructs( - body, - reactor, - constructorCode, - types - ); - - // Next, generate fields for modes - CModesGenerator.generateDeclarations(reactor, body, constructorCode); - - // The first field has to always be a pointer to the list of - // of allocated memory that must be freed when the reactor is freed. - // This means that the struct can be safely cast to self_base_t. - builder.pr("typedef struct {"); - builder.indent(); - builder.pr("struct self_base_t base;"); - builder.pr(body.toString()); - builder.unindent(); - builder.pr("} " + selfType + ";"); + // The very first item on this struct needs to be + // a trigger_t* because the struct will be cast to (trigger_t*) + // by the lf_schedule() functions to get to the trigger. + for (Action action : allActions(r)) { + builder.pr( + CActionGenerator.generateAuxiliaryStruct( + r, action, getTarget(), types, federatedExtension, userFacing)); } - - /** - * Generate structs and associated code for contained reactors that - * send or receive data to or from the container's reactions. - * - * If there are contained reactors that either receive inputs - * from reactions of this reactor or produce outputs that trigger - * reactions of this reactor, then we need to create a struct - * inside the self struct of the container for each contained reactor. - * That struct has a place to hold the data produced by the container reactor's - * reactions and a place to put pointers to data produced by - * the contained reactors. - * - * @param reactor The reactor. - * @param body The place to put the struct definition for the contained reactors. - * @param constructorCode The place to put matching code that goes in the container's constructor. - */ - private void generateInteractingContainedReactors( - Reactor reactor, - CodeBuilder body, - CodeBuilder constructorCode - ) { - // The contents of the struct will be collected first so that - // we avoid duplicate entries and then the struct will be constructed. - var contained = new InteractingContainedReactors(reactor); - // Next generate the relevant code. - for (Instantiation containedReactor : contained.containedReactors()) { - Reactor containedReactorType = ASTUtils.toDefinition(containedReactor.getReactorClass()); - // First define an _width variable in case it is a bank. - var array = ""; - var width = -2; - // If the instantiation is a bank, find the maximum bank width - // to define an array. - if (containedReactor.getWidthSpec() != null) { - width = CReactionGenerator.maxContainedReactorBankWidth(containedReactor, null, 0, mainDef); - array = "[" + width + "]"; - } - // NOTE: The following needs to be done for each instance - // so that the width can be parameter, not in the constructor. - // Here, we conservatively use a width that is the largest of all isntances. - constructorCode.pr(String.join("\n", - "// Set the _width variable for all cases. This will be -2", - "// if the reactor is not a bank of reactors.", - "self->_lf_"+containedReactor.getName()+"_width = "+width+";" - )); - - // Generate one struct for each contained reactor that interacts. - body.pr("struct {"); - body.indent(); - for (Port port : contained.portsOfInstance(containedReactor)) { - if (port instanceof Input) { - // If the variable is a multiport, then the place to store the data has - // to be malloc'd at initialization. - if (!ASTUtils.isMultiport(port)) { - // Not a multiport. - body.pr(port, variableStructType(port, containedReactorType, false)+" "+port.getName()+";"); - } else { - // Is a multiport. - // Memory will be malloc'd in initialization. - body.pr(port, String.join("\n", - variableStructType(port, containedReactorType, false)+"** "+port.getName()+";", - "int "+port.getName()+"_width;" - )); - } - } else { - // Must be an output port. - // Outputs of contained reactors are pointers to the source of data on the - // self struct of the container. - if (!ASTUtils.isMultiport(port)) { - // Not a multiport. - body.pr(port, variableStructType(port, containedReactorType, false)+"* "+port.getName()+";"); - } else { - // Is a multiport. - // Here, we will use an array of pointers. - // Memory will be malloc'd in initialization. - body.pr(port, String.join("\n", - variableStructType(port, containedReactorType, false)+"** "+port.getName()+";", - "int "+port.getName()+"_width;" - )); - } - body.pr(port, "trigger_t "+port.getName()+"_trigger;"); - var reactorIndex = ""; - if (containedReactor.getWidthSpec() != null) { - reactorIndex = "[reactor_index]"; - constructorCode.pr("for (int reactor_index = 0; reactor_index < self->_lf_"+containedReactor.getName()+"_width; reactor_index++) {"); - constructorCode.indent(); - } - var portOnSelf = "self->_lf_"+containedReactor.getName()+reactorIndex+"."+port.getName(); - - constructorCode.pr( - port, - CExtensionUtils.surroundWithIfFederatedDecentralized( - portOnSelf+"_trigger.intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};" - ) - ); - - var triggered = contained.reactionsTriggered(containedReactor, port); - //noinspection StatementWithEmptyBody - if (triggered.size() > 0) { - body.pr(port, "reaction_t* "+port.getName()+"_reactions["+triggered.size()+"];"); - var triggeredCount = 0; - for (Integer index : triggered) { - constructorCode.pr(port, portOnSelf+"_reactions["+triggeredCount+++"] = &self->_lf__reaction_"+index+";"); - } - constructorCode.pr(port, portOnSelf+"_trigger.reactions = "+portOnSelf+"_reactions;"); - } else { - // Since the self struct is created using calloc, there is no need to set - // self->_lf_"+containedReactor.getName()+"."+port.getName()+"_trigger.reactions = NULL - } - // Since the self struct is created using calloc, there is no need to set falsy fields. - constructorCode.pr(port, String.join("\n", - portOnSelf+"_trigger.last = NULL;", - portOnSelf+"_trigger.number_of_reactions = "+triggered.size()+";" - )); - - - // Set the physical_time_of_arrival - constructorCode.pr( - port, - CExtensionUtils.surroundWithIfFederated( - portOnSelf+"_trigger.physical_time_of_arrival = NEVER;" - ) - ); - - if (containedReactor.getWidthSpec() != null) { - constructorCode.unindent(); - constructorCode.pr("}"); - } - } + } + + /** + * Generate the self struct type definition for the specified reactor in the specified federate. + * + * @param decl The parsed reactor data structure. + * @param constructorCode Place to put lines of code that need to go into the constructor. + */ + private void generateSelfStruct( + CodeBuilder builder, ReactorDecl decl, CodeBuilder constructorCode) { + var reactor = toDefinition(decl); + var selfType = CUtil.selfType(ASTUtils.toDefinition(decl)); + + // Construct the typedef for the "self" struct. + // Create a type name for the self struct. + var body = new CodeBuilder(); + + // Extensions can add functionality to the CGenerator + generateSelfStructExtension(body, decl, constructorCode); + + // Next handle parameters. + body.pr(CParameterGenerator.generateDeclarations(reactor, types)); + + // Next handle states. + body.pr(CStateGenerator.generateDeclarations(reactor, types)); + + // Next handle actions. + CActionGenerator.generateDeclarations(reactor, body, constructorCode); + + // Next handle inputs and outputs. + CPortGenerator.generateDeclarations(reactor, decl, body, constructorCode); + + // If there are contained reactors that either receive inputs + // from reactions of this reactor or produce outputs that trigger + // reactions of this reactor, then we need to create a struct + // inside the self struct for each contained reactor. That + // struct has a place to hold the data produced by this reactor's + // reactions and a place to put pointers to data produced by + // the contained reactors. + generateInteractingContainedReactors(reactor, body, constructorCode); + + // Next, generate the fields needed for each reaction. + CReactionGenerator.generateReactionAndTriggerStructs(body, reactor, constructorCode, types); + + // Generate the fields needed for each watchdog. + CWatchdogGenerator.generateWatchdogStruct(body, decl, constructorCode); + + // Next, generate fields for modes + CModesGenerator.generateDeclarations(reactor, body, constructorCode); + + // The first field has to always be a pointer to the list of + // of allocated memory that must be freed when the reactor is freed. + // This means that the struct can be safely cast to self_base_t. + builder.pr("typedef struct {"); + builder.indent(); + builder.pr("struct self_base_t base;"); + builder.pr(body.toString()); + builder.unindent(); + builder.pr("} " + selfType + ";"); + } + + /** + * Generate structs and associated code for contained reactors that send or receive data to or + * from the container's reactions. + * + *

If there are contained reactors that either receive inputs from reactions of this reactor or + * produce outputs that trigger reactions of this reactor, then we need to create a struct inside + * the self struct of the container for each contained reactor. That struct has a place to hold + * the data produced by the container reactor's reactions and a place to put pointers to data + * produced by the contained reactors. + * + * @param reactor The reactor. + * @param body The place to put the struct definition for the contained reactors. + * @param constructorCode The place to put matching code that goes in the container's constructor. + */ + private void generateInteractingContainedReactors( + Reactor reactor, CodeBuilder body, CodeBuilder constructorCode) { + // The contents of the struct will be collected first so that + // we avoid duplicate entries and then the struct will be constructed. + var contained = new InteractingContainedReactors(reactor); + // Next generate the relevant code. + for (Instantiation containedReactor : contained.containedReactors()) { + Reactor containedReactorType = ASTUtils.toDefinition(containedReactor.getReactorClass()); + // First define an _width variable in case it is a bank. + var array = ""; + var width = -2; + // If the instantiation is a bank, find the maximum bank width + // to define an array. + if (containedReactor.getWidthSpec() != null) { + width = CReactionGenerator.maxContainedReactorBankWidth(containedReactor, null, 0, mainDef); + array = "[" + width + "]"; + } + // NOTE: The following needs to be done for each instance + // so that the width can be parameter, not in the constructor. + // Here, we conservatively use a width that is the largest of all isntances. + constructorCode.pr( + String.join( + "\n", + "// Set the _width variable for all cases. This will be -2", + "// if the reactor is not a bank of reactors.", + "self->_lf_" + containedReactor.getName() + "_width = " + width + ";")); + + // Generate one struct for each contained reactor that interacts. + body.pr("struct {"); + body.indent(); + for (Port port : contained.portsOfInstance(containedReactor)) { + if (port instanceof Input) { + // If the variable is a multiport, then the place to store the data has + // to be malloc'd at initialization. + if (!ASTUtils.isMultiport(port)) { + // Not a multiport. + body.pr( + port, + variableStructType(port, containedReactorType, false) + " " + port.getName() + ";"); + } else { + // Is a multiport. + // Memory will be malloc'd in initialization. + body.pr( + port, + String.join( + "\n", + variableStructType(port, containedReactorType, false) + + "** " + + port.getName() + + ";", + "int " + port.getName() + "_width;")); + } + } else { + // Must be an output port. + // Outputs of contained reactors are pointers to the source of data on the + // self struct of the container. + if (!ASTUtils.isMultiport(port)) { + // Not a multiport. + body.pr( + port, + variableStructType(port, containedReactorType, false) + + "* " + + port.getName() + + ";"); + } else { + // Is a multiport. + // Here, we will use an array of pointers. + // Memory will be malloc'd in initialization. + body.pr( + port, + String.join( + "\n", + variableStructType(port, containedReactorType, false) + + "** " + + port.getName() + + ";", + "int " + port.getName() + "_width;")); + } + body.pr(port, "trigger_t " + port.getName() + "_trigger;"); + var reactorIndex = ""; + if (containedReactor.getWidthSpec() != null) { + reactorIndex = "[reactor_index]"; + constructorCode.pr( + "for (int reactor_index = 0; reactor_index < self->_lf_" + + containedReactor.getName() + + "_width; reactor_index++) {"); + constructorCode.indent(); + } + var portOnSelf = + "self->_lf_" + containedReactor.getName() + reactorIndex + "." + port.getName(); + + constructorCode.pr( + port, + CExtensionUtils.surroundWithIfFederatedDecentralized( + portOnSelf + + "_trigger.intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); + + var triggered = contained.reactionsTriggered(containedReactor, port); + //noinspection StatementWithEmptyBody + if (triggered.size() > 0) { + body.pr( + port, "reaction_t* " + port.getName() + "_reactions[" + triggered.size() + "];"); + var triggeredCount = 0; + for (Integer index : triggered) { + constructorCode.pr( + port, + portOnSelf + + "_reactions[" + + triggeredCount++ + + "] = &self->_lf__reaction_" + + index + + ";"); } - body.unindent(); - body.pr(String.join("\n", - "} _lf_"+containedReactor.getName()+array+";", - "int _lf_"+containedReactor.getName()+"_width;" - )); + constructorCode.pr( + port, portOnSelf + "_trigger.reactions = " + portOnSelf + "_reactions;"); + } else { + // Since the self struct is created using calloc, there is no need to set + // self->_lf_"+containedReactor.getName()+"."+port.getName()+"_trigger.reactions = NULL + } + // Since the self struct is created using calloc, there is no need to set falsy fields. + constructorCode.pr( + port, + String.join( + "\n", + portOnSelf + "_trigger.last = NULL;", + portOnSelf + "_trigger.number_of_reactions = " + triggered.size() + ";")); + + // Set the physical_time_of_arrival + constructorCode.pr( + port, + CExtensionUtils.surroundWithIfFederated( + portOnSelf + "_trigger.physical_time_of_arrival = NEVER;")); + + if (containedReactor.getWidthSpec() != null) { + constructorCode.unindent(); + constructorCode.pr("}"); + } } + } + body.unindent(); + body.pr( + String.join( + "\n", + "} _lf_" + containedReactor.getName() + array + ";", + "int _lf_" + containedReactor.getName() + "_width;")); } - - /** - * This function is provided to allow extensions of the CGenerator to append the structure of the self struct - * @param body The body of the self struct - * @param decl The reactor declaration for the self struct - * @param constructorCode Code that is executed when the reactor is instantiated - */ - protected void generateSelfStructExtension( - CodeBuilder body, - ReactorDecl decl, - CodeBuilder constructorCode - ) { - // Do nothing - } - - /** Generate reaction functions definition for a reactor. - * These functions have a single argument that is a void* pointing to - * a struct that contains parameters, state variables, inputs (triggering or not), - * actions (triggering or produced), and outputs. - * @param r The reactor. - */ - public void generateReactions(CodeBuilder src, Reactor r) { - var reactionIndex = 0; - var reactor = ASTUtils.toDefinition(r); - for (Reaction reaction : allReactions(reactor)) { - generateReaction(src, reaction, r, reactionIndex); - // Increment reaction index even if the reaction is not in the federate - // so that across federates, the reaction indices are consistent. - reactionIndex++; - } + } + + /** + * This function is provided to allow extensions of the CGenerator to append the structure of the + * self struct + * + * @param body The body of the self struct + * @param decl The reactor declaration for the self struct + * @param constructorCode Code that is executed when the reactor is instantiated + */ + protected void generateSelfStructExtension( + CodeBuilder body, ReactorDecl decl, CodeBuilder constructorCode) { + // Do nothing + } + + /** + * Generate reaction functions definition for a reactor. These functions have a single argument + * that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param r The reactor. + */ + public void generateReactions(CodeBuilder src, Reactor r) { + var reactionIndex = 0; + var reactor = ASTUtils.toDefinition(r); + for (Reaction reaction : allReactions(reactor)) { + generateReaction(src, reaction, r, reactionIndex); + // Increment reaction index even if the reaction is not in the federate + // so that across federates, the reaction indices are consistent. + reactionIndex++; } - - /** Generate a reaction function definition for a reactor. - * This function will have a single argument that is a void* pointing to - * a struct that contains parameters, state variables, inputs (triggering or not), - * actions (triggering or produced), and outputs. - * @param reaction The reaction. - * @param r The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - protected void generateReaction(CodeBuilder src, Reaction reaction, Reactor r, int reactionIndex) { - src.pr(CReactionGenerator.generateReaction( + } + + /** + * Generate a reaction function definition for a reactor. This function will have a single + * argument that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param reaction The reaction. + * @param r The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + protected void generateReaction( + CodeBuilder src, Reaction reaction, Reactor r, int reactionIndex) { + src.pr( + CReactionGenerator.generateReaction( reaction, r, reactionIndex, @@ -1430,694 +1400,783 @@ protected void generateReaction(CodeBuilder src, Reaction reaction, Reactor r, i errorReporter, types, targetConfig, - getTarget().requiresTypes - )); + getTarget().requiresTypes)); + } + + /** + * Generate watchdog functions definition for a reactor. These functions have a single argument + * that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param decl The reactor. federated or not the main reactor and reactions should be + * unconditionally generated. + */ + public void generateWatchdogs(CodeBuilder src, ReactorDecl decl) { + var reactor = ASTUtils.toDefinition(decl); + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + generateWatchdog(src, watchdog, decl); } - - /** - * Record startup, shutdown, and reset reactions. - * @param instance A reactor instance. - */ - private void recordBuiltinTriggers(ReactorInstance instance) { - // For each reaction instance, allocate the arrays that will be used to - // trigger downstream reactions. - for (ReactionInstance reaction : instance.reactions) { - var reactor = reaction.getParent(); - var temp = new CodeBuilder(); - var foundOne = false; - - var reactionRef = CUtil.reactionRef(reaction); - - // Next handle triggers of the reaction that come from a multiport output - // of a contained reactor. Also, handle startup and shutdown triggers. - for (TriggerInstance trigger : reaction.triggers) { - if (trigger.isStartup()) { - temp.pr("_lf_startup_reactions[_lf_startup_reactions_count++] = &"+reactionRef+";"); - startupReactionCount += reactor.getTotalWidth(); - foundOne = true; - } else if (trigger.isShutdown()) { - temp.pr("_lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &"+reactionRef+";"); - foundOne = true; - shutdownReactionCount += reactor.getTotalWidth(); - - if (targetConfig.tracing != null) { - var description = CUtil.getShortenedName(reactor); - var reactorRef = CUtil.reactorRef(reactor); - temp.pr(String.join("\n", - "_lf_register_trace_event("+reactorRef+", &("+reactorRef+"->_lf__shutdown),", - "trace_trigger, "+addDoubleQuotes(description+".shutdown")+");" - )); - } - } else if (trigger.isReset()) { - temp.pr("_lf_reset_reactions[_lf_reset_reactions_count++] = &"+reactionRef+";"); - resetReactionCount += reactor.getTotalWidth(); - foundOne = true; - } - } - if (foundOne) initializeTriggerObjects.pr(temp.toString()); + } + + /** + * Generate a watchdog function definition for a reactor. This function will have a single + * argument that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param watchdog The watchdog. + * @param decl The reactor. + */ + protected void generateWatchdog(CodeBuilder src, Watchdog watchdog, ReactorDecl decl) { + src.pr(CWatchdogGenerator.generateWatchdog(watchdog, decl)); + } + + /** + * Record startup, shutdown, and reset reactions. + * + * @param instance A reactor instance. + */ + private void recordBuiltinTriggers(ReactorInstance instance) { + // For each reaction instance, allocate the arrays that will be used to + // trigger downstream reactions. + for (ReactionInstance reaction : instance.reactions) { + var reactor = reaction.getParent(); + var temp = new CodeBuilder(); + var foundOne = false; + + var reactionRef = CUtil.reactionRef(reaction); + + // Next handle triggers of the reaction that come from a multiport output + // of a contained reactor. Also, handle startup and shutdown triggers. + for (TriggerInstance trigger : reaction.triggers) { + if (trigger.isStartup()) { + temp.pr("_lf_startup_reactions[_lf_startup_reactions_count++] = &" + reactionRef + ";"); + startupReactionCount += reactor.getTotalWidth(); + foundOne = true; + } else if (trigger.isShutdown()) { + temp.pr("_lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &" + reactionRef + ";"); + foundOne = true; + shutdownReactionCount += reactor.getTotalWidth(); + + if (targetConfig.tracing != null) { + var description = CUtil.getShortenedName(reactor); + var reactorRef = CUtil.reactorRef(reactor); + temp.pr( + String.join( + "\n", + "_lf_register_trace_event(" + + reactorRef + + ", &(" + + reactorRef + + "->_lf__shutdown),", + "trace_trigger, " + addDoubleQuotes(description + ".shutdown") + ");")); + } + } else if (trigger.isReset()) { + temp.pr("_lf_reset_reactions[_lf_reset_reactions_count++] = &" + reactionRef + ";"); + resetReactionCount += reactor.getTotalWidth(); + foundOne = true; } + } + if (foundOne) initializeTriggerObjects.pr(temp.toString()); } - - - - /** - * Generate code to set up the tables used in _lf_start_time_step to decrement reference - * counts and mark outputs absent between time steps. This function puts the code - * into startTimeStep. - */ - private void generateStartTimeStep(ReactorInstance instance) { - // Avoid generating dead code if nothing is relevant. - var foundOne = false; - var temp = new CodeBuilder(); - var containerSelfStructName = CUtil.reactorRef(instance); - - // Handle inputs that get sent data from a reaction rather than from - // another contained reactor and reactions that are triggered by an - // output of a contained reactor. - // Note that there may be more than one reaction reacting to the same - // port so we have to avoid listing the port more than once. - var portsSeen = new LinkedHashSet(); - for (ReactionInstance reaction : instance.reactions) { - for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { - if (port.getDefinition() instanceof Input && !portsSeen.contains(port)) { - portsSeen.add(port); - // This reaction is sending to an input. Must be - // the input of a contained reactor in the federate. - // NOTE: If instance == main and the federate is within a bank, - // this assumes that the reaction writes only to the bank member in the federate. - foundOne = true; - - temp.pr("// Add port "+port.getFullName()+" to array of is_present fields."); - - if (!Objects.equal(port.getParent(), instance)) { - // The port belongs to contained reactor, so we also have - // iterate over the instance bank members. - temp.startScopedBlock(); - temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); - temp.startScopedBlock(instance); - temp.startScopedBankChannelIteration(port, null); - } else { - temp.startScopedBankChannelIteration(port, "count"); - } - var portRef = CUtil.portRefNested(port); - var con = (port.isMultiport()) ? "->" : "."; - - temp.pr("_lf_is_present_fields["+startTimeStepIsPresentCount+" + count] = &"+portRef+con+"is_present;"); - // Intended_tag is only applicable to ports in federated execution. - temp.pr( - CExtensionUtils.surroundWithIfFederatedDecentralized( - "_lf_intended_tag_fields["+startTimeStepIsPresentCount+" + count] = &"+portRef+con+"intended_tag;" - ) - ); - - startTimeStepIsPresentCount += port.getWidth() * port.getParent().getTotalWidth(); - - if (!Objects.equal(port.getParent(), instance)) { - temp.pr("count++;"); - temp.endScopedBlock(); - temp.endScopedBlock(); - temp.endScopedBankChannelIteration(port, null); - } else { - temp.endScopedBankChannelIteration(port, "count"); - } - } - } + } + + private void recordWatchdogs(ReactorInstance instance) { + var foundOne = false; + var temp = new CodeBuilder(); + var reactorRef = CUtil.reactorRef(instance); + // temp.pr("#ifdef LF_THREADED"); + for (Watchdog watchdog : ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) { + temp.pr( + " _lf_watchdogs[_lf_watchdog_number_count++] = &" + + reactorRef + + "->_lf_watchdog_" + + watchdog.getName() + + ";"); + temp.pr( + " " + + reactorRef + + "->_lf_watchdog_" + + watchdog.getName() + + ".min_expiration = " + + CTypes.getInstance().getTargetTimeExpr(instance.getTimeValue(watchdog.getTimeout())) + + ";"); + temp.pr(" " + reactorRef + "->_lf_watchdog_" + watchdog.getName() + ".thread_id;"); + watchdogCount += 1; + foundOne = true; + } + // temp.pr("#endif"); + if (foundOne) { + initializeTriggerObjects.pr(temp.toString()); + } + } + + /** + * Generate code to set up the tables used in _lf_start_time_step to decrement reference counts + * and mark outputs absent between time steps. This function puts the code into startTimeStep. + */ + private void generateStartTimeStep(ReactorInstance instance) { + // Avoid generating dead code if nothing is relevant. + var foundOne = false; + var temp = new CodeBuilder(); + var containerSelfStructName = CUtil.reactorRef(instance); + + // Handle inputs that get sent data from a reaction rather than from + // another contained reactor and reactions that are triggered by an + // output of a contained reactor. + // Note that there may be more than one reaction reacting to the same + // port so we have to avoid listing the port more than once. + var portsSeen = new LinkedHashSet(); + for (ReactionInstance reaction : instance.reactions) { + for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { + if (port.getDefinition() instanceof Input && !portsSeen.contains(port)) { + portsSeen.add(port); + // This reaction is sending to an input. Must be + // the input of a contained reactor in the federate. + // NOTE: If instance == main and the federate is within a bank, + // this assumes that the reaction writes only to the bank member in the federate. + foundOne = true; + + temp.pr("// Add port " + port.getFullName() + " to array of is_present fields."); + + if (!Objects.equal(port.getParent(), instance)) { + // The port belongs to contained reactor, so we also have + // iterate over the instance bank members. + temp.startScopedBlock(); + temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); + temp.startScopedBlock(instance); + temp.startScopedBankChannelIteration(port, null); + } else { + temp.startScopedBankChannelIteration(port, "count"); + } + var portRef = CUtil.portRefNested(port); + var con = (port.isMultiport()) ? "->" : "."; + + temp.pr( + "_lf_is_present_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + portRef + + con + + "is_present;"); + // Intended_tag is only applicable to ports in federated execution. + temp.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + "_lf_intended_tag_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + portRef + + con + + "intended_tag;")); + + startTimeStepIsPresentCount += port.getWidth() * port.getParent().getTotalWidth(); + + if (!Objects.equal(port.getParent(), instance)) { + temp.pr("count++;"); + temp.endScopedBlock(); + temp.endScopedBlock(); + temp.endScopedBankChannelIteration(port, null); + } else { + temp.endScopedBankChannelIteration(port, "count"); + } } - if (foundOne) startTimeStep.pr(temp.toString()); - temp = new CodeBuilder(); - foundOne = false; + } + } + if (foundOne) startTimeStep.pr(temp.toString()); + temp = new CodeBuilder(); + foundOne = false; + + for (ActionInstance action : instance.actions) { + foundOne = true; + temp.startScopedBlock(instance); + + temp.pr( + String.join( + "\n", + "// Add action " + action.getFullName() + " to array of is_present fields.", + "_lf_is_present_fields[" + startTimeStepIsPresentCount + "] ", + " = &" + + containerSelfStructName + + "->_lf_" + + action.getName() + + ".is_present;")); + + // Intended_tag is only applicable to actions in federated execution with decentralized + // coordination. + temp.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + String.join( + "\n", + "// Add action " + action.getFullName() + " to array of intended_tag fields.", + "_lf_intended_tag_fields[" + startTimeStepIsPresentCount + "] ", + " = &" + + containerSelfStructName + + "->_lf_" + + action.getName() + + ".intended_tag;"))); + + startTimeStepIsPresentCount += action.getParent().getTotalWidth(); + temp.endScopedBlock(); + } + if (foundOne) startTimeStep.pr(temp.toString()); + temp = new CodeBuilder(); + foundOne = false; - for (ActionInstance action : instance.actions) { - foundOne = true; - temp.startScopedBlock(instance); + // Next, set up the table to mark each output of each contained reactor absent. + for (ReactorInstance child : instance.children) { + if (child.outputs.size() > 0) { - temp.pr(String.join("\n", - "// Add action "+action.getFullName()+" to array of is_present fields.", - "_lf_is_present_fields["+startTimeStepIsPresentCount+"] ", - " = &"+containerSelfStructName+"->_lf_"+action.getName()+".is_present;" - )); + temp.startScopedBlock(); + temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); + temp.startScopedBlock(child); - // Intended_tag is only applicable to actions in federated execution with decentralized coordination. + var channelCount = 0; + for (PortInstance output : child.outputs) { + if (!output.getDependsOnReactions().isEmpty()) { + foundOne = true; + temp.pr("// Add port " + output.getFullName() + " to array of is_present fields."); + temp.startChannelIteration(output); + temp.pr( + "_lf_is_present_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + CUtil.portRef(output) + + ".is_present;"); + + // Intended_tag is only applicable to ports in federated execution with decentralized + // coordination. temp.pr( CExtensionUtils.surroundWithIfFederatedDecentralized( - String.join("\n", - "// Add action " + action.getFullName() - + " to array of intended_tag fields.", - "_lf_intended_tag_fields[" - + startTimeStepIsPresentCount + "] ", - " = &" + containerSelfStructName - + "->_lf_" + action.getName() - + ".intended_tag;" - ))); - - startTimeStepIsPresentCount += action.getParent().getTotalWidth(); - temp.endScopedBlock(); - } - if (foundOne) startTimeStep.pr(temp.toString()); - temp = new CodeBuilder(); - foundOne = false; - - // Next, set up the table to mark each output of each contained reactor absent. - for (ReactorInstance child : instance.children) { - if (child.outputs.size() > 0) { - - temp.startScopedBlock(); - temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); - temp.startScopedBlock(child); - - var channelCount = 0; - for (PortInstance output : child.outputs) { - if (!output.getDependsOnReactions().isEmpty()){ - foundOne = true; - temp.pr("// Add port "+output.getFullName()+" to array of is_present fields."); - temp.startChannelIteration(output); - temp.pr("_lf_is_present_fields["+startTimeStepIsPresentCount+" + count] = &"+CUtil.portRef(output)+".is_present;"); - - // Intended_tag is only applicable to ports in federated execution with decentralized coordination. - temp.pr( - CExtensionUtils.surroundWithIfFederatedDecentralized( - String.join("\n", - "// Add port "+output.getFullName()+" to array of intended_tag fields.", - "_lf_intended_tag_fields["+startTimeStepIsPresentCount+" + count] = &"+CUtil.portRef(output)+".intended_tag;" - ))); - - temp.pr("count++;"); - channelCount += output.getWidth(); - temp.endChannelIteration(output); - } - } - startTimeStepIsPresentCount += channelCount * child.getTotalWidth(); - temp.endScopedBlock(); - temp.endScopedBlock(); - } + String.join( + "\n", + "// Add port " + output.getFullName() + " to array of intended_tag fields.", + "_lf_intended_tag_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + CUtil.portRef(output) + + ".intended_tag;"))); + + temp.pr("count++;"); + channelCount += output.getWidth(); + temp.endChannelIteration(output); + } } - if (foundOne) startTimeStep.pr(temp.toString()); + startTimeStepIsPresentCount += channelCount * child.getTotalWidth(); + temp.endScopedBlock(); + temp.endScopedBlock(); + } } - - /** - * For each timer in the given reactor, generate initialization code for the offset - * and period fields. - * - * This method will also populate the global _lf_timer_triggers array, which is - * used to start all timers at the start of execution. - * - * @param instance A reactor instance. - */ - private void generateTimerInitializations(ReactorInstance instance) { - for (TimerInstance timer : instance.timers) { - if (!timer.isStartup()) { - initializeTriggerObjects.pr(CTimerGenerator.generateInitializer(timer)); - timerCount += timer.getParent().getTotalWidth(); - } - } + if (foundOne) startTimeStep.pr(temp.toString()); + } + + /** + * For each timer in the given reactor, generate initialization code for the offset and period + * fields. + * + *

This method will also populate the global _lf_timer_triggers array, which is used to start + * all timers at the start of execution. + * + * @param instance A reactor instance. + */ + private void generateTimerInitializations(ReactorInstance instance) { + for (TimerInstance timer : instance.timers) { + if (!timer.isStartup()) { + initializeTriggerObjects.pr(CTimerGenerator.generateInitializer(timer)); + timerCount += timer.getParent().getTotalWidth(); + } } - - /** - * Process a given .proto file. - * - * Run, if possible, the proto-c protocol buffer code generator to produce - * the required .h and .c files. - * @param filename Name of the file to process. - */ - public void processProtoFile(String filename) { - var protoc = commandFactory.createCommand( + } + + /** + * Process a given .proto file. + * + *

Run, if possible, the proto-c protocol buffer code generator to produce the required .h and + * .c files. + * + * @param filename Name of the file to process. + */ + public void processProtoFile(String filename) { + var protoc = + commandFactory.createCommand( "protoc-c", - List.of("--c_out="+this.fileConfig.getSrcGenPath(), filename), + List.of("--c_out=" + this.fileConfig.getSrcGenPath(), filename), fileConfig.srcPath); - if (protoc == null) { - errorReporter.reportError("Processing .proto files requires protoc-c >= 1.3.3."); - return; - } - var returnCode = protoc.run(); - if (returnCode == 0) { - var nameSansProto = filename.substring(0, filename.length() - 6); - targetConfig.compileAdditionalSources.add( - fileConfig.getSrcGenPath().resolve(nameSansProto + ".pb-c.c").toString() - ); - - targetConfig.compileLibraries.add("-l"); - targetConfig.compileLibraries.add("protobuf-c"); - targetConfig.compilerFlags.add("-lprotobuf-c"); - } else { - errorReporter.reportError("protoc-c returns error code " + returnCode); - } + if (protoc == null) { + errorReporter.reportError("Processing .proto files requires protoc-c >= 1.3.3."); + return; } - - /** - * Construct a unique type for the struct of the specified - * typed variable (port or action) of the specified reactor class. - * This is required to be the same as the type name returned by - * {@link #variableStructType(TriggerInstance)}. - */ - public static String variableStructType(Variable variable, Reactor reactor, boolean userFacing) { - return (userFacing ? reactor.getName().toLowerCase() : CUtil.getName(reactor)) +"_"+variable.getName()+"_t"; + var returnCode = protoc.run(); + if (returnCode == 0) { + var nameSansProto = filename.substring(0, filename.length() - 6); + targetConfig.compileAdditionalSources.add( + fileConfig.getSrcGenPath().resolve(nameSansProto + ".pb-c.c").toString()); + + targetConfig.compileLibraries.add("-l"); + targetConfig.compileLibraries.add("protobuf-c"); + targetConfig.compilerFlags.add("-lprotobuf-c"); + } else { + errorReporter.reportError("protoc-c returns error code " + returnCode); } - - /** - * Construct a unique type for the struct of the specified - * instance (port or action). - * This is required to be the same as the type name returned by - * {@link #variableStructType(Variable, Reactor, boolean)}. - * @param portOrAction The port or action instance. - * @return The name of the self struct. - */ - public static String variableStructType(TriggerInstance portOrAction) { - return CUtil.getName(portOrAction.getParent().reactorDefinition)+"_"+portOrAction.getName()+"_t"; + } + + /** + * Construct a unique type for the struct of the specified typed variable (port or action) of the + * specified reactor class. This is required to be the same as the type name returned by {@link + * #variableStructType(TriggerInstance)}. + */ + public static String variableStructType(Variable variable, Reactor reactor, boolean userFacing) { + return (userFacing ? reactor.getName().toLowerCase() : CUtil.getName(reactor)) + + "_" + + variable.getName() + + "_t"; + } + + /** + * Construct a unique type for the struct of the specified instance (port or action). This is + * required to be the same as the type name returned by {@link #variableStructType(Variable, + * Reactor, boolean)}. + * + * @param portOrAction The port or action instance. + * @return The name of the self struct. + */ + public static String variableStructType(TriggerInstance portOrAction) { + return CUtil.getName(portOrAction.getParent().reactorDefinition) + + "_" + + portOrAction.getName() + + "_t"; + } + + /** + * If tracing is turned on, then generate code that records the full name of the specified reactor + * instance in the trace table. If tracing is not turned on, do nothing. + * + * @param instance The reactor instance. + */ + private void generateTraceTableEntries(ReactorInstance instance) { + if (targetConfig.tracing != null) { + initializeTriggerObjects.pr(CTracingGenerator.generateTraceTableEntries(instance)); } - - /** - * If tracing is turned on, then generate code that records - * the full name of the specified reactor instance in the - * trace table. If tracing is not turned on, do nothing. - * @param instance The reactor instance. - */ - private void generateTraceTableEntries(ReactorInstance instance) { - if (targetConfig.tracing != null) { - initializeTriggerObjects.pr( - CTracingGenerator.generateTraceTableEntries(instance) - ); - } + } + + /** + * Generate code to instantiate the specified reactor instance and initialize it. + * + * @param instance A reactor instance. + */ + public void generateReactorInstance(ReactorInstance instance) { + var reactorClass = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); + var fullName = instance.getFullName(); + initializeTriggerObjects.pr( + "// ***** Start initializing " + fullName + " of class " + reactorClass.getName()); + // Generate the instance self struct containing parameters, state variables, + // and outputs (the "self" struct). + initializeTriggerObjects.pr( + CUtil.reactorRefName(instance) + + "[" + + CUtil.runtimeIndex(instance) + + "] = new_" + + CUtil.getName(reactorClass) + + "();"); + // Generate code to initialize the "self" struct in the + // _lf_initialize_trigger_objects function. + generateTraceTableEntries(instance); + generateReactorInstanceExtension(instance); + generateParameterInitialization(instance); + initializeOutputMultiports(instance); + initializeInputMultiports(instance); + recordBuiltinTriggers(instance); + recordWatchdogs(instance); + + // Next, initialize the "self" struct with state variables. + // These values may be expressions that refer to the parameter values defined above. + generateStateVariableInitializations(instance); + + // Generate trigger objects for the instance. + generateTimerInitializations(instance); + generateActionInitializations(instance); + generateInitializeActionToken(instance); + generateSetDeadline(instance); + generateModeStructure(instance); + + // Recursively generate code for the children. + for (ReactorInstance child : instance.children) { + // If this reactor is a placeholder for a bank of reactors, then generate + // an array of instances of reactors and create an enclosing for loop. + // Need to do this for each of the builders into which the code writes. + startTimeStep.startScopedBlock(child); + initializeTriggerObjects.startScopedBlock(child); + generateReactorInstance(child); + initializeTriggerObjects.endScopedBlock(); + startTimeStep.endScopedBlock(); } - /** - * Generate code to instantiate the specified reactor instance and - * initialize it. - * @param instance A reactor instance. - */ - public void generateReactorInstance(ReactorInstance instance) { - var reactorClass = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); - var fullName = instance.getFullName(); - initializeTriggerObjects.pr( - "// ***** Start initializing " + fullName + " of class " + reactorClass.getName()); - // Generate the instance self struct containing parameters, state variables, - // and outputs (the "self" struct). - initializeTriggerObjects.pr(CUtil.reactorRefName(instance)+"["+CUtil.runtimeIndex(instance)+"] = new_"+CUtil.getName(reactorClass)+"();"); - // Generate code to initialize the "self" struct in the - // _lf_initialize_trigger_objects function. - generateTraceTableEntries(instance); - generateReactorInstanceExtension(instance); - generateParameterInitialization(instance); - initializeOutputMultiports(instance); - initializeInputMultiports(instance); - recordBuiltinTriggers(instance); - - // Next, initialize the "self" struct with state variables. - // These values may be expressions that refer to the parameter values defined above. - generateStateVariableInitializations(instance); - - // Generate trigger objects for the instance. - generateTimerInitializations(instance); - generateActionInitializations(instance); - generateInitializeActionToken(instance); - generateSetDeadline(instance); - generateModeStructure(instance); - - // Recursively generate code for the children. - for (ReactorInstance child : instance.children) { - // If this reactor is a placeholder for a bank of reactors, then generate - // an array of instances of reactors and create an enclosing for loop. - // Need to do this for each of the builders into which the code writes. - startTimeStep.startScopedBlock(child); - initializeTriggerObjects.startScopedBlock(child); - generateReactorInstance(child); - initializeTriggerObjects.endScopedBlock(); - startTimeStep.endScopedBlock(); + // For this instance, define what must be done at the start of + // each time step. This sets up the tables that are used by the + // _lf_start_time_step() function in reactor_common.c. + // Note that this function is also run once at the end + // so that it can deallocate any memory. + generateStartTimeStep(instance); + initializeTriggerObjects.pr("//***** End initializing " + fullName); + } + + /** + * For each action of the specified reactor instance, generate initialization code for the offset + * and period fields. + * + * @param instance The reactor. + */ + private void generateActionInitializations(ReactorInstance instance) { + initializeTriggerObjects.pr(CActionGenerator.generateInitializers(instance)); + } + + /** + * Initialize actions by creating a lf_token_t in the self struct. This has the information + * required to allocate memory for the action payload. Skip any action that is not actually used + * as a trigger. + * + * @param reactor The reactor containing the actions. + */ + private void generateInitializeActionToken(ReactorInstance reactor) { + for (ActionInstance action : reactor.actions) { + // Skip this step if the action is not in use. + if (action.getParent().getTriggers().contains(action)) { + var type = getInferredType(action.getDefinition()); + var payloadSize = "0"; + if (!type.isUndefined()) { + var typeStr = types.getTargetType(type); + if (CUtil.isTokenType(type, types)) { + typeStr = CUtil.rootType(typeStr); + } + if (typeStr != null && !typeStr.equals("") && !typeStr.equals("void")) { + payloadSize = "sizeof(" + typeStr + ")"; + } } - // For this instance, define what must be done at the start of - // each time step. This sets up the tables that are used by the - // _lf_start_time_step() function in reactor_common.c. - // Note that this function is also run once at the end - // so that it can deallocate any memory. - generateStartTimeStep(instance); - initializeTriggerObjects.pr("//***** End initializing " + fullName); - } - - /** - * For each action of the specified reactor instance, generate initialization code - * for the offset and period fields. - * @param instance The reactor. - */ - private void generateActionInitializations(ReactorInstance instance) { - initializeTriggerObjects.pr(CActionGenerator.generateInitializers(instance)); + var selfStruct = CUtil.reactorRef(action.getParent()); + initializeTriggerObjects.pr( + CActionGenerator.generateTokenInitializer(selfStruct, action.getName(), payloadSize)); + } } - - /** - * Initialize actions by creating a lf_token_t in the self struct. - * This has the information required to allocate memory for the action payload. - * Skip any action that is not actually used as a trigger. - * @param reactor The reactor containing the actions. - */ - private void generateInitializeActionToken(ReactorInstance reactor) { - for (ActionInstance action : reactor.actions) { - // Skip this step if the action is not in use. - if (action.getParent().getTriggers().contains(action) - ) { - var type = getInferredType(action.getDefinition()); - var payloadSize = "0"; - if (!type.isUndefined()) { - var typeStr = types.getTargetType(type); - if (CUtil.isTokenType(type, types)) { - typeStr = CUtil.rootType(typeStr); - } - if (typeStr != null && !typeStr.equals("") && !typeStr.equals("void")) { - payloadSize = "sizeof("+typeStr+")"; - } - } - - var selfStruct = CUtil.reactorRef(action.getParent()); - initializeTriggerObjects.pr( - CActionGenerator.generateTokenInitializer( - selfStruct, action.getName(), payloadSize - ) - ); - } + } + + /** + * Generate code that is executed while the reactor instance is being initialized. This is + * provided as an extension point for subclasses. Normally, the reactions argument is the full + * list of reactions, but for the top-level of a federate, will be a subset of reactions that is + * relevant to the federate. + * + * @param instance The reactor instance. + */ + protected void generateReactorInstanceExtension(ReactorInstance instance) { + // Do nothing + } + + /** + * Generate code that initializes the state variables for a given instance. Unlike parameters, + * state variables are uniformly initialized for all instances of the same reactor. + * + * @param instance The reactor class instance + */ + protected void generateStateVariableInitializations(ReactorInstance instance) { + var reactorClass = instance.getDefinition().getReactorClass(); + var selfRef = CUtil.reactorRef(instance); + for (StateVar stateVar : allStateVars(toDefinition(reactorClass))) { + if (isInitialized(stateVar)) { + var mode = + stateVar.eContainer() instanceof Mode + ? instance.lookupModeInstance((Mode) stateVar.eContainer()) + : instance.getMode(false); + initializeTriggerObjects.pr( + CStateGenerator.generateInitializer(instance, selfRef, stateVar, mode, types)); + if (mode != null && stateVar.isReset()) { + modalStateResetCount += instance.getTotalWidth(); } + } } - - /** - * Generate code that is executed while the reactor instance is being initialized. - * This is provided as an extension point for subclasses. - * Normally, the reactions argument is the full list of reactions, - * but for the top-level of a federate, will be a subset of reactions that - * is relevant to the federate. - * @param instance The reactor instance. - */ - protected void generateReactorInstanceExtension(ReactorInstance instance) { - // Do nothing + } + + /** + * Generate code to set the deadline field of the reactions in the specified reactor instance. + * + * @param instance The reactor instance. + */ + private void generateSetDeadline(ReactorInstance instance) { + for (ReactionInstance reaction : instance.reactions) { + var selfRef = CUtil.reactorRef(reaction.getParent()) + "->_lf__reaction_" + reaction.index; + if (reaction.declaredDeadline != null) { + var deadline = reaction.declaredDeadline.maxDelay; + initializeTriggerObjects.pr( + selfRef + ".deadline = " + types.getTargetTimeExpr(deadline) + ";"); + } else { // No deadline. + initializeTriggerObjects.pr(selfRef + ".deadline = NEVER;"); + } } - - /** - * Generate code that initializes the state variables for a given instance. - * Unlike parameters, state variables are uniformly initialized for all instances - * of the same reactor. - * @param instance The reactor class instance - */ - protected void generateStateVariableInitializations(ReactorInstance instance) { - var reactorClass = instance.getDefinition().getReactorClass(); - var selfRef = CUtil.reactorRef(instance); - for (StateVar stateVar : allStateVars(toDefinition(reactorClass))) { - if (isInitialized(stateVar)) { - var mode = stateVar.eContainer() instanceof Mode ? - instance.lookupModeInstance((Mode) stateVar.eContainer()) : - instance.getMode(false); - initializeTriggerObjects.pr(CStateGenerator.generateInitializer( - instance, - selfRef, - stateVar, - mode, - types - )); - if (mode != null && stateVar.isReset()) { - modalStateResetCount += instance.getTotalWidth(); - } - } - } + } + + /** + * Generate code to initialize modes. + * + * @param instance The reactor instance. + */ + private void generateModeStructure(ReactorInstance instance) { + CModesGenerator.generateModeStructure(instance, initializeTriggerObjects); + if (!instance.modes.isEmpty()) { + modalReactorCount += instance.getTotalWidth(); } - - /** - * Generate code to set the deadline field of the reactions in the - * specified reactor instance. - * @param instance The reactor instance. - */ - private void generateSetDeadline(ReactorInstance instance) { - for (ReactionInstance reaction : instance.reactions) { - var selfRef = CUtil.reactorRef(reaction.getParent())+"->_lf__reaction_"+reaction.index; - if (reaction.declaredDeadline != null) { - var deadline = reaction.declaredDeadline.maxDelay; - initializeTriggerObjects.pr(selfRef+".deadline = "+types.getTargetTimeExpr(deadline)+";"); - } else { // No deadline. - initializeTriggerObjects.pr(selfRef+".deadline = NEVER;"); - } - } + } + + /** + * Generate runtime initialization code for parameters of a given reactor instance + * + * @param instance The reactor instance. + */ + protected void generateParameterInitialization(ReactorInstance instance) { + var selfRef = CUtil.reactorRef(instance); + // Set the local bank_index variable so that initializers can use it. + initializeTriggerObjects.pr( + "bank_index = " + + CUtil.bankIndex(instance) + + ";" + + " SUPPRESS_UNUSED_WARNING(bank_index);"); + for (ParameterInstance parameter : instance.parameters) { + // NOTE: we now use the resolved literal value. For better efficiency, we could + // store constants in a global array and refer to its elements to avoid duplicate + // memory allocations. + // NOTE: If the parameter is initialized with a static initializer for an array + // or struct (the initialization expression is surrounded by { ... }), then we + // have to declare a static variable to ensure that the memory is put in data space + // and not on the stack. + // FIXME: Is there a better way to determine this than the string comparison? + var initializer = CParameterGenerator.getInitializer(parameter); + if (initializer.startsWith("{")) { + var temporaryVariableName = parameter.uniqueID(); + initializeTriggerObjects.pr( + String.join( + "\n", + "static " + + types.getVariableDeclaration(parameter.type, temporaryVariableName, true) + + " = " + + initializer + + ";", + selfRef + "->" + parameter.getName() + " = " + temporaryVariableName + ";")); + } else { + initializeTriggerObjects.pr( + selfRef + "->" + parameter.getName() + " = " + initializer + ";"); + } } - - /** - * Generate code to initialize modes. - * @param instance The reactor instance. - */ - private void generateModeStructure(ReactorInstance instance) { - CModesGenerator.generateModeStructure(instance, initializeTriggerObjects); - if (!instance.modes.isEmpty()) { - modalReactorCount += instance.getTotalWidth(); - } + } + + /** + * Generate code that mallocs memory for any output multiports. + * + * @param reactor The reactor instance. + */ + private void initializeOutputMultiports(ReactorInstance reactor) { + var reactorSelfStruct = CUtil.reactorRef(reactor); + for (PortInstance output : reactor.outputs) { + initializeTriggerObjects.pr( + CPortGenerator.initializeOutputMultiport(output, reactorSelfStruct)); } - - /** - * Generate runtime initialization code for parameters of a given reactor instance - * @param instance The reactor instance. - */ - protected void generateParameterInitialization(ReactorInstance instance) { - var selfRef = CUtil.reactorRef(instance); - // Set the local bank_index variable so that initializers can use it. - initializeTriggerObjects.pr("bank_index = "+CUtil.bankIndex(instance)+";" - + " SUPPRESS_UNUSED_WARNING(bank_index);"); - for (ParameterInstance parameter : instance.parameters) { - // NOTE: we now use the resolved literal value. For better efficiency, we could - // store constants in a global array and refer to its elements to avoid duplicate - // memory allocations. - // NOTE: If the parameter is initialized with a static initializer for an array - // or struct (the initialization expression is surrounded by { ... }), then we - // have to declare a static variable to ensure that the memory is put in data space - // and not on the stack. - // FIXME: Is there a better way to determine this than the string comparison? - var initializer = CParameterGenerator.getInitializer(parameter); - if (initializer.startsWith("{")) { - var temporaryVariableName = parameter.uniqueID(); - initializeTriggerObjects.pr(String.join("\n", - "static "+types.getVariableDeclaration(parameter.type, temporaryVariableName, true)+" = "+initializer+";", - selfRef+"->"+parameter.getName()+" = "+temporaryVariableName+";" - )); - } else { - initializeTriggerObjects.pr(selfRef+"->"+parameter.getName()+" = "+initializer+";"); - } - } + } + + /** + * Allocate memory for inputs. + * + * @param reactor The reactor. + */ + private void initializeInputMultiports(ReactorInstance reactor) { + var reactorSelfStruct = CUtil.reactorRef(reactor); + for (PortInstance input : reactor.inputs) { + initializeTriggerObjects.pr( + CPortGenerator.initializeInputMultiport(input, reactorSelfStruct)); } - - /** - * Generate code that mallocs memory for any output multiports. - * @param reactor The reactor instance. - */ - private void initializeOutputMultiports(ReactorInstance reactor) { - var reactorSelfStruct = CUtil.reactorRef(reactor); - for (PortInstance output : reactor.outputs) { - initializeTriggerObjects.pr(CPortGenerator.initializeOutputMultiport( - output, - reactorSelfStruct - )); - } + } + + @Override + public TargetTypes getTargetTypes() { + return types; + } + + /** + * @param context + * @return + */ + protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { + return new CDockerGenerator(context); + } + + // ////////////////////////////////////////// + // // Protected methods. + + // Perform set up that does not generate code + protected void setUpGeneralParameters() { + accommodatePhysicalActionsIfPresent(); + targetConfig.compileDefinitions.put("LOG_LEVEL", targetConfig.logLevel.ordinal() + ""); + targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); + // Create the main reactor instance if there is a main reactor. + createMainReactorInstance(); + if (hasModalReactors) { + // So that each separate compile knows about modal reactors, do this: + targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); } - - /** - * Allocate memory for inputs. - * @param reactor The reactor. - */ - private void initializeInputMultiports(ReactorInstance reactor) { - var reactorSelfStruct = CUtil.reactorRef(reactor); - for (PortInstance input : reactor.inputs) { - initializeTriggerObjects.pr(CPortGenerator.initializeInputMultiport( - input, - reactorSelfStruct - )); - } + if (targetConfig.threading + && targetConfig.platformOptions.platform == Platform.ARDUINO + && (targetConfig.platformOptions.board == null + || !targetConfig.platformOptions.board.contains("mbed"))) { + // non-MBED boards should not use threading + System.out.println( + "Threading is incompatible on your current Arduino flavor. Setting threading to false."); + targetConfig.threading = false; } - @Override - public TargetTypes getTargetTypes() { - return types; + if (targetConfig.platformOptions.platform == Platform.ARDUINO + && !targetConfig.noCompile + && targetConfig.platformOptions.board == null) { + System.out.println( + "To enable compilation for the Arduino platform, you must specify the fully-qualified" + + " board name (FQBN) in the target property. For example, platform: {name: arduino," + + " board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target" + + " code only."); + targetConfig.noCompile = true; } - - /** - * - * @param context - * @return - */ - protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { - return new CDockerGenerator(context); + if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake + pickScheduler(); + // FIXME: this and pickScheduler should be combined. + targetConfig.compileDefinitions.put("SCHEDULER", targetConfig.schedulerType.name()); + targetConfig.compileDefinitions.put( + "NUMBER_OF_WORKERS", String.valueOf(targetConfig.workers)); } + pickCompilePlatform(); + } - // ////////////////////////////////////////// - // // Protected methods. - - // Perform set up that does not generate code - protected void setUpGeneralParameters() { - accommodatePhysicalActionsIfPresent(); - targetConfig.compileDefinitions.put("LOG_LEVEL", targetConfig.logLevel.ordinal() + ""); - targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); - // Create the main reactor instance if there is a main reactor. - createMainReactorInstance(); - if (hasModalReactors) { - // So that each separate compile knows about modal reactors, do this: - targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); - } - if (targetConfig.threading && targetConfig.platformOptions.platform == Platform.ARDUINO - && (targetConfig.platformOptions.board == null || !targetConfig.platformOptions.board.contains("mbed"))) { - //non-MBED boards should not use threading - System.out.println("Threading is incompatible on your current Arduino flavor. Setting threading to false."); - targetConfig.threading = false; - } - - if (targetConfig.platformOptions.platform == Platform.ARDUINO && !targetConfig.noCompile - && targetConfig.platformOptions.board == null) { - System.out.println("To enable compilation for the Arduino platform, you must specify the fully-qualified board name (FQBN) in the target property. For example, platform: {name: arduino, board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target code only."); - targetConfig.noCompile = true; - } - if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake - pickScheduler(); - // FIXME: this and pickScheduler should be combined. - targetConfig.compileDefinitions.put( - "SCHEDULER", - targetConfig.schedulerType.name() - ); - targetConfig.compileDefinitions.put( - "NUMBER_OF_WORKERS", - String.valueOf(targetConfig.workers) - ); - } - pickCompilePlatform(); + protected void handleProtoFiles() { + // Handle .proto files. + for (String file : targetConfig.protoFiles) { + this.processProtoFile(file); } - - protected void handleProtoFiles() { - // Handle .proto files. - for (String file : targetConfig.protoFiles) { - this.processProtoFile(file); - } + } + + /** + * Generate code that needs to appear at the top of the generated C file, such as #define and + * #include statements. + */ + public String generateDirectives() { + CodeBuilder code = new CodeBuilder(); + code.prComment("Code generated by the Lingua Franca compiler from:"); + code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); + code.pr( + CPreambleGenerator.generateDefineDirectives( + targetConfig, fileConfig.getSrcGenPath(), hasModalReactors)); + code.pr(CPreambleGenerator.generateIncludeStatements(targetConfig, CCppMode)); + return code.toString(); + } + + /** Generate top-level preamble code. */ + protected String generateTopLevelPreambles(Reactor reactor) { + CodeBuilder builder = new CodeBuilder(); + var guard = "TOP_LEVEL_PREAMBLE_" + reactor.eContainer().hashCode() + "_H"; + builder.pr("#ifndef " + guard); + builder.pr("#define " + guard); + Stream.concat(Stream.of(reactor), ASTUtils.allNestedClasses(reactor)) + .flatMap(it -> ((Model) it.eContainer()).getPreambles().stream()) + .collect(Collectors.toSet()) + .forEach(it -> builder.pr(toText(it.getCode()))); + for (String file : targetConfig.protoFiles) { + var dotIndex = file.lastIndexOf("."); + var rootFilename = file; + if (dotIndex > 0) { + rootFilename = file.substring(0, dotIndex); + } + code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); + builder.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); } - - /** - * Generate code that needs to appear at the top of the generated - * C file, such as #define and #include statements. - */ - public String generateDirectives() { - CodeBuilder code = new CodeBuilder(); - code.prComment("Code generated by the Lingua Franca compiler from:"); - code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); - code.pr(CPreambleGenerator.generateDefineDirectives( - targetConfig, - fileConfig.getSrcGenPath(), - hasModalReactors - )); - code.pr(CPreambleGenerator.generateIncludeStatements( - targetConfig, - CCppMode - )); - return code.toString(); + builder.pr("#endif"); + return builder.toString(); + } + + protected boolean targetLanguageIsCpp() { + return CCppMode; + } + + /** + * Given a line of text from the output of a compiler, return an instance of ErrorFileAndLine if + * the line is recognized as the first line of an error message. Otherwise, return null. + * + * @param line A line of output from a compiler or other external tool that might generate errors. + * @return If the line is recognized as the start of an error message, then return a class + * containing the path to the file on which the error occurred (or null if there is none), the + * line number (or the string "1" if there is none), the character position (or the string "0" + * if there is none), and the message (or an empty string if there is none). + */ + @Override + public GeneratorBase.ErrorFileAndLine parseCommandOutput(String line) { + var matcher = compileErrorPattern.matcher(line); + if (matcher.find()) { + var result = new ErrorFileAndLine(); + result.filepath = matcher.group("path"); + result.line = matcher.group("line"); + result.character = matcher.group("column"); + result.message = matcher.group("message"); + + if (!result.message.toLowerCase().contains("error:")) { + result.isError = false; + } + return result; } - - /** - * Generate top-level preamble code. - */ - protected String generateTopLevelPreambles(Reactor reactor) { - CodeBuilder builder = new CodeBuilder(); - var guard = "TOP_LEVEL_PREAMBLE_" + reactor.eContainer().hashCode() + "_H"; - builder.pr("#ifndef " + guard); - builder.pr("#define " + guard); - Stream.concat(Stream.of(reactor), ASTUtils.allNestedClasses(reactor)) - .flatMap(it -> ((Model) it.eContainer()).getPreambles().stream()) - .collect(Collectors.toSet()) - .forEach(it -> builder.pr(toText(it.getCode()))); - for (String file : targetConfig.protoFiles) { - var dotIndex = file.lastIndexOf("."); - var rootFilename = file; - if (dotIndex > 0) { - rootFilename = file.substring(0, dotIndex); - } - code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); - builder.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); + return null; + } + + //////////////////////////////////////////// + //// Private methods. + /** Returns the Target enum for this generator */ + @Override + public Target getTarget() { + return Target.C; + } + + //////////////////////////////////////////////////////////// + //// Private methods + + /** + * If a main or federated reactor has been declared, create a ReactorInstance for this top level. + * This will also assign levels to reactions, then, if the program is federated, perform an AST + * transformation to disconnect connections between federates. + */ + private void createMainReactorInstance() { + if (this.mainDef != null) { + if (this.main == null) { + // Recursively build instances. + this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter); + var reactionInstanceGraph = this.main.assignLevels(); + if (reactionInstanceGraph.nodeCount() > 0) { + errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); + return; } - builder.pr("#endif"); - return builder.toString(); - } - - protected boolean targetLanguageIsCpp() { - return CCppMode; - } - - /** Given a line of text from the output of a compiler, return - * an instance of ErrorFileAndLine if the line is recognized as - * the first line of an error message. Otherwise, return null. - * @param line A line of output from a compiler or other external - * tool that might generate errors. - * @return If the line is recognized as the start of an error message, - * then return a class containing the path to the file on which the - * error occurred (or null if there is none), the line number (or the - * string "1" if there is none), the character position (or the string - * "0" if there is none), and the message (or an empty string if there - * is none). - */ - @Override - public GeneratorBase.ErrorFileAndLine parseCommandOutput(String line) { - var matcher = compileErrorPattern.matcher(line); - if (matcher.find()) { - var result = new ErrorFileAndLine(); - result.filepath = matcher.group("path"); - result.line = matcher.group("line"); - result.character = matcher.group("column"); - result.message = matcher.group("message"); - - if (!result.message.toLowerCase().contains("error:")) { - result.isError = false; - } - return result; + if (hasDeadlines) { + this.main.assignDeadlines(); } - return null; - } - - //////////////////////////////////////////// - //// Private methods. - /** Returns the Target enum for this generator */ - @Override - public Target getTarget() { - return Target.C; - } - - //////////////////////////////////////////////////////////// - //// Private methods - - /** - * If a main or federated reactor has been declared, create a ReactorInstance - * for this top level. This will also assign levels to reactions, then, - * if the program is federated, perform an AST transformation to disconnect - * connections between federates. - */ - private void createMainReactorInstance() { - if (this.mainDef != null) { - if (this.main == null) { - // Recursively build instances. - this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter); - var reactionInstanceGraph = this.main.assignLevels(); - if (reactionInstanceGraph.nodeCount() > 0) { - errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); - return; - } - if (hasDeadlines) { - this.main.assignDeadlines(); - } - // Inform the run-time of the breadth/parallelism of the reaction graph - var breadth = reactionInstanceGraph.getBreadth(); - if (breadth == 0) { - errorReporter.reportWarning("The program has no reactions"); - } else { - targetConfig.compileDefinitions.put( - "LF_REACTION_GRAPH_BREADTH", - String.valueOf(reactionInstanceGraph.getBreadth()) - ); - } - } + // Inform the run-time of the breadth/parallelism of the reaction graph + var breadth = reactionInstanceGraph.getBreadth(); + if (breadth == 0) { + errorReporter.reportWarning("The program has no reactions"); + } else { + targetConfig.compileDefinitions.put( + "LF_REACTION_GRAPH_BREADTH", String.valueOf(reactionInstanceGraph.getBreadth())); } - + } } - - /** - * Generate an array of self structs for the reactor - * and one for each of its children. - * @param r The reactor instance. - */ - private void generateSelfStructs(ReactorInstance r) { - initializeTriggerObjects.pr(CUtil.selfType(r)+"* "+CUtil.reactorRefName(r)+"["+r.getTotalWidth()+"];"); - initializeTriggerObjects.pr("SUPPRESS_UNUSED_WARNING("+CUtil.reactorRefName(r)+");"); - for (ReactorInstance child : r.children) { - generateSelfStructs(child); - } + } + + /** + * Generate an array of self structs for the reactor and one for each of its children. + * + * @param r The reactor instance. + */ + private void generateSelfStructs(ReactorInstance r) { + initializeTriggerObjects.pr( + CUtil.selfType(r) + "* " + CUtil.reactorRefName(r) + "[" + r.getTotalWidth() + "];"); + initializeTriggerObjects.pr("SUPPRESS_UNUSED_WARNING(" + CUtil.reactorRefName(r) + ");"); + for (ReactorInstance child : r.children) { + generateSelfStructs(child); } + } } diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 100f3deaa4..dae1f01230 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; @@ -34,1133 +33,1351 @@ import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.util.StringUtil; public class CReactionGenerator { - protected static String DISABLE_REACTION_INITIALIZATION_MARKER - = "// **** Do not include initialization code in this reaction."; // FIXME: Such markers should not exist (#1687) - - /** - * Generate necessary initialization code inside the body of the reaction that belongs to reactor decl. - * @param body The body of the reaction. Used to check for the DISABLE_REACTION_INITIALIZATION_MARKER. - * @param reaction The initialization code will be generated for this specific reaction - * @param decl The reactor that has the reaction - * @param reactionIndex The index of the reaction relative to other reactions in the reactor, starting from 0 - */ - public static String generateInitializationForReaction(String body, - Reaction reaction, - Reactor decl, - int reactionIndex, - CTypes types, - ErrorReporter errorReporter, - Instantiation mainDef, - boolean requiresTypes) { - Reactor reactor = ASTUtils.toDefinition(decl); + protected static String DISABLE_REACTION_INITIALIZATION_MARKER = + "// **** Do not include initialization code in this reaction."; // FIXME: Such markers should + // not exist (#1687) - // Construct the reactionInitialization code to go into - // the body of the function before the verbatim code. - CodeBuilder reactionInitialization = new CodeBuilder(); + /** + * Generate necessary initialization code inside the body of the reaction that belongs to reactor + * decl. + * + * @param body The body of the reaction. Used to check for the + * DISABLE_REACTION_INITIALIZATION_MARKER. + * @param reaction The initialization code will be generated for this specific reaction + * @param decl The reactor that has the reaction + * @param reactionIndex The index of the reaction relative to other reactions in the reactor, + * starting from 0 + */ + public static String generateInitializationForReaction( + String body, + Reaction reaction, + Reactor decl, + int reactionIndex, + CTypes types, + ErrorReporter errorReporter, + Instantiation mainDef, + boolean requiresTypes) { + Reactor reactor = ASTUtils.toDefinition(decl); - CodeBuilder code = new CodeBuilder(); + // Construct the reactionInitialization code to go into + // the body of the function before the verbatim code. + CodeBuilder reactionInitialization = new CodeBuilder(); - // Define the "self" struct. - String structType = CUtil.selfType(decl); - // A null structType means there are no inputs, state, - // or anything else. No need to declare it. - if (structType != null) { - code.pr(String.join("\n", - structType+"* self = ("+structType+"*)instance_args; SUPPRESS_UNUSED_WARNING(self);" - )); - } + CodeBuilder code = new CodeBuilder(); - // Do not generate the initialization code if the body is marked - // to not generate it. - if (body.startsWith(DISABLE_REACTION_INITIALIZATION_MARKER)) { - return code.toString(); - } + // Define the "self" struct. + String structType = CUtil.selfType(decl); + // A null structType means there are no inputs, state, + // or anything else. No need to declare it. + if (structType != null) { + code.pr( + String.join( + "\n", + structType + + "* self = (" + + structType + + "*)instance_args; SUPPRESS_UNUSED_WARNING(self);")); + } - // A reaction may send to or receive from multiple ports of - // a contained reactor. The variables for these ports need to - // all be declared as fields of the same struct. Hence, we first - // collect the fields to be defined in the structs and then - // generate the structs. - Map fieldsForStructsForContainedReactors = new LinkedHashMap<>(); + // Do not generate the initialization code if the body is marked + // to not generate it. + if (body.startsWith(DISABLE_REACTION_INITIALIZATION_MARKER)) { + return code.toString(); + } - // Actions may appear twice, first as a trigger, then with the outputs. - // But we need to declare it only once. Collect in this data structure - // the actions that are declared as triggered so that if they appear - // again with the outputs, they are not defined a second time. - // That second redefinition would trigger a compile error. - Set actionsAsTriggers = new LinkedHashSet<>(); + // A reaction may send to or receive from multiple ports of + // a contained reactor. The variables for these ports need to + // all be declared as fields of the same struct. Hence, we first + // collect the fields to be defined in the structs and then + // generate the structs. + Map fieldsForStructsForContainedReactors = new LinkedHashMap<>(); - // Next, add the triggers (input and actions; timers are not needed). - // This defines a local variable in the reaction function whose - // name matches that of the trigger. The value of the local variable - // is a struct with a value and is_present field, the latter a boolean - // that indicates whether the input/action is present. - // If the trigger is an output, then it is an output of a - // contained reactor. In this case, a struct with the name - // of the contained reactor is created with one field that is - // a pointer to a struct with a value and is_present field. - // E.g., if the contained reactor is named 'c' and its output - // port is named 'out', then c.out->value c.out->is_present are - // defined so that they can be used in the verbatim code. - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (trigger instanceof VarRef triggerAsVarRef) { - if (triggerAsVarRef.getVariable() instanceof Port) { - generatePortVariablesInReaction( - reactionInitialization, - fieldsForStructsForContainedReactors, - triggerAsVarRef, - decl, - types); - } else if (triggerAsVarRef.getVariable() instanceof Action) { - reactionInitialization.pr(generateActionVariablesInReaction( - (Action) triggerAsVarRef.getVariable(), - decl, - types - )); - actionsAsTriggers.add((Action) triggerAsVarRef.getVariable()); - } - } - } - if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { - // No triggers are given, which means react to any input. - // Declare an argument for every input. - // NOTE: this does not include contained outputs. - for (Input input : reactor.getInputs()) { - reactionInitialization.pr(generateInputVariablesInReaction(input, decl, types)); - } - } - // Define argument for non-triggering inputs. - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - if (src.getVariable() instanceof Port) { - generatePortVariablesInReaction(reactionInitialization, fieldsForStructsForContainedReactors, src, decl, types); - } else if (src.getVariable() instanceof Action) { - // It's a bit odd to read but not be triggered by an action, but - // OK, I guess we allow it. - reactionInitialization.pr(generateActionVariablesInReaction( - (Action) src.getVariable(), - decl, - types - )); - actionsAsTriggers.add((Action) src.getVariable()); - } - } + // Actions may appear twice, first as a trigger, then with the outputs. + // But we need to declare it only once. Collect in this data structure + // the actions that are declared as triggered so that if they appear + // again with the outputs, they are not defined a second time. + // That second redefinition would trigger a compile error. + Set actionsAsTriggers = new LinkedHashSet<>(); - // Define variables for each declared output or action. - // In the case of outputs, the variable is a pointer to where the - // output is stored. This gives the reaction code access to any previous - // value that may have been written to that output in an earlier reaction. - if (reaction.getEffects() != null) { - for (VarRef effect : reaction.getEffects()) { - Variable variable = effect.getVariable(); - if (variable instanceof Action) { - // It is an action, not an output. - // If it has already appeared as trigger, do not redefine it. - if (!actionsAsTriggers.contains(effect.getVariable())) { - reactionInitialization.pr(CGenerator.variableStructType(variable, decl, false)+"* "+variable.getName()+" = &self->_lf_"+variable.getName()+";"); - } - } else if (effect.getVariable() instanceof Mode) { - // Mode change effect - int idx = ASTUtils.allModes(reactor).indexOf((Mode)effect.getVariable()); - String name = effect.getVariable().getName(); - if (idx >= 0) { - reactionInitialization.pr( - "reactor_mode_t* " + name + " = &self->_lf__modes[" + idx + "];\n" - + "lf_mode_change_type_t _lf_" + name + "_change_type = " - + (effect.getTransition() == ModeTransition.HISTORY ? - "history_transition" : "reset_transition") - + ";" - ); - } else { - errorReporter.reportError( - reaction, - "In generateReaction(): " + name + " not a valid mode of this reactor." - ); - } - } else { - if (variable instanceof Output) { - reactionInitialization.pr(generateOutputVariablesInReaction( - effect, - decl, - errorReporter, - requiresTypes - )); - } else if (variable instanceof Input) { - // It is the input of a contained reactor. - generateVariablesForSendingToContainedReactors( - reactionInitialization, - fieldsForStructsForContainedReactors, - effect.getContainer(), - (Input) variable - ); - } else { - errorReporter.reportError( - reaction, - "In generateReaction(): effect is neither an input nor an output." - ); - } - } - } + // Next, add the triggers (input and actions; timers are not needed). + // This defines a local variable in the reaction function whose + // name matches that of the trigger. The value of the local variable + // is a struct with a value and is_present field, the latter a boolean + // that indicates whether the input/action is present. + // If the trigger is an output, then it is an output of a + // contained reactor. In this case, a struct with the name + // of the contained reactor is created with one field that is + // a pointer to a struct with a value and is_present field. + // E.g., if the contained reactor is named 'c' and its output + // port is named 'out', then c.out->value c.out->is_present are + // defined so that they can be used in the verbatim code. + for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { + if (trigger instanceof VarRef triggerAsVarRef) { + if (triggerAsVarRef.getVariable() instanceof Port) { + generatePortVariablesInReaction( + reactionInitialization, + fieldsForStructsForContainedReactors, + triggerAsVarRef, + decl, + types); + } else if (triggerAsVarRef.getVariable() instanceof Action) { + reactionInitialization.pr( + generateActionVariablesInReaction( + (Action) triggerAsVarRef.getVariable(), decl, types)); + actionsAsTriggers.add((Action) triggerAsVarRef.getVariable()); } - // Before the reaction initialization, - // generate the structs used for communication to and from contained reactors. - for (Instantiation containedReactor : fieldsForStructsForContainedReactors.keySet()) { - String array = ""; - if (containedReactor.getWidthSpec() != null) { - String containedReactorWidthVar = generateWidthVariable(containedReactor.getName()); - code.pr("int "+containedReactorWidthVar+" = self->_lf_"+containedReactorWidthVar+";"); - // Windows does not support variables in arrays declared on the stack, - // so we use the maximum size over all bank members. - array = "["+maxContainedReactorBankWidth(containedReactor, null, 0, mainDef)+"]"; - } - code.pr(String.join("\n", - "struct "+containedReactor.getName()+" {", - " "+fieldsForStructsForContainedReactors.get(containedReactor)+"", - "} "+containedReactor.getName()+array+";" - )); - } - // Next generate all the collected setup code. - code.pr(reactionInitialization.toString()); - return code.toString(); + } + } + if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { + // No triggers are given, which means react to any input. + // Declare an argument for every input. + // NOTE: this does not include contained outputs. + for (Input input : reactor.getInputs()) { + reactionInitialization.pr(generateInputVariablesInReaction(input, decl, types)); + } + } + // Define argument for non-triggering inputs. + for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { + if (src.getVariable() instanceof Port) { + generatePortVariablesInReaction( + reactionInitialization, fieldsForStructsForContainedReactors, src, decl, types); + } else if (src.getVariable() instanceof Action) { + // It's a bit odd to read but not be triggered by an action, but + // OK, I guess we allow it. + reactionInitialization.pr( + generateActionVariablesInReaction((Action) src.getVariable(), decl, types)); + actionsAsTriggers.add((Action) src.getVariable()); + } } - /** - * Return the maximum bank width for the given instantiation within all - * instantiations of its parent reactor. - * On the first call to this method, the breadcrumbs should be null and the max - * argument should be zero. On recursive calls, breadcrumbs is a list of nested - * instantiations, the max is the maximum width found so far. The search for - * instances of the parent reactor will begin with the last instantiation - * in the specified list. - * - * This rather complicated method is used when a reaction sends or receives data - * to or from a bank of contained reactors. There will be an array of structs on - * the self struct of the parent, and the size of the array is conservatively set - * to the maximum of all the identified bank widths. This is a bit wasteful of - * memory, but it avoids having to malloc the array for each instance, and in - * typical usage, there will be few instances or instances that are all the same - * width. - * - * @param containedReactor The contained reactor instantiation. - * @param breadcrumbs null on first call (non-recursive). - * @param max 0 on first call. - */ - public static int maxContainedReactorBankWidth( - Instantiation containedReactor, - LinkedList breadcrumbs, - int max, - Instantiation mainDef - ) { - // If the instantiation is not a bank, return 1. - if (containedReactor.getWidthSpec() == null) { - return 1; - } - // If there is no main, then we just use the default width. - if (mainDef == null) { - return ASTUtils.width(containedReactor.getWidthSpec(), null); - } - LinkedList nestedBreadcrumbs = breadcrumbs; - if (nestedBreadcrumbs == null) { - nestedBreadcrumbs = new LinkedList<>(); - nestedBreadcrumbs.add(mainDef); - } - int result = max; - Reactor parent = (Reactor) containedReactor.eContainer(); - if (parent == ASTUtils.toDefinition(mainDef.getReactorClass())) { - // The parent is main, so there can't be any other instantiations of it. - return ASTUtils.width(containedReactor.getWidthSpec(), null); - } - // Search for instances of the parent within the tail of the breadcrumbs list. - Reactor container = ASTUtils.toDefinition(nestedBreadcrumbs.get(0).getReactorClass()); - for (Instantiation instantiation : container.getInstantiations()) { - // Put this new instantiation at the head of the list. - nestedBreadcrumbs.add(0, instantiation); - if (ASTUtils.toDefinition(instantiation.getReactorClass()) == parent) { - // Found a matching instantiation of the parent. - // Evaluate the original width specification in this context. - int candidate = ASTUtils.width(containedReactor.getWidthSpec(), nestedBreadcrumbs); - if (candidate > result) { - result = candidate; - } - } else { - // Found some other instantiation, not the parent. - // Search within it for instantiations of the parent. - // Note that we assume here that the parent cannot contain - // instances of itself. - int candidate = maxContainedReactorBankWidth(containedReactor, nestedBreadcrumbs, result, mainDef); - if (candidate > result) { - result = candidate; - } - } - nestedBreadcrumbs.remove(); + // Define variables for each declared output or action. + // In the case of outputs, the variable is a pointer to where the + // output is stored. This gives the reaction code access to any previous + // value that may have been written to that output in an earlier reaction. + if (reaction.getEffects() != null) { + for (VarRef effect : reaction.getEffects()) { + Variable variable = effect.getVariable(); + if (variable instanceof Action) { + // It is an action, not an output. + // If it has already appeared as trigger, do not redefine it. + if (!actionsAsTriggers.contains(effect.getVariable())) { + reactionInitialization.pr( + CGenerator.variableStructType(variable, decl, false) + + "* " + + variable.getName() + + " = &self->_lf_" + + variable.getName() + + ";"); + } + } else if (effect.getVariable() instanceof Mode) { + // Mode change effect + int idx = ASTUtils.allModes(reactor).indexOf((Mode) effect.getVariable()); + String name = effect.getVariable().getName(); + if (idx >= 0) { + reactionInitialization.pr( + "reactor_mode_t* " + + name + + " = &self->_lf__modes[" + + idx + + "];\n" + + "lf_mode_change_type_t _lf_" + + name + + "_change_type = " + + (effect.getTransition() == ModeTransition.HISTORY + ? "history_transition" + : "reset_transition") + + ";"); + } else { + errorReporter.reportError( + reaction, "In generateReaction(): " + name + " not a valid mode of this reactor."); + } + } else { + if (variable instanceof Output) { + reactionInitialization.pr( + generateOutputVariablesInReaction(effect, decl, errorReporter, requiresTypes)); + } else if (variable instanceof Input) { + // It is the input of a contained reactor. + generateVariablesForSendingToContainedReactors( + reactionInitialization, + fieldsForStructsForContainedReactors, + effect.getContainer(), + (Input) variable); + } else if (variable instanceof Watchdog) { + reactionInitialization.pr(generateWatchdogVariablesInReaction(effect, decl)); + } else { + errorReporter.reportError( + reaction, "In generateReaction(): effect is neither an input nor an output."); + } } - return result; + } } - - /** - * Generate code for the body of a reaction that takes an input and - * schedules an action with the value of that input. - * @param actionName The action to schedule - */ - public static String generateDelayBody(String ref, String actionName, boolean isTokenType) { - // Note that the action.type set by the base class is actually - // the port type. - return isTokenType ? - String.join("\n", - "if ("+ref+"->is_present) {", - " // Put the whole token on the event queue, not just the payload.", - " // This way, the length and element_size are transported.", - " lf_schedule_token("+actionName+", 0, "+ref+"->token);", - "}" - ) : - "lf_schedule_copy("+actionName+", 0, &"+ref+"->value, 1); // Length is 1."; + // Before the reaction initialization, + // generate the structs used for communication to and from contained reactors. + for (Instantiation containedReactor : fieldsForStructsForContainedReactors.keySet()) { + String array = ""; + if (containedReactor.getWidthSpec() != null) { + String containedReactorWidthVar = generateWidthVariable(containedReactor.getName()); + code.pr( + "int " + containedReactorWidthVar + " = self->_lf_" + containedReactorWidthVar + ";"); + // Windows does not support variables in arrays declared on the stack, + // so we use the maximum size over all bank members. + array = "[" + maxContainedReactorBankWidth(containedReactor, null, 0, mainDef) + "]"; + } + code.pr( + String.join( + "\n", + "struct " + containedReactor.getName() + " {", + " " + fieldsForStructsForContainedReactors.get(containedReactor) + "", + "} " + containedReactor.getName() + array + ";")); } + // Next generate all the collected setup code. + code.pr(reactionInitialization.toString()); + return code.toString(); + } - public static String generateForwardBody(String outputName, String targetType, String actionName, boolean isTokenType) { - return isTokenType ? - String.join("\n", - DISABLE_REACTION_INITIALIZATION_MARKER, - "self->_lf_"+outputName+".value = ("+targetType+")self->_lf__"+actionName+".tmplt.token->value;", - "_lf_replace_template_token((token_template_t*)&self->_lf_"+outputName+", (lf_token_t*)self->_lf__"+actionName+".tmplt.token);", - "self->_lf_"+outputName+".is_present = true;" - ) : - "lf_set("+outputName+", "+actionName+"->value);"; + /** + * Return the maximum bank width for the given instantiation within all instantiations of its + * parent reactor. On the first call to this method, the breadcrumbs should be null and the max + * argument should be zero. On recursive calls, breadcrumbs is a list of nested instantiations, + * the max is the maximum width found so far. The search for instances of the parent reactor will + * begin with the last instantiation in the specified list. + * + *

This rather complicated method is used when a reaction sends or receives data to or from a + * bank of contained reactors. There will be an array of structs on the self struct of the parent, + * and the size of the array is conservatively set to the maximum of all the identified bank + * widths. This is a bit wasteful of memory, but it avoids having to malloc the array for each + * instance, and in typical usage, there will be few instances or instances that are all the same + * width. + * + * @param containedReactor The contained reactor instantiation. + * @param breadcrumbs null on first call (non-recursive). + * @param max 0 on first call. + */ + public static int maxContainedReactorBankWidth( + Instantiation containedReactor, + LinkedList breadcrumbs, + int max, + Instantiation mainDef) { + // If the instantiation is not a bank, return 1. + if (containedReactor.getWidthSpec() == null) { + return 1; } - - /** - * Generate into the specified string builder the code to - * initialize local variables for sending data to an input - * of a contained reactor. This will also, if necessary, - * generate entries for local struct definitions into the - * struct argument. These entries point to where the data - * is stored. - * - * @param builder The string builder. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param definition AST node defining the reactor within which this occurs - * @param input Input of the contained reactor. - */ - private static void generateVariablesForSendingToContainedReactors( - CodeBuilder builder, - Map structs, - Instantiation definition, - Input input - ) { - CodeBuilder structBuilder = structs.get(definition); - if (structBuilder == null) { - structBuilder = new CodeBuilder(); - structs.put(definition, structBuilder); + // If there is no main, then we just use the default width. + if (mainDef == null) { + return ASTUtils.width(containedReactor.getWidthSpec(), null); + } + LinkedList nestedBreadcrumbs = breadcrumbs; + if (nestedBreadcrumbs == null) { + nestedBreadcrumbs = new LinkedList<>(); + nestedBreadcrumbs.add(mainDef); + } + int result = max; + Reactor parent = (Reactor) containedReactor.eContainer(); + if (parent == ASTUtils.toDefinition(mainDef.getReactorClass())) { + // The parent is main, so there can't be any other instantiations of it. + return ASTUtils.width(containedReactor.getWidthSpec(), null); + } + // Search for instances of the parent within the tail of the breadcrumbs list. + Reactor container = ASTUtils.toDefinition(nestedBreadcrumbs.get(0).getReactorClass()); + for (Instantiation instantiation : container.getInstantiations()) { + // Put this new instantiation at the head of the list. + nestedBreadcrumbs.add(0, instantiation); + if (ASTUtils.toDefinition(instantiation.getReactorClass()) == parent) { + // Found a matching instantiation of the parent. + // Evaluate the original width specification in this context. + int candidate = ASTUtils.width(containedReactor.getWidthSpec(), nestedBreadcrumbs); + if (candidate > result) { + result = candidate; } - String inputStructType = CGenerator.variableStructType(input, ASTUtils.toDefinition(definition.getReactorClass()), false); - String defName = definition.getName(); - String defWidth = generateWidthVariable(defName); - String inputName = input.getName(); - String inputWidth = generateWidthVariable(inputName); - if (!ASTUtils.isMultiport(input)) { - // Contained reactor's input is not a multiport. - structBuilder.pr(inputStructType+"* "+inputName+";"); - if (definition.getWidthSpec() != null) { - // Contained reactor is a bank. - builder.pr(String.join("\n", - "for (int bankIndex = 0; bankIndex < self->_lf_"+defWidth+"; bankIndex++) {", - " "+defName+"[bankIndex]."+inputName+" = &(self->_lf_"+defName+"[bankIndex]."+inputName+");", - "}" - )); - } else { - // Contained reactor is not a bank. - builder.pr(defName+"."+inputName+" = &(self->_lf_"+defName+"."+inputName+");"); - } - } else { - // Contained reactor's input is a multiport. - structBuilder.pr(String.join("\n", - inputStructType+"** "+inputName+";", - "int "+inputWidth+";" - )); - // If the contained reactor is a bank, then we have to set the - // pointer for each element of the bank. - if (definition.getWidthSpec() != null) { - builder.pr(String.join("\n", - "for (int _i = 0; _i < self->_lf_"+defWidth+"; _i++) {", - " "+defName+"[_i]."+inputName+" = self->_lf_"+defName+"[_i]."+inputName+";", - " "+defName+"[_i]."+inputWidth+" = self->_lf_"+defName+"[_i]."+inputWidth+";", - "}" - )); - } else { - builder.pr(String.join("\n", - defName+"."+inputName+" = self->_lf_"+defName+"."+inputName+";", - defName+"."+inputWidth+" = self->_lf_"+defName+"."+inputWidth+";" - )); - } + } else { + // Found some other instantiation, not the parent. + // Search within it for instantiations of the parent. + // Note that we assume here that the parent cannot contain + // instances of itself. + int candidate = + maxContainedReactorBankWidth(containedReactor, nestedBreadcrumbs, result, mainDef); + if (candidate > result) { + result = candidate; } + } + nestedBreadcrumbs.remove(); } + return result; + } - /** - * Generate into the specified string builder the code to - * initialize local variables for ports in a reaction function - * from the "self" struct. The port may be an input of the - * reactor or an output of a contained reactor. The second - * argument provides, for each contained reactor, a place to - * write the declaration of the output of that reactor that - * is triggering reactions. - * @param builder The place into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - */ - private static void generatePortVariablesInReaction( - CodeBuilder builder, - Map structs, - VarRef port, - Reactor r, - CTypes types - ) { - if (port.getVariable() instanceof Input) { - builder.pr(generateInputVariablesInReaction((Input) port.getVariable(), r, types)); - } else { - // port is an output of a contained reactor. - Output output = (Output) port.getVariable(); - String portStructType = CGenerator.variableStructType(output, ASTUtils.toDefinition(port.getContainer().getReactorClass()), false); + /** + * Generate code for the body of a reaction that takes an input and schedules an action with the + * value of that input. + * + * @param actionName The action to schedule + */ + public static String generateDelayBody(String ref, String actionName, boolean isTokenType) { + // Note that the action.type set by the base class is actually + // the port type. + return isTokenType + ? String.join( + "\n", + "if (" + ref + "->is_present) {", + " // Put the whole token on the event queue, not just the payload.", + " // This way, the length and element_size are transported.", + " lf_schedule_token(" + actionName + ", 0, " + ref + "->token);", + "}") + : "lf_schedule_copy(" + actionName + ", 0, &" + ref + "->value, 1); // Length is 1."; + } - CodeBuilder structBuilder = structs.get(port.getContainer()); - if (structBuilder == null) { - structBuilder = new CodeBuilder(); - structs.put(port.getContainer(), structBuilder); - } - String reactorName = port.getContainer().getName(); - String reactorWidth = generateWidthVariable(reactorName); - String outputName = output.getName(); - String outputWidth = generateWidthVariable(outputName); - // First define the struct containing the output value and indicator - // of its presence. - if (!ASTUtils.isMultiport(output)) { - // Output is not a multiport. - structBuilder.pr(portStructType+"* "+outputName+";"); - } else { - // Output is a multiport. - structBuilder.pr(String.join("\n", - portStructType+"** "+outputName+";", - "int "+outputWidth+";" - )); - } + public static String generateForwardBody( + String outputName, String targetType, String actionName, boolean isTokenType) { + return isTokenType + ? String.join( + "\n", + DISABLE_REACTION_INITIALIZATION_MARKER, + "self->_lf_" + + outputName + + ".value = (" + + targetType + + ")self->_lf__" + + actionName + + ".tmplt.token->value;", + "_lf_replace_template_token((token_template_t*)&self->_lf_" + + outputName + + ", (lf_token_t*)self->_lf__" + + actionName + + ".tmplt.token);", + "self->_lf_" + outputName + ".is_present = true;") + : "lf_set(" + outputName + ", " + actionName + "->value);"; + } - // Next, initialize the struct with the current values. - if (port.getContainer().getWidthSpec() != null) { - // Output is in a bank. - builder.pr(String.join("\n", - "for (int i = 0; i < "+reactorWidth+"; i++) {", - " "+reactorName+"[i]."+outputName+" = self->_lf_"+reactorName+"[i]."+outputName+";", - "}" - )); - if (ASTUtils.isMultiport(output)) { - builder.pr(String.join("\n", - "for (int i = 0; i < "+reactorWidth+"; i++) {", - " "+reactorName+"[i]."+outputWidth+" = self->_lf_"+reactorName+"[i]."+outputWidth+";", - "}" - )); - } - } else { - // Output is not in a bank. - builder.pr(reactorName+"."+outputName+" = self->_lf_"+reactorName+"."+outputName+";"); - if (ASTUtils.isMultiport(output)) { - builder.pr(reactorName+"."+outputWidth+" = self->_lf_"+reactorName+"."+outputWidth+";"); - } - } - } + /** + * Generate into the specified string builder the code to initialize local variables for sending + * data to an input of a contained reactor. This will also, if necessary, generate entries for + * local struct definitions into the struct argument. These entries point to where the data is + * stored. + * + * @param builder The string builder. + * @param structs A map from reactor instantiations to a place to write struct fields. + * @param definition AST node defining the reactor within which this occurs + * @param input Input of the contained reactor. + */ + private static void generateVariablesForSendingToContainedReactors( + CodeBuilder builder, + Map structs, + Instantiation definition, + Input input) { + CodeBuilder structBuilder = structs.get(definition); + if (structBuilder == null) { + structBuilder = new CodeBuilder(); + structs.put(definition, structBuilder); } - - /** Generate action variables for a reaction. - * @param action The action. - */ - private static String generateActionVariablesInReaction( - Action action, - Reactor r, - CTypes types - ) { - String structType = CGenerator.variableStructType(action, r, false); - // If the action has a type, create variables for accessing the value. - InferredType type = ASTUtils.getInferredType(action); - // Pointer to the lf_token_t sent as the payload in the trigger. - String tokenPointer = "(self->_lf__"+action.getName()+".tmplt.token)"; - CodeBuilder builder = new CodeBuilder(); - + String inputStructType = + CGenerator.variableStructType( + input, ASTUtils.toDefinition(definition.getReactorClass()), false); + String defName = definition.getName(); + String defWidth = generateWidthVariable(defName); + String inputName = input.getName(); + String inputWidth = generateWidthVariable(inputName); + if (!ASTUtils.isMultiport(input)) { + // Contained reactor's input is not a multiport. + structBuilder.pr(inputStructType + "* " + inputName + ";"); + if (definition.getWidthSpec() != null) { + // Contained reactor is a bank. builder.pr( - String.join("\n", - "// Expose the action struct as a local variable whose name matches the action name.", - structType+"* "+action.getName()+" = &self->_lf_"+action.getName()+";", - "// Set the fields of the action struct to match the current trigger.", - action.getName()+"->is_present = (bool)self->_lf__"+action.getName()+".status;", - action.getName()+"->has_value = ("+tokenPointer+" != NULL && "+tokenPointer+"->value != NULL);", - "_lf_replace_template_token((token_template_t*)"+action.getName()+", "+tokenPointer+");") - ); - // Set the value field only if there is a type. - if (!type.isUndefined()) { - // The value field will either be a copy (for primitive types) - // or a pointer (for types ending in *). - builder.pr("if ("+action.getName()+"->has_value) {"); - builder.indent(); - if (CUtil.isTokenType(type, types)) { - builder.pr(action.getName()+"->value = ("+types.getTargetType(type)+")"+tokenPointer+"->value;"); - } else { - builder.pr(action.getName()+"->value = *("+types.getTargetType(type)+"*)"+tokenPointer+"->value;"); - } - builder.unindent(); - builder.pr("}"); - } - return builder.toString(); + String.join( + "\n", + "for (int bankIndex = 0; bankIndex < self->_lf_" + defWidth + "; bankIndex++) {", + " " + + defName + + "[bankIndex]." + + inputName + + " = &(self->_lf_" + + defName + + "[bankIndex]." + + inputName + + ");", + "}")); + } else { + // Contained reactor is not a bank. + builder.pr( + defName + "." + inputName + " = &(self->_lf_" + defName + "." + inputName + ");"); + } + } else { + // Contained reactor's input is a multiport. + structBuilder.pr( + String.join("\n", inputStructType + "** " + inputName + ";", "int " + inputWidth + ";")); + // If the contained reactor is a bank, then we have to set the + // pointer for each element of the bank. + if (definition.getWidthSpec() != null) { + builder.pr( + String.join( + "\n", + "for (int _i = 0; _i < self->_lf_" + defWidth + "; _i++) {", + " " + + defName + + "[_i]." + + inputName + + " = self->_lf_" + + defName + + "[_i]." + + inputName + + ";", + " " + + defName + + "[_i]." + + inputWidth + + " = self->_lf_" + + defName + + "[_i]." + + inputWidth + + ";", + "}")); + } else { + builder.pr( + String.join( + "\n", + defName + "." + inputName + " = self->_lf_" + defName + "." + inputName + ";", + defName + "." + inputWidth + " = self->_lf_" + defName + "." + inputWidth + ";")); + } } + } - /** Generate into the specified string builder the code to - * initialize local variables for the specified input port - * in a reaction function from the "self" struct. - * @param input The input statement from the AST. - * @param r The reactor. - */ - private static String generateInputVariablesInReaction( - Input input, - Reactor r, - CTypes types - ) { - String structType = CGenerator.variableStructType(input, r, false); - InferredType inputType = ASTUtils.getInferredType(input); - CodeBuilder builder = new CodeBuilder(); - String inputName = input.getName(); - String inputWidth = generateWidthVariable(inputName); + /** + * Generate into the specified string builder the code to initialize local variables for ports in + * a reaction function from the "self" struct. The port may be an input of the reactor or an + * output of a contained reactor. The second argument provides, for each contained reactor, a + * place to write the declaration of the output of that reactor that is triggering reactions. + * + * @param builder The place into which to write the code. + * @param structs A map from reactor instantiations to a place to write struct fields. + */ + private static void generatePortVariablesInReaction( + CodeBuilder builder, + Map structs, + VarRef port, + Reactor r, + CTypes types) { + if (port.getVariable() instanceof Input) { + builder.pr(generateInputVariablesInReaction((Input) port.getVariable(), r, types)); + } else { + // port is an output of a contained reactor. + Output output = (Output) port.getVariable(); + String portStructType = + CGenerator.variableStructType( + output, ASTUtils.toDefinition(port.getContainer().getReactorClass()), false); - // Create the local variable whose name matches the input name. - // If the input has not been declared mutable, then this is a pointer - // to the upstream output. Otherwise, it is a copy of the upstream output, - // which nevertheless points to the same token and value (hence, as done - // below, we have to use lf_writable_copy()). There are 8 cases, - // depending on whether the input is mutable, whether it is a multiport, - // and whether it is a token type. - // Easy case first. - if (!input.isMutable() && !CUtil.isTokenType(inputType, types) && !ASTUtils.isMultiport(input)) { - // Non-mutable, non-multiport, primitive type. - builder.pr(structType+"* "+inputName+" = self->_lf_"+inputName+";"); - } else if (input.isMutable()&& !CUtil.isTokenType(inputType, types) && !ASTUtils.isMultiport(input)) { - // Mutable, non-multiport, primitive type. - builder.pr(String.join("\n", - "// Mutable input, so copy the input into a temporary variable.", - "// The input value on the struct is a copy.", - structType+" _lf_tmp_"+inputName+" = *(self->_lf_"+inputName+");", - structType+"* "+inputName+" = &_lf_tmp_"+inputName+";" - )); - } else if (!input.isMutable()&& CUtil.isTokenType(inputType, types) && !ASTUtils.isMultiport(input)) { - // Non-mutable, non-multiport, token type. - builder.pr(String.join("\n", - structType+"* "+inputName+" = self->_lf_"+inputName+";", - "if ("+inputName+"->is_present) {", - " "+inputName+"->length = "+inputName+"->token->length;", - " "+inputName+"->value = ("+types.getTargetType(inputType)+")"+inputName+"->token->value;", - "} else {", - " "+inputName+"->length = 0;", - "}" - )); - } else if (input.isMutable()&& CUtil.isTokenType(inputType, types) && !ASTUtils.isMultiport(input)) { - // Mutable, non-multiport, token type. - builder.pr(String.join("\n", - "// Mutable input, so copy the input struct into a temporary variable.", - structType+" _lf_tmp_"+inputName+" = *(self->_lf_"+inputName+");", - structType+"* "+inputName+" = &_lf_tmp_"+inputName+";", - inputName+"->value = NULL;", // Prevent payload from being freed. - "if ("+inputName+"->is_present) {", - " "+inputName+"->length = "+inputName+"->token->length;", - " "+inputName+"->token = lf_writable_copy((lf_port_base_t*)self->_lf_"+inputName+");", - " "+inputName+"->value = ("+types.getTargetType(inputType)+")"+inputName+"->token->value;", - "} else {", - " "+inputName+"->length = 0;", - "}" - )); - } else if (!input.isMutable()&& ASTUtils.isMultiport(input)) { - // Non-mutable, multiport, primitive or token type. - builder.pr(structType+"** "+inputName+" = self->_lf_"+inputName+";"); - } else if (CUtil.isTokenType(inputType, types)) { - // Mutable, multiport, token type - builder.pr(String.join("\n", - "// Mutable multiport input, so copy the input structs", - "// into an array of temporary variables on the stack.", - structType+" _lf_tmp_"+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - structType+"* "+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - "for (int i = 0; i < "+CUtil.multiportWidthExpression(input)+"; i++) {", - " "+inputName+"[i] = &_lf_tmp_"+inputName+"[i];", - " _lf_tmp_"+inputName+"[i] = *(self->_lf_"+inputName+"[i]);", - " // If necessary, copy the tokens.", - " if ("+inputName+"[i]->is_present) {", - " "+inputName+"[i]->length = "+inputName+"[i]->token->length;", - " token_template_t* _lf_input = (token_template_t*)self->_lf_"+inputName+"[i];", - " "+inputName+"[i]->token = lf_writable_copy((lf_port_base_t*)_lf_input);", - " "+inputName+"[i]->value = ("+types.getTargetType(inputType)+")"+inputName+"[i]->token->value;", - " } else {", - " "+inputName+"[i]->length = 0;", - " }", - "}" - )); - } else { - // Mutable, multiport, primitive type - builder.pr(String.join("\n", - "// Mutable multiport input, so copy the input structs", - "// into an array of temporary variables on the stack.", - structType+" _lf_tmp_"+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - structType+"* "+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - "for (int i = 0; i < "+CUtil.multiportWidthExpression(input)+"; i++) {", - " "+inputName+"[i] = &_lf_tmp_"+inputName+"[i];", - " // Copy the struct, which includes the value.", - " _lf_tmp_"+inputName+"[i] = *(self->_lf_"+inputName+"[i]);", - "}" - )); + CodeBuilder structBuilder = structs.get(port.getContainer()); + if (structBuilder == null) { + structBuilder = new CodeBuilder(); + structs.put(port.getContainer(), structBuilder); + } + String reactorName = port.getContainer().getName(); + String reactorWidth = generateWidthVariable(reactorName); + String outputName = output.getName(); + String outputWidth = generateWidthVariable(outputName); + // First define the struct containing the output value and indicator + // of its presence. + if (!ASTUtils.isMultiport(output)) { + // Output is not a multiport. + structBuilder.pr(portStructType + "* " + outputName + ";"); + } else { + // Output is a multiport. + structBuilder.pr( + String.join( + "\n", portStructType + "** " + outputName + ";", "int " + outputWidth + ";")); + } + + // Next, initialize the struct with the current values. + if (port.getContainer().getWidthSpec() != null) { + // Output is in a bank. + builder.pr( + String.join( + "\n", + "for (int i = 0; i < " + reactorWidth + "; i++) {", + " " + + reactorName + + "[i]." + + outputName + + " = self->_lf_" + + reactorName + + "[i]." + + outputName + + ";", + "}")); + if (ASTUtils.isMultiport(output)) { + builder.pr( + String.join( + "\n", + "for (int i = 0; i < " + reactorWidth + "; i++) {", + " " + + reactorName + + "[i]." + + outputWidth + + " = self->_lf_" + + reactorName + + "[i]." + + outputWidth + + ";", + "}")); + } + } else { + // Output is not in a bank. + builder.pr( + reactorName + + "." + + outputName + + " = self->_lf_" + + reactorName + + "." + + outputName + + ";"); + if (ASTUtils.isMultiport(output)) { + builder.pr( + reactorName + + "." + + outputWidth + + " = self->_lf_" + + reactorName + + "." + + outputWidth + + ";"); } - // Set the _width variable for all cases. This will be -1 - // for a variable-width multiport, which is not currently supported. - // It will be -2 if it is not multiport. - builder.pr("int "+inputWidth+" = self->_lf_"+inputWidth+"; SUPPRESS_UNUSED_WARNING("+inputWidth+");"); - return builder.toString(); + } } + } - /** - * Generate into the specified string builder the code to - * initialize local variables for outputs in a reaction function - * from the "self" struct. - * @param effect The effect declared by the reaction. This must refer to an output. - * @param r The reactor containing the reaction. - */ - public static String generateOutputVariablesInReaction( - VarRef effect, - Reactor r, - ErrorReporter errorReporter, - boolean requiresTypes - ) { - Output output = (Output) effect.getVariable(); - String outputName = output.getName(); - String outputWidth = generateWidthVariable(outputName); - if (output.getType() == null && requiresTypes) { - errorReporter.reportError(output, "Output is required to have a type: " + outputName); - return ""; - } else { - // The container of the output may be a contained reactor or - // the reactor containing the reaction. - String outputStructType = (effect.getContainer() == null) ? - CGenerator.variableStructType(output, r, false) - : - CGenerator.variableStructType(output, ASTUtils.toDefinition(effect.getContainer().getReactorClass()), false); - if (!ASTUtils.isMultiport(output)) { - // Output port is not a multiport. - return outputStructType+"* "+outputName+" = &self->_lf_"+outputName+";"; - } else { - // Output port is a multiport. - // Set the _width variable. - return String.join("\n", - "int "+outputWidth+" = self->_lf_"+outputWidth+"; SUPPRESS_UNUSED_WARNING("+outputWidth+");", - outputStructType+"** "+outputName+" = self->_lf_"+outputName+"_pointers;" - ); + /** + * Generate action variables for a reaction. + * + * @param action The action. + */ + private static String generateActionVariablesInReaction(Action action, Reactor r, CTypes types) { + String structType = CGenerator.variableStructType(action, r, false); + // If the action has a type, create variables for accessing the value. + InferredType type = ASTUtils.getInferredType(action); + // Pointer to the lf_token_t sent as the payload in the trigger. + String tokenPointer = "(self->_lf__" + action.getName() + ".tmplt.token)"; + CodeBuilder builder = new CodeBuilder(); - } - } + builder.pr( + String.join( + "\n", + "// Expose the action struct as a local variable whose name matches the action name.", + structType + "* " + action.getName() + " = &self->_lf_" + action.getName() + ";", + "// Set the fields of the action struct to match the current trigger.", + action.getName() + "->is_present = (bool)self->_lf__" + action.getName() + ".status;", + action.getName() + + "->has_value = (" + + tokenPointer + + " != NULL && " + + tokenPointer + + "->value != NULL);", + "_lf_replace_template_token((token_template_t*)" + + action.getName() + + ", " + + tokenPointer + + ");")); + // Set the value field only if there is a type. + if (!type.isUndefined()) { + // The value field will either be a copy (for primitive types) + // or a pointer (for types ending in *). + builder.pr("if (" + action.getName() + "->has_value) {"); + builder.indent(); + if (CUtil.isTokenType(type, types)) { + builder.pr( + action.getName() + + "->value = (" + + types.getTargetType(type) + + ")" + + tokenPointer + + "->value;"); + } else { + builder.pr( + action.getName() + + "->value = *(" + + types.getTargetType(type) + + "*)" + + tokenPointer + + "->value;"); + } + builder.unindent(); + builder.pr("}"); } + return builder.toString(); + } - /** - * Generate the fields of the self struct and statements for the constructor - * to create and initialize a reaction_t struct for each reaction in the - * specified reactor and a trigger_t struct for each trigger (input, action, - * timer, or output of a contained reactor). - * @param body The place to put the code for the self struct. - * @param reactor The reactor. - * @param constructorCode The place to put the constructor code. - */ - public static void generateReactionAndTriggerStructs( - CodeBuilder body, - Reactor reactor, - CodeBuilder constructorCode, - CTypes types - ) { - var reactionCount = 0; - // Iterate over reactions and create initialize the reaction_t struct - // on the self struct. Also, collect a map from triggers to the reactions - // that are triggered by that trigger. Also, collect a set of sources - // that are read by reactions but do not trigger reactions. - // Finally, collect a set of triggers and sources that are outputs - // of contained reactors. - var triggerMap = new LinkedHashMap>(); - var sourceSet = new LinkedHashSet(); - var outputsOfContainedReactors = new LinkedHashMap(); - var startupReactions = new LinkedHashSet(); - var shutdownReactions = new LinkedHashSet(); - var resetReactions = new LinkedHashSet(); - for (Reaction reaction : ASTUtils.allReactions(reactor)) { - // Create the reaction_t struct. - body.pr(reaction, "reaction_t _lf__reaction_"+reactionCount+";"); + /** + * Generate into the specified string builder the code to initialize local variables for the + * specified input port in a reaction function from the "self" struct. + * + * @param input The input statement from the AST. + * @param r The reactor. + */ + private static String generateInputVariablesInReaction(Input input, Reactor r, CTypes types) { + String structType = CGenerator.variableStructType(input, r, false); + InferredType inputType = ASTUtils.getInferredType(input); + CodeBuilder builder = new CodeBuilder(); + String inputName = input.getName(); + String inputWidth = generateWidthVariable(inputName); - // Create the map of triggers to reactions. - for (TriggerRef trigger : reaction.getTriggers()) { - // trigger may not be a VarRef (it could be "startup" or "shutdown"). - if (trigger instanceof VarRef) { - var triggerAsVarRef = (VarRef) trigger; - var reactionList = triggerMap.get(triggerAsVarRef.getVariable()); - if (reactionList == null) { - reactionList = new LinkedList<>(); - triggerMap.put(triggerAsVarRef.getVariable(), reactionList); - } - reactionList.add(reactionCount); - if (triggerAsVarRef.getContainer() != null) { - outputsOfContainedReactors.put(triggerAsVarRef.getVariable(), triggerAsVarRef.getContainer()); - } - } else if (trigger instanceof BuiltinTriggerRef) { - switch(((BuiltinTriggerRef) trigger).getType()) { - case STARTUP: - startupReactions.add(reactionCount); - break; - case SHUTDOWN: - shutdownReactions.add(reactionCount); - break; - case RESET: - resetReactions.add(reactionCount); - break; - } - } - } - // Create the set of sources read but not triggering. - for (VarRef source : reaction.getSources()) { - sourceSet.add(source.getVariable()); - if (source.getContainer() != null) { - outputsOfContainedReactors.put(source.getVariable(), source.getContainer()); - } - } + // Create the local variable whose name matches the input name. + // If the input has not been declared mutable, then this is a pointer + // to the upstream output. Otherwise, it is a copy of the upstream output, + // which nevertheless points to the same token and value (hence, as done + // below, we have to use lf_writable_copy()). There are 8 cases, + // depending on whether the input is mutable, whether it is a multiport, + // and whether it is a token type. + // Easy case first. + if (!input.isMutable() + && !CUtil.isTokenType(inputType, types) + && !ASTUtils.isMultiport(input)) { + // Non-mutable, non-multiport, primitive type. + builder.pr(structType + "* " + inputName + " = self->_lf_" + inputName + ";"); + } else if (input.isMutable() + && !CUtil.isTokenType(inputType, types) + && !ASTUtils.isMultiport(input)) { + // Mutable, non-multiport, primitive type. + builder.pr( + String.join( + "\n", + "// Mutable input, so copy the input into a temporary variable.", + "// The input value on the struct is a copy.", + structType + " _lf_tmp_" + inputName + " = *(self->_lf_" + inputName + ");", + structType + "* " + inputName + " = &_lf_tmp_" + inputName + ";")); + } else if (!input.isMutable() + && CUtil.isTokenType(inputType, types) + && !ASTUtils.isMultiport(input)) { + // Non-mutable, non-multiport, token type. + builder.pr( + String.join( + "\n", + structType + "* " + inputName + " = self->_lf_" + inputName + ";", + "if (" + inputName + "->is_present) {", + " " + inputName + "->length = " + inputName + "->token->length;", + " " + + inputName + + "->value = (" + + types.getTargetType(inputType) + + ")" + + inputName + + "->token->value;", + "} else {", + " " + inputName + "->length = 0;", + "}")); + } else if (input.isMutable() + && CUtil.isTokenType(inputType, types) + && !ASTUtils.isMultiport(input)) { + // Mutable, non-multiport, token type. + builder.pr( + String.join( + "\n", + "// Mutable input, so copy the input struct into a temporary variable.", + structType + " _lf_tmp_" + inputName + " = *(self->_lf_" + inputName + ");", + structType + "* " + inputName + " = &_lf_tmp_" + inputName + ";", + inputName + "->value = NULL;", // Prevent payload from being freed. + "if (" + inputName + "->is_present) {", + " " + inputName + "->length = " + inputName + "->token->length;", + " " + + inputName + + "->token = lf_writable_copy((lf_port_base_t*)self->_lf_" + + inputName + + ");", + " " + + inputName + + "->value = (" + + types.getTargetType(inputType) + + ")" + + inputName + + "->token->value;", + "} else {", + " " + inputName + "->length = 0;", + "}")); + } else if (!input.isMutable() && ASTUtils.isMultiport(input)) { + // Non-mutable, multiport, primitive or token type. + builder.pr(structType + "** " + inputName + " = self->_lf_" + inputName + ";"); + } else if (CUtil.isTokenType(inputType, types)) { + // Mutable, multiport, token type + builder.pr( + String.join( + "\n", + "// Mutable multiport input, so copy the input structs", + "// into an array of temporary variables on the stack.", + structType + + " _lf_tmp_" + + inputName + + "[" + + CUtil.multiportWidthExpression(input) + + "];", + structType + "* " + inputName + "[" + CUtil.multiportWidthExpression(input) + "];", + "for (int i = 0; i < " + CUtil.multiportWidthExpression(input) + "; i++) {", + " " + inputName + "[i] = &_lf_tmp_" + inputName + "[i];", + " _lf_tmp_" + inputName + "[i] = *(self->_lf_" + inputName + "[i]);", + " // If necessary, copy the tokens.", + " if (" + inputName + "[i]->is_present) {", + " " + inputName + "[i]->length = " + inputName + "[i]->token->length;", + " token_template_t* _lf_input = (token_template_t*)self->_lf_" + + inputName + + "[i];", + " " + inputName + "[i]->token = lf_writable_copy((lf_port_base_t*)_lf_input);", + " " + + inputName + + "[i]->value = (" + + types.getTargetType(inputType) + + ")" + + inputName + + "[i]->token->value;", + " } else {", + " " + inputName + "[i]->length = 0;", + " }", + "}")); + } else { + // Mutable, multiport, primitive type + builder.pr( + String.join( + "\n", + "// Mutable multiport input, so copy the input structs", + "// into an array of temporary variables on the stack.", + structType + + " _lf_tmp_" + + inputName + + "[" + + CUtil.multiportWidthExpression(input) + + "];", + structType + "* " + inputName + "[" + CUtil.multiportWidthExpression(input) + "];", + "for (int i = 0; i < " + CUtil.multiportWidthExpression(input) + "; i++) {", + " " + inputName + "[i] = &_lf_tmp_" + inputName + "[i];", + " // Copy the struct, which includes the value.", + " _lf_tmp_" + inputName + "[i] = *(self->_lf_" + inputName + "[i]);", + "}")); + } + // Set the _width variable for all cases. This will be -1 + // for a variable-width multiport, which is not currently supported. + // It will be -2 if it is not multiport. + builder.pr( + "int " + + inputWidth + + " = self->_lf_" + + inputWidth + + "; SUPPRESS_UNUSED_WARNING(" + + inputWidth + + ");"); + return builder.toString(); + } - var deadlineFunctionPointer = "NULL"; - if (reaction.getDeadline() != null) { - // The following has to match the name chosen in generateReactions - var deadlineFunctionName = generateDeadlineFunctionName(reactor, reactionCount); - deadlineFunctionPointer = "&" + deadlineFunctionName; - } + /** + * Generate into the specified string builder the code to initialize local variables for outputs + * in a reaction function from the "self" struct. + * + * @param effect The effect declared by the reaction. This must refer to an output. + * @param r The reactor containing the reaction. + */ + public static String generateOutputVariablesInReaction( + VarRef effect, Reactor r, ErrorReporter errorReporter, boolean requiresTypes) { + Output output = (Output) effect.getVariable(); + String outputName = output.getName(); + String outputWidth = generateWidthVariable(outputName); + if (output.getType() == null && requiresTypes) { + errorReporter.reportError(output, "Output is required to have a type: " + outputName); + return ""; + } else { + // The container of the output may be a contained reactor or + // the reactor containing the reaction. + String outputStructType = + (effect.getContainer() == null) + ? CGenerator.variableStructType(output, r, false) + : CGenerator.variableStructType( + output, ASTUtils.toDefinition(effect.getContainer().getReactorClass()), false); + if (!ASTUtils.isMultiport(output)) { + // Output port is not a multiport. + return outputStructType + "* " + outputName + " = &self->_lf_" + outputName + ";"; + } else { + // Output port is a multiport. + // Set the _width variable. + return String.join( + "\n", + "int " + + outputWidth + + " = self->_lf_" + + outputWidth + + "; SUPPRESS_UNUSED_WARNING(" + + outputWidth + + ");", + outputStructType + "** " + outputName + " = self->_lf_" + outputName + "_pointers;"); + } + } + } - // Assign the STP handler - var STPFunctionPointer = "NULL"; - if (reaction.getStp() != null) { - // The following has to match the name chosen in generateReactions - var STPFunctionName = generateStpFunctionName(reactor, reactionCount); - STPFunctionPointer = "&" + STPFunctionName; - } + /** + * Generate into the specified string builder the code to initialize local variables for watchdogs + * in a reaction function from the "self" struct. + * + * @param effect The effect declared by the reaction. This must refer to a watchdog. + * @param decl The reactor containing the reaction or the import statement. + */ + // Fine to have watchdog be in reactor self struct? + public static String generateWatchdogVariablesInReaction(VarRef effect, ReactorDecl decl) { + Watchdog watchdog = (Watchdog) effect.getVariable(); + String watchdogName = watchdog.getName(); - // Set the defaults of the reaction_t struct in the constructor. - // Since the self struct is allocated using calloc, there is no need to set: - // self->_lf__reaction_"+reactionCount+".index = 0; - // self->_lf__reaction_"+reactionCount+".chain_id = 0; - // self->_lf__reaction_"+reactionCount+".pos = 0; - // self->_lf__reaction_"+reactionCount+".status = inactive; - // self->_lf__reaction_"+reactionCount+".deadline = 0LL; - // self->_lf__reaction_"+reactionCount+".is_STP_violated = false; - constructorCode.pr(reaction, String.join("\n", - "self->_lf__reaction_"+reactionCount+".number = "+reactionCount+";", - "self->_lf__reaction_"+reactionCount+".function = "+CReactionGenerator.generateReactionFunctionName(reactor, reactionCount)+";", - "self->_lf__reaction_"+reactionCount+".self = self;", - "self->_lf__reaction_"+reactionCount+".deadline_violation_handler = "+deadlineFunctionPointer+";", - "self->_lf__reaction_"+reactionCount+".STP_handler = "+STPFunctionPointer+";", - "self->_lf__reaction_"+reactionCount+".name = "+addDoubleQuotes("?")+";", - (reaction.eContainer() instanceof Mode ? - "self->_lf__reaction_"+reactionCount+".mode = &self->_lf__modes["+reactor.getModes().indexOf((Mode) reaction.eContainer())+"];" : - "self->_lf__reaction_"+reactionCount+".mode = NULL;") - )); - // Increment the reactionCount even if the reaction is not in the federate - // so that reaction indices are consistent across federates. - reactionCount++; - } + return String.join( + "\n", + List.of("watchdog_t* " + watchdogName + " = &(self->_lf_watchdog_" + watchdogName + ");")); + } - // Next, create and initialize the trigger_t objects. - // Start with the timers. - for (Timer timer : ASTUtils.allTimers(reactor)) { - createTriggerT(body, timer, triggerMap, constructorCode, types); - // Since the self struct is allocated using calloc, there is no need to set falsy fields. - constructorCode.pr("self->_lf__"+timer.getName()+".is_timer = true;"); - constructorCode.pr(CExtensionUtils.surroundWithIfFederatedDecentralized( - "self->_lf__"+timer.getName()+".intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); - } + /** + * Generate the fields of the self struct and statements for the constructor to create and + * initialize a reaction_t struct for each reaction in the specified reactor and a trigger_t + * struct for each trigger (input, action, timer, or output of a contained reactor). + * + * @param body The place to put the code for the self struct. + * @param reactor The reactor. + * @param constructorCode The place to put the constructor code. + */ + public static void generateReactionAndTriggerStructs( + CodeBuilder body, Reactor reactor, CodeBuilder constructorCode, CTypes types) { + var reactionCount = 0; + // Iterate over reactions and create initialize the reaction_t struct + // on the self struct. Also, collect a map from triggers to the reactions + // that are triggered by that trigger. Also, collect a set of sources + // that are read by reactions but do not trigger reactions. + // Finally, collect a set of triggers and sources that are outputs + // of contained reactors. + var triggerMap = new LinkedHashMap>(); + var sourceSet = new LinkedHashSet(); + var outputsOfContainedReactors = new LinkedHashMap(); + var startupReactions = new LinkedHashSet(); + var shutdownReactions = new LinkedHashSet(); + var resetReactions = new LinkedHashSet(); + for (Reaction reaction : ASTUtils.allReactions(reactor)) { + // Create the reaction_t struct. + body.pr(reaction, "reaction_t _lf__reaction_" + reactionCount + ";"); - // Handle builtin triggers. - if (startupReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(startupReactions, "startup", body, constructorCode); - } - // Handle shutdown triggers. - if (shutdownReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(shutdownReactions, "shutdown", body, constructorCode); + // Create the map of triggers to reactions. + for (TriggerRef trigger : reaction.getTriggers()) { + // trigger may not be a VarRef (it could be "startup" or "shutdown"). + if (trigger instanceof VarRef) { + var triggerAsVarRef = (VarRef) trigger; + var reactionList = triggerMap.get(triggerAsVarRef.getVariable()); + if (reactionList == null) { + reactionList = new LinkedList<>(); + triggerMap.put(triggerAsVarRef.getVariable(), reactionList); + } + reactionList.add(reactionCount); + if (triggerAsVarRef.getContainer() != null) { + outputsOfContainedReactors.put( + triggerAsVarRef.getVariable(), triggerAsVarRef.getContainer()); + } + } else if (trigger instanceof BuiltinTriggerRef) { + switch (((BuiltinTriggerRef) trigger).getType()) { + case STARTUP: + startupReactions.add(reactionCount); + break; + case SHUTDOWN: + shutdownReactions.add(reactionCount); + break; + case RESET: + resetReactions.add(reactionCount); + break; + } } - if (resetReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(resetReactions, "reset", body, constructorCode); + } + // Create the set of sources read but not triggering. + for (VarRef source : reaction.getSources()) { + sourceSet.add(source.getVariable()); + if (source.getContainer() != null) { + outputsOfContainedReactors.put(source.getVariable(), source.getContainer()); } + } - // Next handle actions. - for (Action action : ASTUtils.allActions(reactor)) { - createTriggerT(body, action, triggerMap, constructorCode, types); - var isPhysical = "true"; - if (action.getOrigin().equals(ActionOrigin.LOGICAL)) { - isPhysical = "false"; - } - var elementSize = "0"; - // If the action type is 'void', we need to avoid generating the code - // 'sizeof(void)', which some compilers reject. - var rootType = action.getType() != null ? CUtil.rootType(types.getTargetType(action)) - : null; - if (rootType != null && !rootType.equals("void")) { - elementSize = "sizeof(" + rootType + ")"; - } + var deadlineFunctionPointer = "NULL"; + if (reaction.getDeadline() != null) { + // The following has to match the name chosen in generateReactions + var deadlineFunctionName = generateDeadlineFunctionName(reactor, reactionCount); + deadlineFunctionPointer = "&" + deadlineFunctionName; + } - // Since the self struct is allocated using calloc, there is no need to set: - // self->_lf__"+action.getName()+".is_timer = false; - constructorCode.pr(String.join("\n", - "self->_lf__" + action.getName() + ".is_physical = " + isPhysical + ";", - (!(action.getPolicy() == null || action.getPolicy().isEmpty()) ? - "self->_lf__" + action.getName() + ".policy = " + action.getPolicy() + ";" : - ""), - // Need to set the element_size in the trigger_t and the action struct. - "self->_lf__" + action.getName() + ".tmplt.type.element_size = " + elementSize - + ";", - "self->_lf_" + action.getName() + ".type.element_size = " + elementSize + ";" - )); - } + // Assign the STP handler + var STPFunctionPointer = "NULL"; + if (reaction.getStp() != null) { + // The following has to match the name chosen in generateReactions + var STPFunctionName = generateStpFunctionName(reactor, reactionCount); + STPFunctionPointer = "&" + STPFunctionName; + } - // Next handle inputs. - for (Input input : ASTUtils.allInputs(reactor)) { - createTriggerT(body, input, triggerMap, constructorCode, types); - } + // Set the defaults of the reaction_t struct in the constructor. + // Since the self struct is allocated using calloc, there is no need to set: + // self->_lf__reaction_"+reactionCount+".index = 0; + // self->_lf__reaction_"+reactionCount+".chain_id = 0; + // self->_lf__reaction_"+reactionCount+".pos = 0; + // self->_lf__reaction_"+reactionCount+".status = inactive; + // self->_lf__reaction_"+reactionCount+".deadline = 0LL; + // self->_lf__reaction_"+reactionCount+".is_STP_violated = false; + constructorCode.pr( + reaction, + String.join( + "\n", + "self->_lf__reaction_" + reactionCount + ".number = " + reactionCount + ";", + "self->_lf__reaction_" + + reactionCount + + ".function = " + + CReactionGenerator.generateReactionFunctionName(reactor, reactionCount) + + ";", + "self->_lf__reaction_" + reactionCount + ".self = self;", + "self->_lf__reaction_" + + reactionCount + + ".deadline_violation_handler = " + + deadlineFunctionPointer + + ";", + "self->_lf__reaction_" + reactionCount + ".STP_handler = " + STPFunctionPointer + ";", + "self->_lf__reaction_" + reactionCount + ".name = " + addDoubleQuotes("?") + ";", + (reaction.eContainer() instanceof Mode + ? "self->_lf__reaction_" + + reactionCount + + ".mode = &self->_lf__modes[" + + reactor.getModes().indexOf((Mode) reaction.eContainer()) + + "];" + : "self->_lf__reaction_" + reactionCount + ".mode = NULL;"))); + // Increment the reactionCount even if the reaction is not in the federate + // so that reaction indices are consistent across federates. + reactionCount++; } - /** - * Define the trigger_t object on the self struct, an array of - * reaction_t pointers pointing to reactions triggered by this variable, - * and initialize the pointers in the array in the constructor. - * @param body The place to write the self struct entries. - * @param variable The trigger variable (Timer, Action, or Input). - * @param triggerMap A map from Variables to a list of the reaction indices - * triggered by the variable. - * @param constructorCode The place to write the constructor code. - */ - private static void createTriggerT( - CodeBuilder body, - Variable variable, - LinkedHashMap> triggerMap, - CodeBuilder constructorCode, - CTypes types - ) { - var varName = variable.getName(); - // variable is a port, a timer, or an action. - body.pr(variable, "trigger_t _lf__"+varName+";"); - constructorCode.pr(variable, "self->_lf__"+varName+".last = NULL;"); - constructorCode.pr(variable, CExtensionUtils.surroundWithIfFederatedDecentralized( - "self->_lf__"+varName+".intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); - - // Generate the reactions triggered table. - var reactionsTriggered = triggerMap.get(variable); - if (reactionsTriggered != null) { - body.pr(variable, "reaction_t* _lf__"+varName+"_reactions["+reactionsTriggered.size()+"];"); - var count = 0; - for (Integer reactionTriggered : reactionsTriggered) { - constructorCode.prSourceLineNumber(variable); - constructorCode.pr(variable, "self->_lf__"+varName+"_reactions["+count+"] = &self->_lf__reaction_"+reactionTriggered+";"); - count++; - } - // Set up the trigger_t struct's pointer to the reactions. - constructorCode.pr(variable, String.join("\n", - "self->_lf__"+varName+".reactions = &self->_lf__"+varName+"_reactions[0];", - "self->_lf__"+varName+".number_of_reactions = "+count+";" - )); - - // If federated, set the physical_time_of_arrival - constructorCode.pr(variable, CExtensionUtils.surroundWithIfFederated( - "self->_lf__"+varName+".physical_time_of_arrival = NEVER;")); - } - if (variable instanceof Input) { - var rootType = CUtil.rootType(types.getTargetType((Input) variable)); - // Since the self struct is allocated using calloc, there is no need to set falsy fields. - // If the input type is 'void', we need to avoid generating the code - // 'sizeof(void)', which some compilers reject. - var size = (rootType.equals("void")) ? "0" : "sizeof("+rootType+")"; - - constructorCode.pr("self->_lf__"+varName+".tmplt.type.element_size = "+size+";"); - body.pr( - CExtensionUtils.surroundWithIfFederated( - CExtensionUtils.createPortStatusFieldForInput((Input) variable) - ) - ); - } + // Next, create and initialize the trigger_t objects. + // Start with the timers. + for (Timer timer : ASTUtils.allTimers(reactor)) { + createTriggerT(body, timer, triggerMap, constructorCode, types); + // Since the self struct is allocated using calloc, there is no need to set falsy fields. + constructorCode.pr("self->_lf__" + timer.getName() + ".is_timer = true;"); + constructorCode.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + "self->_lf__" + + timer.getName() + + ".intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); } - public static void generateBuiltinTriggeredReactionsArray( - Set reactions, - String name, - CodeBuilder body, - CodeBuilder constructorCode - ) { - body.pr(String.join("\n", - "trigger_t _lf__"+name+";", - "reaction_t* _lf__"+name+"_reactions["+reactions.size()+"];" - )); - constructorCode.pr(CExtensionUtils.surroundWithIfFederatedDecentralized( - "self->_lf__"+name+".intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); - var i = 0; - for (Integer reactionIndex : reactions) { - constructorCode.pr("self->_lf__"+name+"_reactions["+i+++"] = &self->_lf__reaction_"+reactionIndex+";"); - } - constructorCode.pr(String.join("\n", - "self->_lf__"+name+".last = NULL;", - "self->_lf__"+name+".reactions = &self->_lf__"+name+"_reactions[0];", - "self->_lf__"+name+".number_of_reactions = "+reactions.size()+";", - "self->_lf__"+name+".is_timer = false;" - )); + // Handle builtin triggers. + if (startupReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(startupReactions, "startup", body, constructorCode); } - - public static String generateBuiltinTriggersTable(int reactionCount, String name) { - return String.join("\n", List.of( - "// Array of pointers to "+name+" triggers.", - (reactionCount > 0 ? - "reaction_t* _lf_"+name+"_reactions["+reactionCount+"]" : - "reaction_t** _lf_"+name+"_reactions = NULL") + ";", - "int _lf_"+name+"_reactions_size = "+reactionCount+";" - )); + // Handle shutdown triggers. + if (shutdownReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(shutdownReactions, "shutdown", body, constructorCode); } - - /** - * Generate the _lf_trigger_startup_reactions function. - */ - public static String generateLfTriggerStartupReactions(int startupReactionCount, boolean hasModalReactors) { - var s = new StringBuilder(); - s.append("void _lf_trigger_startup_reactions() {"); - if (startupReactionCount > 0) { - s.append("\n"); - if (hasModalReactors) { - s.append(String.join("\n", - " for (int i = 0; i < _lf_startup_reactions_size; i++) {", - " if (_lf_startup_reactions[i] != NULL) {", - " if (_lf_startup_reactions[i]->mode != NULL) {", - " // Skip reactions in modes", - " continue;", - " }", - " _lf_trigger_reaction(_lf_startup_reactions[i], -1);", - " }", - " }", - " _lf_handle_mode_startup_reset_reactions(", - " _lf_startup_reactions, _lf_startup_reactions_size,", - " NULL, 0,", - " _lf_modal_reactor_states, _lf_modal_reactor_states_size);" - )); - } else { - s.append(String.join("\n", - " for (int i = 0; i < _lf_startup_reactions_size; i++) {", - " if (_lf_startup_reactions[i] != NULL) {", - " _lf_trigger_reaction(_lf_startup_reactions[i], -1);", - " }", - " }" - )); - } - s.append("\n"); - } - s.append("}\n"); - return s.toString(); + if (resetReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(resetReactions, "reset", body, constructorCode); } - /** - * Generate the _lf_trigger_shutdown_reactions function. - */ - public static String generateLfTriggerShutdownReactions(int shutdownReactionCount, boolean hasModalReactors) { - var s = new StringBuilder(); - s.append("bool _lf_trigger_shutdown_reactions() {\n"); - if (shutdownReactionCount > 0) { - if (hasModalReactors) { - s.append(String.join("\n", - " for (int i = 0; i < _lf_shutdown_reactions_size; i++) {", - " if (_lf_shutdown_reactions[i] != NULL) {", - " if (_lf_shutdown_reactions[i]->mode != NULL) {", - " // Skip reactions in modes", - " continue;", - " }", - " _lf_trigger_reaction(_lf_shutdown_reactions[i], -1);", - " }", - " }", - " _lf_handle_mode_shutdown_reactions(_lf_shutdown_reactions, _lf_shutdown_reactions_size);", - " return true;" - )); - } else { - s.append(String.join("\n", - " for (int i = 0; i < _lf_shutdown_reactions_size; i++) {", - " if (_lf_shutdown_reactions[i] != NULL) {", - " _lf_trigger_reaction(_lf_shutdown_reactions[i], -1);", - " }", - " }", - " return true;" - )); - } - s.append("\n"); - } else { - s.append(" return false;\n"); - } - s.append("}\n"); - return s.toString(); + // Next handle actions. + for (Action action : ASTUtils.allActions(reactor)) { + createTriggerT(body, action, triggerMap, constructorCode, types); + var isPhysical = "true"; + if (action.getOrigin().equals(ActionOrigin.LOGICAL)) { + isPhysical = "false"; + } + var elementSize = "0"; + // If the action type is 'void', we need to avoid generating the code + // 'sizeof(void)', which some compilers reject. + var rootType = action.getType() != null ? CUtil.rootType(types.getTargetType(action)) : null; + if (rootType != null && !rootType.equals("void")) { + elementSize = "sizeof(" + rootType + ")"; + } + + // Since the self struct is allocated using calloc, there is no need to set: + // self->_lf__"+action.getName()+".is_timer = false; + constructorCode.pr( + String.join( + "\n", + "self->_lf__" + action.getName() + ".is_physical = " + isPhysical + ";", + (!(action.getPolicy() == null || action.getPolicy().isEmpty()) + ? "self->_lf__" + action.getName() + ".policy = " + action.getPolicy() + ";" + : ""), + // Need to set the element_size in the trigger_t and the action struct. + "self->_lf__" + action.getName() + ".tmplt.type.element_size = " + elementSize + ";", + "self->_lf_" + action.getName() + ".type.element_size = " + elementSize + ";")); } - /** - * Generate the _lf_handle_mode_triggered_reactions function. - */ - public static String generateLfModeTriggeredReactions( - int startupReactionCount, - int resetReactionCount, - boolean hasModalReactors - ) { - if (!hasModalReactors) { - return ""; - } - var s = new StringBuilder(); - s.append("void _lf_handle_mode_triggered_reactions() {\n"); - s.append(" _lf_handle_mode_startup_reset_reactions(\n"); - if (startupReactionCount > 0) { - s.append(" _lf_startup_reactions, _lf_startup_reactions_size,\n"); - } else { - s.append(" NULL, 0,\n"); - } - if (resetReactionCount > 0) { - s.append(" _lf_reset_reactions, _lf_reset_reactions_size,\n"); - } else { - s.append(" NULL, 0,\n"); - } - s.append(" _lf_modal_reactor_states, _lf_modal_reactor_states_size);\n"); - s.append("}\n"); - return s.toString(); + // Next handle inputs. + for (Input input : ASTUtils.allInputs(reactor)) { + createTriggerT(body, input, triggerMap, constructorCode, types); } - /** Generate a reaction function definition for a reactor. - * This function will have a single argument that is a void* pointing to - * a struct that contains parameters, state variables, inputs (triggering or not), - * actions (triggering or produced), and outputs. - * @param reaction The reaction. - * @param r The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - public static String generateReaction( - Reaction reaction, - Reactor r, - int reactionIndex, - Instantiation mainDef, - ErrorReporter errorReporter, - CTypes types, - TargetConfig targetConfig, - boolean requiresType - ) { - var code = new CodeBuilder(); - var body = ASTUtils.toText(getCode(types, reaction, r)); - String init = generateInitializationForReaction( - body, reaction, ASTUtils.toDefinition(r), reactionIndex, - types, errorReporter, mainDef, - requiresType); + // Next handle watchdogs. + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + createTriggerT(body, watchdog, triggerMap, constructorCode, types); + } + } - code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetHeader())); + /** + * Define the trigger_t object on the self struct, an array of reaction_t pointers pointing to + * reactions triggered by this variable, and initialize the pointers in the array in the + * constructor. + * + * @param body The place to write the self struct entries. + * @param variable The trigger variable (Timer, Action, Watchdog, or Input). + * @param triggerMap A map from Variables to a list of the reaction indices triggered by the + * variable. + * @param constructorCode The place to write the constructor code. + */ + private static void createTriggerT( + CodeBuilder body, + Variable variable, + LinkedHashMap> triggerMap, + CodeBuilder constructorCode, + CTypes types) { + var varName = variable.getName(); + // variable is a port, a timer, or an action. + body.pr(variable, "trigger_t _lf__" + varName + ";"); + constructorCode.pr(variable, "self->_lf__" + varName + ".last = NULL;"); + constructorCode.pr( + variable, + CExtensionUtils.surroundWithIfFederatedDecentralized( + "self->_lf__" + + varName + + ".intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); - CMethodGenerator.generateMacrosForMethods(ASTUtils.toDefinition(r), code); - code.pr(generateFunction( - generateReactionFunctionHeader(r, reactionIndex), - init, getCode(types, reaction, r) - )); - // Now generate code for the late function, if there is one - // Note that this function can only be defined on reactions - // in federates that have inputs from a logical connection. - if (reaction.getStp() != null) { - code.pr(generateFunction( - generateStpFunctionHeader(r, reactionIndex), - init, reaction.getStp().getCode())); - } + // Generate the reactions triggered table. + var reactionsTriggered = triggerMap.get(variable); + if (reactionsTriggered != null) { + body.pr( + variable, + "reaction_t* _lf__" + varName + "_reactions[" + reactionsTriggered.size() + "];"); + var count = 0; + for (Integer reactionTriggered : reactionsTriggered) { + constructorCode.prSourceLineNumber(variable); + constructorCode.pr( + variable, + "self->_lf__" + + varName + + "_reactions[" + + count + + "] = &self->_lf__reaction_" + + reactionTriggered + + ";"); + count++; + } + // Set up the trigger_t struct's pointer to the reactions. + constructorCode.pr( + variable, + String.join( + "\n", + "self->_lf__" + varName + ".reactions = &self->_lf__" + varName + "_reactions[0];", + "self->_lf__" + varName + ".number_of_reactions = " + count + ";")); - // Now generate code for the deadline violation function, if there is one. - if (reaction.getDeadline() != null) { - code.pr(generateFunction( - generateDeadlineFunctionHeader(r, reactionIndex), - init, reaction.getDeadline().getCode())); - } - CMethodGenerator.generateMacroUndefsForMethods(ASTUtils.toDefinition(r), code); - code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetUndefHeader())); - return code.toString(); + // If federated, set the physical_time_of_arrival + constructorCode.pr( + variable, + CExtensionUtils.surroundWithIfFederated( + "self->_lf__" + varName + ".physical_time_of_arrival = NEVER;")); } + if (variable instanceof Input) { + var rootType = CUtil.rootType(types.getTargetType((Input) variable)); + // Since the self struct is allocated using calloc, there is no need to set falsy fields. + // If the input type is 'void', we need to avoid generating the code + // 'sizeof(void)', which some compilers reject. + var size = (rootType.equals("void")) ? "0" : "sizeof(" + rootType + ")"; - private static Code getCode(CTypes types, Reaction r, ReactorDecl container) { - if (r.getCode() != null) return r.getCode(); - Code ret = LfFactory.eINSTANCE.createCode(); - var reactor = ASTUtils.toDefinition(container); - ret.setBody( - CReactorHeaderFileGenerator.nonInlineInitialization(r, reactor) + "\n" - + r.getName() + "( " + CReactorHeaderFileGenerator.reactionArguments(r, reactor) + " );"); - return ret; + constructorCode.pr("self->_lf__" + varName + ".tmplt.type.element_size = " + size + ";"); + body.pr( + CExtensionUtils.surroundWithIfFederated( + CExtensionUtils.createPortStatusFieldForInput((Input) variable))); } + } - public static String generateFunction(String header, String init, Code code) { - var function = new CodeBuilder(); - function.pr(header + " {"); - function.indent(); - function.pr(init); - function.prSourceLineNumber(code); - function.pr(ASTUtils.toText(code)); - function.unindent(); - function.pr("}"); - return function.toString(); + public static void generateBuiltinTriggeredReactionsArray( + Set reactions, String name, CodeBuilder body, CodeBuilder constructorCode) { + body.pr( + String.join( + "\n", + "trigger_t _lf__" + name + ";", + "reaction_t* _lf__" + name + "_reactions[" + reactions.size() + "];")); + constructorCode.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + "self->_lf__" + name + ".intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); + var i = 0; + for (Integer reactionIndex : reactions) { + constructorCode.pr( + "self->_lf__" + + name + + "_reactions[" + + i++ + + "] = &self->_lf__reaction_" + + reactionIndex + + ";"); } + constructorCode.pr( + String.join( + "\n", + "self->_lf__" + name + ".last = NULL;", + "self->_lf__" + name + ".reactions = &self->_lf__" + name + "_reactions[0];", + "self->_lf__" + name + ".number_of_reactions = " + reactions.size() + ";", + "self->_lf__" + name + ".is_timer = false;")); + } - /** - * Returns the name of the deadline function for reaction. - * @param r The reactor with the deadline - * @param reactionIndex The number assigned to this reaction deadline - */ - public static String generateDeadlineFunctionName(Reactor r, int reactionIndex) { - return CUtil.getName(r).toLowerCase() + "_deadline_function" + reactionIndex; - } + public static String generateBuiltinTriggersTable(int reactionCount, String name) { + return String.join( + "\n", + List.of( + "// Array of pointers to " + name + " triggers.", + (reactionCount > 0 + ? "reaction_t* _lf_" + name + "_reactions[" + reactionCount + "]" + : "reaction_t** _lf_" + name + "_reactions = NULL") + + ";", + "int _lf_" + name + "_reactions_size = " + reactionCount + ";")); + } - /** - * Return the function name for specified reaction of the - * specified reactor. - * @param reactor The reactor - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateReactionFunctionName(Reactor reactor, int reactionIndex) { - return CUtil.getName(reactor).toLowerCase() + "reaction_function_" + reactionIndex; + /** Generate the _lf_trigger_startup_reactions function. */ + public static String generateLfTriggerStartupReactions( + int startupReactionCount, boolean hasModalReactors) { + var s = new StringBuilder(); + s.append("void _lf_trigger_startup_reactions() {"); + if (startupReactionCount > 0) { + s.append("\n"); + if (hasModalReactors) { + s.append( + String.join( + "\n", + " for (int i = 0; i < _lf_startup_reactions_size; i++) {", + " if (_lf_startup_reactions[i] != NULL) {", + " if (_lf_startup_reactions[i]->mode != NULL) {", + " // Skip reactions in modes", + " continue;", + " }", + " _lf_trigger_reaction(_lf_startup_reactions[i], -1);", + " }", + " }", + " _lf_handle_mode_startup_reset_reactions(", + " _lf_startup_reactions, _lf_startup_reactions_size,", + " NULL, 0,", + " _lf_modal_reactor_states, _lf_modal_reactor_states_size);")); + } else { + s.append( + String.join( + "\n", + " for (int i = 0; i < _lf_startup_reactions_size; i++) {", + " if (_lf_startup_reactions[i] != NULL) {", + " _lf_trigger_reaction(_lf_startup_reactions[i], -1);", + " }", + " }")); + } + s.append("\n"); } + s.append("}\n"); + return s.toString(); + } - /** - * Returns the name of the stp function for reaction. - * @param r The reactor with the stp - * @param reactionIndex The number assigned to this reaction deadline - */ - public static String generateStpFunctionName(Reactor r, int reactionIndex) { - return CUtil.getName(r).toLowerCase() + "_STP_function" + reactionIndex; + /** Generate the _lf_trigger_shutdown_reactions function. */ + public static String generateLfTriggerShutdownReactions( + int shutdownReactionCount, boolean hasModalReactors) { + var s = new StringBuilder(); + s.append("bool _lf_trigger_shutdown_reactions() {\n"); + if (shutdownReactionCount > 0) { + if (hasModalReactors) { + s.append( + String.join( + "\n", + " for (int i = 0; i < _lf_shutdown_reactions_size; i++) {", + " if (_lf_shutdown_reactions[i] != NULL) {", + " if (_lf_shutdown_reactions[i]->mode != NULL) {", + " // Skip reactions in modes", + " continue;", + " }", + " _lf_trigger_reaction(_lf_shutdown_reactions[i], -1);", + " }", + " }", + " _lf_handle_mode_shutdown_reactions(_lf_shutdown_reactions," + + " _lf_shutdown_reactions_size);", + " return true;")); + } else { + s.append( + String.join( + "\n", + " for (int i = 0; i < _lf_shutdown_reactions_size; i++) {", + " if (_lf_shutdown_reactions[i] != NULL) {", + " _lf_trigger_reaction(_lf_shutdown_reactions[i], -1);", + " }", + " }", + " return true;")); + } + s.append("\n"); + } else { + s.append(" return false;\n"); } + s.append("}\n"); + return s.toString(); + } - /** Return the top level C function header for the deadline function numbered "reactionIndex" in "r" - * @param r The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the deadline function. - */ - public static String generateDeadlineFunctionHeader(Reactor r, - int reactionIndex) { - String functionName = generateDeadlineFunctionName(r, reactionIndex); - return generateFunctionHeader(functionName); + /** Generate the _lf_handle_mode_triggered_reactions function. */ + public static String generateLfModeTriggeredReactions( + int startupReactionCount, int resetReactionCount, boolean hasModalReactors) { + if (!hasModalReactors) { + return ""; } - - /** Return the top level C function header for the reaction numbered "reactionIndex" in "r" - * @param r The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateReactionFunctionHeader(Reactor r, - int reactionIndex) { - String functionName = generateReactionFunctionName(r, reactionIndex); - return generateFunctionHeader(functionName); + var s = new StringBuilder(); + s.append("void _lf_handle_mode_triggered_reactions() {\n"); + s.append(" _lf_handle_mode_startup_reset_reactions(\n"); + if (startupReactionCount > 0) { + s.append(" _lf_startup_reactions, _lf_startup_reactions_size,\n"); + } else { + s.append(" NULL, 0,\n"); } + if (resetReactionCount > 0) { + s.append(" _lf_reset_reactions, _lf_reset_reactions_size,\n"); + } else { + s.append(" NULL, 0,\n"); + } + s.append(" _lf_modal_reactor_states, _lf_modal_reactor_states_size);\n"); + s.append("}\n"); + return s.toString(); + } + + /** + * Generate a reaction function definition for a reactor. This function will have a single + * argument that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param reaction The reaction. + * @param r The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + public static String generateReaction( + Reaction reaction, + Reactor r, + int reactionIndex, + Instantiation mainDef, + ErrorReporter errorReporter, + CTypes types, + TargetConfig targetConfig, + boolean requiresType) { + var code = new CodeBuilder(); + var body = ASTUtils.toText(getCode(types, reaction, r)); + String init = + generateInitializationForReaction( + body, + reaction, + ASTUtils.toDefinition(r), + reactionIndex, + types, + errorReporter, + mainDef, + requiresType); + + code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader())); - public static String generateStpFunctionHeader(Reactor r, - int reactionIndex) { - String functionName = generateStpFunctionName(r, reactionIndex); - return generateFunctionHeader(functionName); + CMethodGenerator.generateMacrosForMethods(ASTUtils.toDefinition(r), code); + code.pr( + generateFunction( + generateReactionFunctionHeader(r, reactionIndex), init, getCode(types, reaction, r))); + // Now generate code for the late function, if there is one + // Note that this function can only be defined on reactions + // in federates that have inputs from a logical connection. + if (reaction.getStp() != null) { + code.pr( + generateFunction( + generateStpFunctionHeader(r, reactionIndex), init, reaction.getStp().getCode())); } - private static String generateFunctionHeader(String functionName) { - return "void " + functionName + "(void* instance_args)"; + // Now generate code for the deadline violation function, if there is one. + if (reaction.getDeadline() != null) { + code.pr( + generateFunction( + generateDeadlineFunctionHeader(r, reactionIndex), + init, + reaction.getDeadline().getCode())); } + CMethodGenerator.generateMacroUndefsForMethods(ASTUtils.toDefinition(r), code); + code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetUndefHeader())); + return code.toString(); + } + + private static Code getCode(CTypes types, Reaction r, ReactorDecl container) { + if (r.getCode() != null) return r.getCode(); + Code ret = LfFactory.eINSTANCE.createCode(); + var reactor = ASTUtils.toDefinition(container); + ret.setBody( + CReactorHeaderFileGenerator.nonInlineInitialization(r, reactor) + + "\n" + + r.getName() + + "( " + + CReactorHeaderFileGenerator.reactionArguments(r, reactor) + + " );"); + return ret; + } + + public static String generateFunction(String header, String init, Code code) { + var function = new CodeBuilder(); + function.pr(header + " {"); + function.indent(); + function.pr(init); + function.prSourceLineNumber(code); + function.pr(ASTUtils.toText(code)); + function.unindent(); + function.pr("}"); + return function.toString(); + } + + /** + * Returns the name of the deadline function for reaction. + * + * @param r The reactor with the deadline + * @param reactionIndex The number assigned to this reaction deadline + */ + public static String generateDeadlineFunctionName(Reactor r, int reactionIndex) { + return CUtil.getName(r).toLowerCase() + "_deadline_function" + reactionIndex; + } + + /** + * Return the function name for specified reaction of the specified reactor. + * + * @param reactor The reactor + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateReactionFunctionName(Reactor reactor, int reactionIndex) { + return CUtil.getName(reactor).toLowerCase() + "reaction_function_" + reactionIndex; + } + + /** + * Returns the name of the stp function for reaction. + * + * @param r The reactor with the stp + * @param reactionIndex The number assigned to this reaction deadline + */ + public static String generateStpFunctionName(Reactor r, int reactionIndex) { + return CUtil.getName(r).toLowerCase() + "_STP_function" + reactionIndex; + } + + /** + * Return the top level C function header for the deadline function numbered "reactionIndex" in + * "r" + * + * @param r The reactor declaration + * @param reactionIndex The reaction index. + * @return The function name for the deadline function. + */ + public static String generateDeadlineFunctionHeader(Reactor r, int reactionIndex) { + String functionName = generateDeadlineFunctionName(r, reactionIndex); + return generateFunctionHeader(functionName); + } + + /** + * Return the top level C function header for the reaction numbered "reactionIndex" in "r" + * + * @param r The reactor declaration + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateReactionFunctionHeader(Reactor r, int reactionIndex) { + String functionName = generateReactionFunctionName(r, reactionIndex); + return generateFunctionHeader(functionName); + } + + public static String generateStpFunctionHeader(Reactor r, int reactionIndex) { + String functionName = generateStpFunctionName(r, reactionIndex); + return generateFunctionHeader(functionName); + } + + public static String generateFunctionHeader(String functionName) { + return "void " + functionName + "(void* instance_args)"; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java new file mode 100644 index 0000000000..5a6f1dc3ba --- /dev/null +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -0,0 +1,238 @@ +package org.lflang.generator.c; + +import java.util.List; +import org.lflang.ASTUtils; +import org.lflang.generator.CodeBuilder; +import org.lflang.lf.Mode; +import org.lflang.lf.ModeTransition; +import org.lflang.lf.Reactor; +import org.lflang.lf.ReactorDecl; +import org.lflang.lf.VarRef; +import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; + +/** + * Generates necessary C code for watchdogs. + * + * @author{Benjamin Asch } + */ +public class CWatchdogGenerator { + + /** + * Generate necessary initialization code inside the body of the watchdog that belongs to reactor + * decl. + * + * @param decl The reactor that has the watchdog + */ + public static String generateInitializationForWatchdog(Watchdog watchdog, ReactorDecl decl) { + Reactor reactor = ASTUtils.toDefinition(decl); + + // Construct the reactionInitialization code to go into + // the body of the function before the verbatim code. + CodeBuilder watchdogInitialization = new CodeBuilder(); + + CodeBuilder code = new CodeBuilder(); + + // Define the "self" struct. + String structType = CUtil.selfType(reactor); + // A null structType means there are no inputs, state, + // or anything else. No need to declare it. + if (structType != null) { + code.pr( + String.join( + "\n", + structType + + "* self = (" + + structType + + "*)instance_args; SUPPRESS_UNUSED_WARNING(self);")); + } + + // Declare mode if in effects field of watchdog + if (watchdog.getEffects() != null) { + for (VarRef effect : watchdog.getEffects()) { + Variable variable = effect.getVariable(); + if (variable instanceof Mode) { + // Mode change effect + int idx = ASTUtils.allModes(reactor).indexOf((Mode) effect.getVariable()); + String name = effect.getVariable().getName(); + if (idx >= 0) { + watchdogInitialization.pr( + "reactor_mode_t* " + + name + + " = &self->_lf__modes[" + + idx + + "];\n" + + "lf_mode_change_type_t _lf_" + + name + + "_change_type = " + + (effect.getTransition() == ModeTransition.HISTORY + ? "history_transition" + : "reset_transition") + + ";"); + } + // FIXME: include error reporter + // else { + // errorReporter.reportError( + // watchdog, + // "In generateWatchdog(): " + name + " not a valid mode of this reactor." + // ); + // } + } + } + } + // Add watchdog definition + watchdogInitialization.pr( + "watchdog_t* " + + watchdog.getName() + + " = &(self->_lf_watchdog_" + + watchdog.getName() + + ");\n"); + + // Next generate all the collected setup code. + code.pr(watchdogInitialization.toString()); + return code.toString(); + } + + /** + * Returns the name of the watchdog function for reaction. + * + * @param decl The reactor with the watchdog + * @param watchdog The watchdog + * @return Name of the watchdog function for reaction + */ + public static String generateWatchdogFunctionName(Watchdog watchdog, ReactorDecl decl) { + return decl.getName().toLowerCase() + + "_" + + watchdog.getName().toLowerCase() + + "_watchdog_function"; + } + + /** + * Return the top level C function header for the watchdog function in "decl" + * + * @param decl The reactor declaration + * @param watchdog The watchdog. + * @return The function name for the watchdog function. + */ + public static String generateWatchdogFunctionHeader(Watchdog watchdog, ReactorDecl decl) { + String functionName = generateWatchdogFunctionName(watchdog, decl); + return CReactionGenerator.generateFunctionHeader(functionName); + } + + /** Generate the watchdog function. */ + public static String generateWatchdogFunction(Watchdog watchdog, ReactorDecl decl) { + return generateFunction( + generateWatchdogFunctionHeader(watchdog, decl), + generateInitializationForWatchdog(watchdog, decl), + watchdog); + } + + /** + * Do heavy lifting to generate above watchdog function + * + * @param header function name and declaration. + * @param init initialize variable. + * @param watchdog The watchdog. + */ + public static String generateFunction(String header, String init, Watchdog watchdog) { + var function = new CodeBuilder(); + function.pr(header + " {"); + function.indent(); + function.pr(init); + function.pr("_lf_schedule((*" + watchdog.getName() + ").trigger, 0, NULL);"); + function.prSourceLineNumber(watchdog.getCode()); + function.pr(ASTUtils.toText(watchdog.getCode())); + function.unindent(); + function.pr("}"); + return function.toString(); + } + + /** Generate watchdog definition in parent struct. */ + public static void generateWatchdogStruct( + CodeBuilder body, ReactorDecl decl, CodeBuilder constructorCode) { + var reactor = ASTUtils.toDefinition(decl); + + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + String watchdogName = watchdog.getName(); + + body.pr(watchdog, "watchdog_t _lf_watchdog_" + watchdogName + ";"); + + // watchdog function name + var watchdogFunctionName = generateWatchdogFunctionName(watchdog, decl); + // Set values of watchdog_t struct in the reactor's constructor + // FIXME: update parameters + // constructorCode.pr("#ifdef LF_THREADED"); + constructorCode.pr( + watchdog, + String.join( + "\n", + "self->_lf_watchdog_" + watchdogName + ".base = &(self->base);", + "self->_lf_watchdog_" + watchdogName + ".expiration = NEVER;", + "self->_lf_watchdog_" + watchdogName + ".thread_active = false;", + // "self->_lf_watchdog_"+watchdogName+".min_expiration = "+min_expiration+";", + "self->_lf_watchdog_" + + watchdogName + + ".watchdog_function = " + + watchdogFunctionName + + ";", + "self->_lf_watchdog_" + + watchdogName + + ".trigger = &(self->_lf__" + + watchdogName + + ");")); + } + } + + /** + * Generate a watchdog function definition for a reactor. This function will have a single + * argument that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param watchdog The watchdog. + * @param decl The reactor. + */ + public static String generateWatchdog(Watchdog watchdog, ReactorDecl decl) { + var code = new CodeBuilder(); + + code.pr(generateWatchdogFunction(watchdog, decl)); + + return code.toString(); + } + + public static String generateBuiltinTriggersTable(int count, String name) { + return String.join( + "\n", + List.of( + "// Array of pointers to " + name + " triggers.", + "#ifdef LF_THREADED", + (count > 0 + ? " watchdog_t* _lf_" + name + "s[" + count + "]" + : " watchdog_t* _lf_" + name + "s = NULL") + + ";", + " int _lf_" + name + "_number = " + count + ";", + "#endif")); + } + + /** Generate _lf_initialize_watchdog_mutexes function. */ + // FIXME: finish implementing + public static String generateLfInitializeWatchdogMutexes(int watchdogCount) { + var s = new StringBuilder(); + s.append("void _lf_initialize_watchdog_mutexes() {"); + if (watchdogCount > 0) { + s.append("\n"); + s.append( + String.join( + "\n", + " for (int i = 0; i < _lf_watchdog_number; i++) {", + " self_base_t* current_base = _lf_watchdogs[i]->base;", + " if (&(current_base->watchdog_mutex) == NULL) {", + " lf_mutex_init(&(current_base->watchdog_mutex));", + " current_base->has_watchdog = true;", + " }", + " }")); + } + s.append("\n"); + s.append("}\n"); + return s.toString(); + } +} diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 020954be80..f1f0e9b59d 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -34,18 +34,14 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; - -import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; - import org.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.generator.CodeBuilder; import org.lflang.generator.CodeMap; - import org.lflang.generator.GeneratorResult; import org.lflang.generator.IntegratedBuilder; import org.lflang.generator.LFGeneratorContext; @@ -56,7 +52,6 @@ import org.lflang.generator.c.CGenerator; import org.lflang.generator.c.CUtil; import org.lflang.lf.Action; -import org.lflang.lf.Code; import org.lflang.lf.Input; import org.lflang.lf.Model; import org.lflang.lf.Output; @@ -68,525 +63,505 @@ import org.lflang.util.LFCommand; import org.lflang.util.StringUtil; - /** - * Generator for Python target. This class generates Python code defining each - * reactor - * class given in the input .lf file and imported .lf files. + * Generator for Python target. This class generates Python code defining each reactor class given + * in the input .lf file and imported .lf files. * - * Each class will contain all the reaction functions defined by the user in - * order, with the necessary ports/actions given as parameters. - * Moreover, each class will contain all state variables in native Python - * format. + *

Each class will contain all the reaction functions defined by the user in order, with the + * necessary ports/actions given as parameters. Moreover, each class will contain all state + * variables in native Python format. * - * A backend is also generated using the CGenerator that interacts with the C - * code library (see CGenerator.xtend). - * The backend is responsible for passing arguments to the Python reactor + *

A backend is also generated using the CGenerator that interacts with the C code library (see + * CGenerator.xtend). The backend is responsible for passing arguments to the Python reactor * functions. * * @author Soroush Bateni */ public class PythonGenerator extends CGenerator { - // Used to add statements that come before reactor classes and user code - private final CodeBuilder pythonPreamble = new CodeBuilder(); - - // Used to add module requirements to setup.py (delimited with ,) - private final List pythonRequiredModules = new ArrayList<>(); - - private final PythonTypes types; - - public PythonGenerator(LFGeneratorContext context) { - this(context, - new PythonTypes(), - new CCmakeGenerator( - context.getFileConfig(), - List.of("lib/python_action.c", - "lib/python_port.c", - "lib/python_tag.c", - "lib/python_time.c", - "lib/pythontarget.c" - ), - PythonGenerator::setUpMainTarget, - "install(TARGETS)" // No-op - ) - ); - } - - - private PythonGenerator(LFGeneratorContext context, PythonTypes types, CCmakeGenerator cmakeGenerator) { - super(context, false, types, cmakeGenerator, new PythonDelayBodyGenerator(types)); - this.targetConfig.compiler = "gcc"; - this.targetConfig.compilerFlags = new ArrayList<>(); - this.targetConfig.linkerFlags = ""; - this.types = types; - } - - /** - * Generic struct for ports with primitive types and - * statically allocated arrays in Lingua Franca. - * This template is defined as - * typedef struct { - * bool is_present; - * lf_sparse_io_record_t* sparse_record; // NULL if there is no sparse record. - * int destination_channel; // -1 if there is no destination. - * PyObject* value; - * int num_destinations; - * lf_token_t* token; - * int length; - * void (*destructor) (void* value); - * void* (*copy_constructor) (void* value); - * FEDERATED_GENERIC_EXTENSION - * } generic_port_instance_struct; - * - * See reactor-c-py/lib/pythontarget.h for details. - */ - String genericPortType = "generic_port_instance_struct"; - - /** - * Generic struct for actions. - * This template is defined as - * typedef struct { - * trigger_t* trigger; - * PyObject* value; - * bool is_present; - * bool has_value; - * lf_token_t* token; - * FEDERATED_CAPSULE_EXTENSION - * } generic_action_instance_struct; - * - * See reactor-c-py/lib/pythontarget.h for details. - */ - String genericActionType = "generic_action_instance_struct"; - - /** Returns the Target enum for this generator */ - @Override - public Target getTarget() { - return Target.Python; + // Used to add statements that come before reactor classes and user code + private final CodeBuilder pythonPreamble = new CodeBuilder(); + + // Used to add module requirements to setup.py (delimited with ,) + private final List pythonRequiredModules = new ArrayList<>(); + + private final PythonTypes types; + + public PythonGenerator(LFGeneratorContext context) { + this( + context, + new PythonTypes(), + new CCmakeGenerator( + context.getFileConfig(), + List.of( + "lib/python_action.c", + "lib/python_port.c", + "lib/python_tag.c", + "lib/python_time.c", + "lib/pythontarget.c"), + PythonGenerator::setUpMainTarget, + "install(TARGETS)" // No-op + )); + } + + private PythonGenerator( + LFGeneratorContext context, PythonTypes types, CCmakeGenerator cmakeGenerator) { + super(context, false, types, cmakeGenerator, new PythonDelayBodyGenerator(types)); + this.targetConfig.compiler = "gcc"; + this.targetConfig.compilerFlags = new ArrayList<>(); + this.targetConfig.linkerFlags = ""; + this.types = types; + } + + /** + * Generic struct for ports with primitive types and statically allocated arrays in Lingua Franca. + * This template is defined as typedef struct { bool is_present; lf_sparse_io_record_t* + * sparse_record; // NULL if there is no sparse record. int destination_channel; // -1 if there is + * no destination. PyObject* value; int num_destinations; lf_token_t* token; int length; void + * (*destructor) (void* value); void* (*copy_constructor) (void* value); + * FEDERATED_GENERIC_EXTENSION } generic_port_instance_struct; + * + *

See reactor-c-py/lib/pythontarget.h for details. + */ + String genericPortType = "generic_port_instance_struct"; + + /** + * Generic struct for actions. This template is defined as typedef struct { trigger_t* trigger; + * PyObject* value; bool is_present; bool has_value; lf_token_t* token; + * FEDERATED_CAPSULE_EXTENSION } generic_action_instance_struct; + * + *

See reactor-c-py/lib/pythontarget.h for details. + */ + String genericActionType = "generic_action_instance_struct"; + + /** Returns the Target enum for this generator */ + @Override + public Target getTarget() { + return Target.Python; + } + + private final Set protoNames = new HashSet<>(); + + // ////////////////////////////////////////// + // // Public methods + @Override + public TargetTypes getTargetTypes() { + return types; + } + + // ////////////////////////////////////////// + // // Protected methods + + /** Generate all Python classes if they have a reaction */ + public String generatePythonReactorClasses() { + CodeBuilder pythonClasses = new CodeBuilder(); + CodeBuilder pythonClassesInstantiation = new CodeBuilder(); + + // Generate reactor classes in Python + pythonClasses.pr(PythonReactorGenerator.generatePythonClass(main, main, types)); + + // Create empty lists to hold reactor instances + pythonClassesInstantiation.pr(PythonReactorGenerator.generateListsToHoldClassInstances(main)); + + // Instantiate generated classes + pythonClassesInstantiation.pr( + PythonReactorGenerator.generatePythonClassInstantiations(main, main)); + + return String.join( + "\n", + pythonClasses.toString(), + "", + "# Instantiate classes", + pythonClassesInstantiation.toString()); + } + + /** + * Generate the Python code constructed from reactor classes and user-written classes. + * + * @return the code body + */ + public String generatePythonCode(String pyModuleName) { + return String.join( + "\n", + "import os", + "import sys", + "sys.path.append(os.path.dirname(__file__))", + "# List imported names, but do not use pylint's --extension-pkg-allow-list option", + "# so that these names will be assumed present without having to compile and install.", + "# pylint: disable=no-name-in-module, import-error", + "from " + pyModuleName + " import (", + " Tag, action_capsule_t, port_capsule, request_stop, schedule_copy, start", + ")", + "# pylint: disable=c-extension-no-member", + "import " + pyModuleName + " as lf", + "try:", + " from LinguaFrancaBase.constants import BILLION, FOREVER, NEVER, instant_t, interval_t", + " from LinguaFrancaBase.functions import (", + " DAY, DAYS, HOUR, HOURS, MINUTE, MINUTES, MSEC, MSECS, NSEC, NSECS, SEC, SECS," + + " USEC,", + " USECS, WEEK, WEEKS", + " )", + " from LinguaFrancaBase.classes import Make", + "except ModuleNotFoundError:", + " print(\"No module named 'LinguaFrancaBase'. \"", + " \"Install using \\\"pip3 install LinguaFrancaBase\\\".\")", + " sys.exit(1)", + "import copy", + "", + pythonPreamble.toString(), + "", + generatePythonReactorClasses(), + "", + PythonMainFunctionGenerator.generateCode()); + } + + /** Generate the necessary Python files. */ + public Map generatePythonFiles( + String lfModuleName, String pyModuleName, String pyFileName) throws IOException { + Path filePath = fileConfig.getSrcGenPath().resolve(pyFileName); + File file = filePath.toFile(); + Files.deleteIfExists(filePath); + // Create the necessary directories + if (!file.getParentFile().exists()) { + if (!file.getParentFile().mkdirs()) { + throw new IOException( + "Failed to create directories required for the Python code generator."); + } } - - private final Set protoNames = new HashSet<>(); - - // ////////////////////////////////////////// - // // Public methods - @Override - public TargetTypes getTargetTypes() { - return types; - } - - // ////////////////////////////////////////// - // // Protected methods - - /** - * Generate all Python classes if they have a reaction - * - */ - public String generatePythonReactorClasses() { - CodeBuilder pythonClasses = new CodeBuilder(); - CodeBuilder pythonClassesInstantiation = new CodeBuilder(); - - // Generate reactor classes in Python - pythonClasses.pr(PythonReactorGenerator.generatePythonClass(main, main, types)); - - // Create empty lists to hold reactor instances - pythonClassesInstantiation.pr(PythonReactorGenerator.generateListsToHoldClassInstances(main)); - - // Instantiate generated classes - pythonClassesInstantiation.pr(PythonReactorGenerator.generatePythonClassInstantiations(main, main)); - - return String.join("\n", - pythonClasses.toString(), - "", - "# Instantiate classes", - pythonClassesInstantiation.toString() - ); - } - - /** - * Generate the Python code constructed from reactor classes and - * user-written classes. - * - * @return the code body - */ - public String generatePythonCode(String pyModuleName) { - return String.join("\n", - "import os", - "import sys", - "sys.path.append(os.path.dirname(__file__))", - "# List imported names, but do not use pylint's --extension-pkg-allow-list option", - "# so that these names will be assumed present without having to compile and install.", - "# pylint: disable=no-name-in-module, import-error", - "from "+pyModuleName+" import (", - " Tag, action_capsule_t, port_capsule, request_stop, schedule_copy, start", - ")", - "# pylint: disable=c-extension-no-member", - "import "+pyModuleName+" as lf", - "try:", - " from LinguaFrancaBase.constants import BILLION, FOREVER, NEVER, instant_t, interval_t", - " from LinguaFrancaBase.functions import (", - " DAY, DAYS, HOUR, HOURS, MINUTE, MINUTES, MSEC, MSECS, NSEC, NSECS, SEC, SECS, USEC,", - " USECS, WEEK, WEEKS", - " )", - " from LinguaFrancaBase.classes import Make", - "except ModuleNotFoundError:", - " print(\"No module named 'LinguaFrancaBase'. \"", - " \"Install using \\\"pip3 install LinguaFrancaBase\\\".\")", - " sys.exit(1)", - "import copy", - "", - pythonPreamble.toString(), - "", - generatePythonReactorClasses(), - "", - PythonMainFunctionGenerator.generateCode() - ); - } - - /** - * Generate the necessary Python files. - */ - public Map generatePythonFiles( - String lfModuleName, - String pyModuleName, - String pyFileName - ) throws IOException { - Path filePath = fileConfig.getSrcGenPath().resolve(pyFileName); - File file = filePath.toFile(); - Files.deleteIfExists(filePath); - // Create the necessary directories - if (!file.getParentFile().exists()) { - if (!file.getParentFile().mkdirs()) { - throw new IOException( - "Failed to create directories required for the Python code generator." - ); - } - } - Map codeMaps = new HashMap<>(); - codeMaps.put(filePath, CodeMap.fromGeneratedCode( - generatePythonCode(pyModuleName))); - FileUtil.writeToFile(codeMaps.get(filePath).getGeneratedCode(), filePath); - return codeMaps; - } - - /** - * Generate code that needs to appear at the top of the generated - * C file, such as #define and #include statements. - */ - @Override - public String generateDirectives() { - CodeBuilder code = new CodeBuilder(); - code.prComment("Code generated by the Lingua Franca compiler from:"); - code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); - code.pr(PythonPreambleGenerator.generateCDefineDirectives( + Map codeMaps = new HashMap<>(); + codeMaps.put(filePath, CodeMap.fromGeneratedCode(generatePythonCode(pyModuleName))); + FileUtil.writeToFile(codeMaps.get(filePath).getGeneratedCode(), filePath); + return codeMaps; + } + + /** + * Generate code that needs to appear at the top of the generated C file, such as #define and + * #include statements. + */ + @Override + public String generateDirectives() { + CodeBuilder code = new CodeBuilder(); + code.prComment("Code generated by the Lingua Franca compiler from:"); + code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); + code.pr( + PythonPreambleGenerator.generateCDefineDirectives( targetConfig, fileConfig.getSrcGenPath(), hasModalReactors)); - return code.toString(); - } - - /** - * Override generate top-level preambles, but put the user preambles in the - * .py file rather than the C file. Also handles including the federated - * execution setup preamble specified in the target config. - */ - @Override - protected String generateTopLevelPreambles(Reactor ignored) { - // user preambles - Set models = new LinkedHashSet<>(); - for (Reactor r : ASTUtils.convertToEmptyListIfNull(reactors)) { - // The following assumes all reactors have a container. - // This means that generated reactors **have** to be - // added to a resource; not doing so will result in a NPE. - models.add((Model) ASTUtils.toDefinition(r).eContainer()); - } - // Add the main reactor if it is defined - if (this.mainDef != null) { - models.add((Model) ASTUtils.toDefinition(this.mainDef.getReactorClass()).eContainer()); - } - for (Model m : models) { - pythonPreamble.pr(PythonPreambleGenerator.generatePythonPreambles(m.getPreambles())); - } - return PythonPreambleGenerator.generateCIncludeStatements(targetConfig, targetLanguageIsCpp(), hasModalReactors); + return code.toString(); + } + + /** + * Override generate top-level preambles, but put the user preambles in the .py file rather than + * the C file. Also handles including the federated execution setup preamble specified in the + * target config. + */ + @Override + protected String generateTopLevelPreambles(Reactor ignored) { + // user preambles + Set models = new LinkedHashSet<>(); + for (Reactor r : ASTUtils.convertToEmptyListIfNull(reactors)) { + // The following assumes all reactors have a container. + // This means that generated reactors **have** to be + // added to a resource; not doing so will result in a NPE. + models.add((Model) ASTUtils.toDefinition(r).eContainer()); } - - @Override - protected void handleProtoFiles() { - for (String name : targetConfig.protoFiles) { - this.processProtoFile(name); - int dotIndex = name.lastIndexOf("."); - String rootFilename = dotIndex > 0 ? name.substring(0, dotIndex) : name; - pythonPreamble.pr("import "+rootFilename+"_pb2 as "+rootFilename); - protoNames.add(rootFilename); - } + // Add the main reactor if it is defined + if (this.mainDef != null) { + models.add((Model) ASTUtils.toDefinition(this.mainDef.getReactorClass()).eContainer()); } - - /** - * Process a given .proto file. - * - * Run, if possible, the proto-c protocol buffer code generator to produce - * the required .h and .c files. - * - * @param filename Name of the file to process. - */ - @Override - public void processProtoFile(String filename) { - LFCommand protoc = commandFactory.createCommand( - "protoc", List.of("--python_out=" - + fileConfig.getSrcGenPath(), filename), fileConfig.srcPath); - - if (protoc == null) { - errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1"); - return; - } - int returnCode = protoc.run(); - if (returnCode == 0) { - pythonRequiredModules.add("google-api-python-client"); - } else { - errorReporter.reportError( - "protoc returns error code " + returnCode); - } + for (Model m : models) { + pythonPreamble.pr(PythonPreambleGenerator.generatePythonPreambles(m.getPreambles())); } - - /** - * Generate the aliases for inputs, outputs, and struct type definitions for - * actions of the specified reactor in the specified federate. - * @param r The parsed reactor data structure. - */ - @Override - public void generateAuxiliaryStructs( - CodeBuilder builder, Reactor r, boolean userFacing - ) { - for (Input input : ASTUtils.allInputs(r)) { - generateAuxiliaryStructsForPort(builder, r, input); - } - for (Output output : ASTUtils.allOutputs(r)) { - generateAuxiliaryStructsForPort(builder, r, output); - } - for (Action action : ASTUtils.allActions(r)) { - generateAuxiliaryStructsForAction(builder, r, action); - } + return PythonPreambleGenerator.generateCIncludeStatements( + targetConfig, targetLanguageIsCpp(), hasModalReactors); + } + + @Override + protected void handleProtoFiles() { + for (String name : targetConfig.protoFiles) { + this.processProtoFile(name); + int dotIndex = name.lastIndexOf("."); + String rootFilename = dotIndex > 0 ? name.substring(0, dotIndex) : name; + pythonPreamble.pr("import " + rootFilename + "_pb2 as " + rootFilename); + protoNames.add(rootFilename); } - - private void generateAuxiliaryStructsForPort(CodeBuilder builder, Reactor r, - Port port) { - boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(port), types); - builder.pr(port, - PythonPortGenerator.generateAliasTypeDef(r, port, isTokenType, - genericPortType)); + } + + /** + * Process a given .proto file. + * + *

Run, if possible, the proto-c protocol buffer code generator to produce the required .h and + * .c files. + * + * @param filename Name of the file to process. + */ + @Override + public void processProtoFile(String filename) { + LFCommand protoc = + commandFactory.createCommand( + "protoc", + List.of("--python_out=" + fileConfig.getSrcGenPath(), filename), + fileConfig.srcPath); + + if (protoc == null) { + errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1"); + return; } - - private void generateAuxiliaryStructsForAction(CodeBuilder builder, Reactor r, - Action action) { - builder.pr(action, PythonActionGenerator.generateAliasTypeDef(r, action, genericActionType)); + int returnCode = protoc.run(); + if (returnCode == 0) { + pythonRequiredModules.add("google-api-python-client"); + } else { + errorReporter.reportError("protoc returns error code " + returnCode); } - - /** - * Return true if the host operating system is compatible and - * otherwise report an error and return false. - */ - @Override - public boolean isOSCompatible() { - return true; + } + + /** + * Generate the aliases for inputs, outputs, and struct type definitions for actions of the + * specified reactor in the specified federate. + * + * @param r The parsed reactor data structure. + */ + @Override + public void generateAuxiliaryStructs(CodeBuilder builder, Reactor r, boolean userFacing) { + for (Input input : ASTUtils.allInputs(r)) { + generateAuxiliaryStructsForPort(builder, r, input); } - - /** - * Generate C code from the Lingua Franca model contained by the - * specified resource. This is the main entry point for code - * generation. - * - * @param resource The resource containing the source code. - * @param context Context relating to invocation of the code generator. - */ - @Override - public void doGenerate(Resource resource, LFGeneratorContext context) { - // Set the threading to false by default, unless the user has - // specifically asked for it. - if (!targetConfig.setByUser.contains(TargetProperty.THREADING)) { - targetConfig.threading = false; - } - int cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2; - code.pr(PythonPreambleGenerator.generateCIncludeStatements(targetConfig, targetLanguageIsCpp(), hasModalReactors)); - super.doGenerate(resource, new SubContext( - context, - IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, - cGeneratedPercentProgress - )); - - if (errorsOccurred()) { - context.unsuccessfulFinish(); - return; - } - - Map codeMaps = new HashMap<>(); - var lfModuleName = fileConfig.name; - // Don't generate code if there is no main reactor - if (this.main != null) { - try { - Map codeMapsForFederate = generatePythonFiles(lfModuleName, generatePythonModuleName(lfModuleName), generatePythonFileName(lfModuleName)); - codeMaps.putAll(codeMapsForFederate); - copyTargetFiles(); - new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context); - if (targetConfig.noCompile) { - System.out.println(PythonInfoGenerator.generateSetupInfo(fileConfig)); - } - } catch (Exception e) { - //noinspection ConstantConditions - throw Exceptions.sneakyThrow(e); - } - - System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, lfModuleName)); - } - - if (errorReporter.getErrorsOccurred()) { - context.unsuccessfulFinish(); - } else { - context.finish(GeneratorResult.Status.COMPILED, codeMaps); - } + for (Output output : ASTUtils.allOutputs(r)) { + generateAuxiliaryStructsForPort(builder, r, output); } - - @Override - protected PythonDockerGenerator getDockerGenerator(LFGeneratorContext context) { - return new PythonDockerGenerator(context); + for (Action action : ASTUtils.allActions(r)) { + generateAuxiliaryStructsForAction(builder, r, action); } - - /** Generate a reaction function definition for a reactor. - * This function has a single argument that is a void* pointing to - * a struct that contains parameters, state variables, inputs (triggering or not), - * actions (triggering or produced), and outputs. - * @param reaction The reaction. - * @param r The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - @Override - protected void generateReaction(CodeBuilder src, Reaction reaction, Reactor r, int reactionIndex) { - Reactor reactor = ASTUtils.toDefinition(r); - - // Reactions marked with a `@_c_body` attribute are generated in C - if (AttributeUtils.hasCBody(reaction)) { - super.generateReaction(src, reaction, r, reactionIndex); - return; - } - src.pr(PythonReactionGenerator.generateCReaction(reaction, reactor, reactionIndex, mainDef, errorReporter, types)); + } + + private void generateAuxiliaryStructsForPort(CodeBuilder builder, Reactor r, Port port) { + boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(port), types); + builder.pr( + port, PythonPortGenerator.generateAliasTypeDef(r, port, isTokenType, genericPortType)); + } + + private void generateAuxiliaryStructsForAction(CodeBuilder builder, Reactor r, Action action) { + builder.pr(action, PythonActionGenerator.generateAliasTypeDef(r, action, genericActionType)); + } + + /** + * Return true if the host operating system is compatible and otherwise report an error and return + * false. + */ + @Override + public boolean isOSCompatible() { + return true; + } + + /** + * Generate C code from the Lingua Franca model contained by the specified resource. This is the + * main entry point for code generation. + * + * @param resource The resource containing the source code. + * @param context Context relating to invocation of the code generator. + */ + @Override + public void doGenerate(Resource resource, LFGeneratorContext context) { + // Set the threading to false by default, unless the user has + // specifically asked for it. + if (!targetConfig.setByUser.contains(TargetProperty.THREADING)) { + targetConfig.threading = false; } - - /** - * Generate code that initializes the state variables for a given instance. - * Unlike parameters, state variables are uniformly initialized for all - * instances - * of the same reactor. This task is left to Python code to allow for more - * liberal - * state variable assignments. - * - * @param instance The reactor class instance - * @return Initialization code fore state variables of instance - */ - @Override - protected void generateStateVariableInitializations(ReactorInstance instance) { - // Do nothing + int cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2; + code.pr( + PythonPreambleGenerator.generateCIncludeStatements( + targetConfig, targetLanguageIsCpp(), hasModalReactors)); + super.doGenerate( + resource, + new SubContext( + context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, cGeneratedPercentProgress)); + + if (errorsOccurred()) { + context.unsuccessfulFinish(); + return; } - /** - * Generate runtime initialization code in C for parameters of a given - * reactor instance - * - * @param instance The reactor instance. - */ - @Override - protected void generateParameterInitialization(ReactorInstance instance) { - // Do nothing - // Parameters are initialized in Python - } + Map codeMaps = new HashMap<>(); + var lfModuleName = fileConfig.name; + // Don't generate code if there is no main reactor + if (this.main != null) { + try { + Map codeMapsForFederate = + generatePythonFiles( + lfModuleName, + generatePythonModuleName(lfModuleName), + generatePythonFileName(lfModuleName)); + codeMaps.putAll(codeMapsForFederate); + copyTargetFiles(); + new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context); + if (targetConfig.noCompile) { + System.out.println(PythonInfoGenerator.generateSetupInfo(fileConfig)); + } + } catch (Exception e) { + //noinspection ConstantConditions + throw Exceptions.sneakyThrow(e); + } - /** - * Do nothing. - * Methods are generated in Python not C. - * @see PythonMethodGenerator - */ - @Override - protected void generateMethods(CodeBuilder src, ReactorDecl reactor) { } - - /** - * Generate C preambles defined by user for a given reactor - * Since the Python generator expects preambles written in C, - * this function is overridden and does nothing. - * - * @param reactor The given reactor - */ - @Override - protected void generateUserPreamblesForReactor(Reactor reactor, CodeBuilder src) { - // Do nothing + System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, lfModuleName)); } - /** - * Generate code that is executed while the reactor instance is being - * initialized. - * This wraps the reaction functions in a Python function. - * @param instance The reactor instance. - */ - @Override - protected void generateReactorInstanceExtension( - ReactorInstance instance - ) { - initializeTriggerObjects.pr(PythonReactionGenerator.generateCPythonReactionLinkers(instance, mainDef)); + if (errorReporter.getErrorsOccurred()) { + context.unsuccessfulFinish(); + } else { + context.finish(GeneratorResult.Status.COMPILED, codeMaps); } - - /** - * This function is provided to allow extensions of the CGenerator to append the structure of the self struct - * @param selfStructBody The body of the self struct - * @param decl The reactor declaration for the self struct - * @param constructorCode Code that is executed when the reactor is instantiated - */ - @Override - protected void generateSelfStructExtension( - CodeBuilder selfStructBody, - ReactorDecl decl, - CodeBuilder constructorCode - ) { - Reactor reactor = ASTUtils.toDefinition(decl); - // Add the name field - selfStructBody.pr("char *_lf_name;"); - int reactionIndex = 0; - for (Reaction reaction : ASTUtils.allReactions(reactor)) { - // Create a PyObject for each reaction - selfStructBody.pr("PyObject* "+ PythonReactionGenerator.generateCPythonReactionFunctionName(reactionIndex)+";"); - if (reaction.getStp() != null) { - selfStructBody.pr("PyObject* "+ PythonReactionGenerator.generateCPythonSTPFunctionName(reactionIndex)+";"); - } - if (reaction.getDeadline() != null) { - selfStructBody.pr("PyObject* "+ PythonReactionGenerator.generateCPythonDeadlineFunctionName(reactionIndex)+";"); - } - reactionIndex++; - } + } + + @Override + protected PythonDockerGenerator getDockerGenerator(LFGeneratorContext context) { + return new PythonDockerGenerator(context); + } + + /** + * Generate a reaction function definition for a reactor. This function has a single argument that + * is a void* pointing to a struct that contains parameters, state variables, inputs (triggering + * or not), actions (triggering or produced), and outputs. + * + * @param reaction The reaction. + * @param r The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + @Override + protected void generateReaction( + CodeBuilder src, Reaction reaction, Reactor r, int reactionIndex) { + Reactor reactor = ASTUtils.toDefinition(r); + + // Reactions marked with a `@_c_body` attribute are generated in C + if (AttributeUtils.hasCBody(reaction)) { + super.generateReaction(src, reaction, r, reactionIndex); + return; } - - @Override - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - // NOTE: Strangely, a newline is needed at the beginning or indentation - // gets swallowed. - return String.join("\n", - "\n# Generated forwarding reaction for connections with the same destination", - "# but located in mutually exclusive modes.", - dest + ".set(" + source + ".value)\n" - ); + src.pr( + PythonReactionGenerator.generateCReaction( + reaction, reactor, reactionIndex, mainDef, errorReporter, types)); + } + + /** + * Generate code that initializes the state variables for a given instance. Unlike parameters, + * state variables are uniformly initialized for all instances of the same reactor. This task is + * left to Python code to allow for more liberal state variable assignments. + * + * @param instance The reactor class instance + * @return Initialization code fore state variables of instance + */ + @Override + protected void generateStateVariableInitializations(ReactorInstance instance) { + // Do nothing + } + + /** + * Generate runtime initialization code in C for parameters of a given reactor instance + * + * @param instance The reactor instance. + */ + @Override + protected void generateParameterInitialization(ReactorInstance instance) { + // Do nothing + // Parameters are initialized in Python + } + + /** + * Do nothing. Methods are generated in Python not C. + * + * @see PythonMethodGenerator + */ + @Override + protected void generateMethods(CodeBuilder src, ReactorDecl reactor) {} + + /** + * Generate C preambles defined by user for a given reactor Since the Python generator expects + * preambles written in C, this function is overridden and does nothing. + * + * @param reactor The given reactor + */ + @Override + protected void generateUserPreamblesForReactor(Reactor reactor, CodeBuilder src) { + // Do nothing + } + + /** + * Generate code that is executed while the reactor instance is being initialized. This wraps the + * reaction functions in a Python function. + * + * @param instance The reactor instance. + */ + @Override + protected void generateReactorInstanceExtension(ReactorInstance instance) { + initializeTriggerObjects.pr( + PythonReactionGenerator.generateCPythonReactionLinkers(instance, mainDef)); + } + + /** + * This function is provided to allow extensions of the CGenerator to append the structure of the + * self struct + * + * @param selfStructBody The body of the self struct + * @param decl The reactor declaration for the self struct + * @param constructorCode Code that is executed when the reactor is instantiated + */ + @Override + protected void generateSelfStructExtension( + CodeBuilder selfStructBody, ReactorDecl decl, CodeBuilder constructorCode) { + Reactor reactor = ASTUtils.toDefinition(decl); + // Add the name field + selfStructBody.pr("char *_lf_name;"); + int reactionIndex = 0; + for (Reaction reaction : ASTUtils.allReactions(reactor)) { + // Create a PyObject for each reaction + selfStructBody.pr( + "PyObject* " + + PythonReactionGenerator.generateCPythonReactionFunctionName(reactionIndex) + + ";"); + if (reaction.getStp() != null) { + selfStructBody.pr( + "PyObject* " + + PythonReactionGenerator.generateCPythonSTPFunctionName(reactionIndex) + + ";"); + } + if (reaction.getDeadline() != null) { + selfStructBody.pr( + "PyObject* " + + PythonReactionGenerator.generateCPythonDeadlineFunctionName(reactionIndex) + + ";"); + } + reactionIndex++; } - - @Override - protected void setUpGeneralParameters() { - super.setUpGeneralParameters(); - if (hasModalReactors) { - targetConfig.compileAdditionalSources.add("lib/modal_models/impl.c"); - } + } + + @Override + protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { + // NOTE: Strangely, a newline is needed at the beginning or indentation + // gets swallowed. + return String.join( + "\n", + "\n# Generated forwarding reaction for connections with the same destination", + "# but located in mutually exclusive modes.", + dest + ".set(" + source + ".value)\n"); + } + + @Override + protected void setUpGeneralParameters() { + super.setUpGeneralParameters(); + if (hasModalReactors) { + targetConfig.compileAdditionalSources.add("lib/modal_models/impl.c"); } + } - @Override - protected void additionalPostProcessingForModes() { - if (!hasModalReactors) { - return; - } - PythonModeGenerator.generateResetReactionsIfNeeded(reactors); + @Override + protected void additionalPostProcessingForModes() { + if (!hasModalReactors) { + return; } + PythonModeGenerator.generateResetReactionsIfNeeded(reactors); + } - private static String setUpMainTarget(boolean hasMain, String executableName, Stream cSources) { - return ( - """ + private static String setUpMainTarget( + boolean hasMain, String executableName, Stream cSources) { + return (""" set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_compile_definitions(_LF_GARBAGE_COLLECTED) add_subdirectory(core) @@ -611,71 +586,61 @@ private static String setUpMainTarget(boolean hasMain, String executableName, St include_directories(${Python_INCLUDE_DIRS}) target_link_libraries(${LF_MAIN_TARGET} PRIVATE ${Python_LIBRARIES}) target_compile_definitions(${LF_MAIN_TARGET} PUBLIC MODULE_NAME=) - """ - ).replace("", generatePythonModuleName(executableName)) - .replace("executableName", executableName); - // The use of fileConfig.name will break federated execution, but that's fine - } - - /** - * Generate a (`key`, `val`) tuple pair for the `define_macros` field - * of the Extension class constructor from setuptools. - * - * @param key The key of the macro entry - * @param val The value of the macro entry - * @return A (`key`, `val`) tuple pair as String - */ - private static String generateMacroEntry(String key, String val) { - return "(" + StringUtil.addDoubleQuotes(key) + ", " + StringUtil.addDoubleQuotes(val) + ")"; - } - - /** - * Generate the name of the python module. - * - * Ideally, this function would belong in a class like `PyFileConfig` - * that specifies all the paths to the generated code. - * - * @param lfModuleName The name of the LF module. - * @return The name of the python module. - */ - private static String generatePythonModuleName(String lfModuleName) { - return "LinguaFranca" + lfModuleName; - } - - /** - * Generate the python file name given an `lfModuleName`. - * - * Ideally, this function would belong in a class like `PyFileConfig` - * that specifies all the paths to the generated code. - * - * @param lfModuleName The name of the LF module - * @return The name of the generated python file. - */ - private static String generatePythonFileName(String lfModuleName) { - return lfModuleName + ".py"; - } - - /** - * Copy Python specific target code to the src-gen directory - */ - @Override - protected void copyTargetFiles() throws IOException { - super.copyTargetFiles(); - FileUtil.copyDirectoryFromClassPath( - "/lib/py/reactor-c-py/include", - fileConfig.getSrcGenPath().resolve("include"), - true - ); - FileUtil.copyDirectoryFromClassPath( - "/lib/py/reactor-c-py/lib", - fileConfig.getSrcGenPath().resolve("lib"), - true - ); - FileUtil.copyDirectoryFromClassPath( - "/lib/py/reactor-c-py/LinguaFrancaBase", - fileConfig.getSrcGenPath().resolve("LinguaFrancaBase"), - true - ); - } - + """) + .replace("", generatePythonModuleName(executableName)) + .replace("executableName", executableName); + // The use of fileConfig.name will break federated execution, but that's fine + } + + /** + * Generate a (`key`, `val`) tuple pair for the `define_macros` field of the Extension class + * constructor from setuptools. + * + * @param key The key of the macro entry + * @param val The value of the macro entry + * @return A (`key`, `val`) tuple pair as String + */ + private static String generateMacroEntry(String key, String val) { + return "(" + StringUtil.addDoubleQuotes(key) + ", " + StringUtil.addDoubleQuotes(val) + ")"; + } + + /** + * Generate the name of the python module. + * + *

Ideally, this function would belong in a class like `PyFileConfig` that specifies all the + * paths to the generated code. + * + * @param lfModuleName The name of the LF module. + * @return The name of the python module. + */ + private static String generatePythonModuleName(String lfModuleName) { + return "LinguaFranca" + lfModuleName; + } + + /** + * Generate the python file name given an `lfModuleName`. + * + *

Ideally, this function would belong in a class like `PyFileConfig` that specifies all the + * paths to the generated code. + * + * @param lfModuleName The name of the LF module + * @return The name of the generated python file. + */ + private static String generatePythonFileName(String lfModuleName) { + return lfModuleName + ".py"; + } + + /** Copy Python specific target code to the src-gen directory */ + @Override + protected void copyTargetFiles() throws IOException { + super.copyTargetFiles(); + FileUtil.copyDirectoryFromClassPath( + "/lib/py/reactor-c-py/include", fileConfig.getSrcGenPath().resolve("include"), true); + FileUtil.copyDirectoryFromClassPath( + "/lib/py/reactor-c-py/lib", fileConfig.getSrcGenPath().resolve("lib"), true); + FileUtil.copyDirectoryFromClassPath( + "/lib/py/reactor-c-py/LinguaFrancaBase", + fileConfig.getSrcGenPath().resolve("LinguaFrancaBase"), + true); + } } diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java index ae268a9327..32cd8d4a97 100644 --- a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java +++ b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java @@ -1,27 +1,27 @@ /************* -Copyright (c) 2020, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - 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 -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 -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ + * Copyright (c) 2020, The University of California at Berkeley. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * 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 + * 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 + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ package org.lflang.scoping; @@ -29,251 +29,251 @@ import static org.lflang.ASTUtils.*; import com.google.inject.Inject; - import java.util.ArrayList; - import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.naming.SimpleNameProvider; import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.Scopes; import org.eclipse.xtext.scoping.impl.SelectableBasedScope; - import org.lflang.lf.Assignment; import org.lflang.lf.Connection; import org.lflang.lf.Deadline; import org.lflang.lf.Import; import org.lflang.lf.ImportedReactor; import org.lflang.lf.Instantiation; +import org.lflang.lf.LfPackage; +import org.lflang.lf.Mode; import org.lflang.lf.Model; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; -import org.lflang.lf.LfPackage; -import org.lflang.lf.Mode; /** - * This class enforces custom rules. In particular, it resolves references to - * parameters, ports, actions, and timers. Ports can be referenced across at - * most one level of hierarchy. Parameters, actions, and timers can be - * referenced locally, within the reactor. + * This class enforces custom rules. In particular, it resolves references to parameters, ports, + * actions, and timers. Ports can be referenced across at most one level of hierarchy. Parameters, + * actions, and timers can be referenced locally, within the reactor. * * @author Marten Lohstroh * @see */ public class LFScopeProviderImpl extends AbstractLFScopeProvider { - @Inject - private SimpleNameProvider nameProvider; + @Inject private SimpleNameProvider nameProvider; - @Inject - private LFGlobalScopeProvider scopeProvider; + @Inject private LFGlobalScopeProvider scopeProvider; - /** - * Enumerate of the kinds of references. - */ - enum RefType { - NULL, - TRIGGER, - SOURCE, - EFFECT, - DEADLINE, - CLEFT, - CRIGHT - } + /** Enumerate of the kinds of references. */ + enum RefType { + NULL, + TRIGGER, + SOURCE, + EFFECT, + WATCHDOG, + DEADLINE, + CLEFT, + CRIGHT + } - /** - * Depending on the provided context, construct the appropriate scope - * for the given reference. - * - * @param context The AST node in which a to-be-resolved reference occurs. - * @param reference The reference to resolve. - */ - @Override - public IScope getScope(EObject context, EReference reference) { - if (context instanceof VarRef) { - return getScopeForVarRef((VarRef) context, reference); - } else if (context instanceof Assignment) { - return getScopeForAssignment((Assignment) context, reference); - } else if (context instanceof Instantiation) { - return getScopeForReactorDecl(context, reference); - } else if (context instanceof Reactor) { - return getScopeForReactorDecl(context, reference); - } else if (context instanceof ImportedReactor) { - return getScopeForImportedReactor((ImportedReactor) context, reference); - } - return super.getScope(context, reference); + /** + * Depending on the provided context, construct the appropriate scope for the given reference. + * + * @param context The AST node in which a to-be-resolved reference occurs. + * @param reference The reference to resolve. + */ + @Override + public IScope getScope(EObject context, EReference reference) { + if (context instanceof VarRef) { + return getScopeForVarRef((VarRef) context, reference); + } else if (context instanceof Assignment) { + return getScopeForAssignment((Assignment) context, reference); + } else if (context instanceof Instantiation) { + return getScopeForReactorDecl(context, reference); + } else if (context instanceof Reactor) { + return getScopeForReactorDecl(context, reference); + } else if (context instanceof ImportedReactor) { + return getScopeForImportedReactor((ImportedReactor) context, reference); } + return super.getScope(context, reference); + } - /** - * Filter out candidates that do not originate from the file listed in - * this particular import statement. - */ - protected IScope getScopeForImportedReactor(ImportedReactor context, EReference reference) { - String importURI = ((Import) context.eContainer()).getImportURI(); - var importedURI = scopeProvider.resolve(importURI == null ? "" : importURI, context.eResource()); - if (importedURI != null) { - var uniqueImportURIs = scopeProvider.getImportedUris(context.eResource()); - var descriptions = scopeProvider.getResourceDescriptions(context.eResource(), uniqueImportURIs); - var description = descriptions.getResourceDescription(importedURI); - return SelectableBasedScope.createScope(IScope.NULLSCOPE, description, null, reference.getEReferenceType(), false); - } - return Scopes.scopeFor(emptyList()); + /** + * Filter out candidates that do not originate from the file listed in this particular import + * statement. + */ + protected IScope getScopeForImportedReactor(ImportedReactor context, EReference reference) { + String importURI = ((Import) context.eContainer()).getImportURI(); + var importedURI = + scopeProvider.resolve(importURI == null ? "" : importURI, context.eResource()); + if (importedURI != null) { + var uniqueImportURIs = scopeProvider.getImportedUris(context.eResource()); + var descriptions = + scopeProvider.getResourceDescriptions(context.eResource(), uniqueImportURIs); + var description = descriptions.getResourceDescription(importedURI); + return SelectableBasedScope.createScope( + IScope.NULLSCOPE, description, null, reference.getEReferenceType(), false); } + return Scopes.scopeFor(emptyList()); + } - /** - * @param obj Instantiation or Reactor that has a ReactorDecl to resolve. - * @param reference The reference to link to a ReactorDecl node. - */ - protected IScope getScopeForReactorDecl(EObject obj, EReference reference) { + /** + * @param obj Instantiation or Reactor that has a ReactorDecl to resolve. + * @param reference The reference to link to a ReactorDecl node. + */ + protected IScope getScopeForReactorDecl(EObject obj, EReference reference) { - // Find the local Model - Model model = null; - EObject container = obj; - while(model == null && container != null) { - container = container.eContainer(); - if (container instanceof Model) { - model = (Model)container; - } - } - if (model == null) { - return Scopes.scopeFor(emptyList()); - } + // Find the local Model + Model model = null; + EObject container = obj; + while (model == null && container != null) { + container = container.eContainer(); + if (container instanceof Model) { + model = (Model) container; + } + } + if (model == null) { + return Scopes.scopeFor(emptyList()); + } - // Collect eligible candidates, all of which are local (i.e., not in other files). - var locals = new ArrayList(model.getReactors()); + // Collect eligible candidates, all of which are local (i.e., not in other files). + var locals = new ArrayList(model.getReactors()); - // Either point to the import statement (if it is renamed) - // or directly to the reactor definition. - for (Import it : model.getImports()) { - for (ImportedReactor ir : it.getReactorClasses()) { - if (ir.getName() != null) { - locals.add(ir); - } else if (ir.getReactorClass() != null) { - locals.add(ir.getReactorClass()); - } - } + // Either point to the import statement (if it is renamed) + // or directly to the reactor definition. + for (Import it : model.getImports()) { + for (ImportedReactor ir : it.getReactorClasses()) { + if (ir.getName() != null) { + locals.add(ir); + } else if (ir.getReactorClass() != null) { + locals.add(ir.getReactorClass()); } - return Scopes.scopeFor(locals); + } } + return Scopes.scopeFor(locals); + } - protected IScope getScopeForAssignment(Assignment assignment, EReference reference) { + protected IScope getScopeForAssignment(Assignment assignment, EReference reference) { - if (reference == LfPackage.Literals.ASSIGNMENT__LHS) { - var defn = toDefinition(((Instantiation) assignment.eContainer()).getReactorClass()); - if (defn != null) { - return Scopes.scopeFor(allParameters(defn)); - } - - } - if (reference == LfPackage.Literals.ASSIGNMENT__RHS) { - return Scopes.scopeFor(((Reactor) assignment.eContainer().eContainer()).getParameters()); - } - return Scopes.scopeFor(emptyList()); + if (reference == LfPackage.Literals.ASSIGNMENT__LHS) { + var defn = toDefinition(((Instantiation) assignment.eContainer()).getReactorClass()); + if (defn != null) { + return Scopes.scopeFor(allParameters(defn)); + } + } + if (reference == LfPackage.Literals.ASSIGNMENT__RHS) { + return Scopes.scopeFor(((Reactor) assignment.eContainer().eContainer()).getParameters()); } + return Scopes.scopeFor(emptyList()); + } - protected IScope getScopeForVarRef(VarRef variable, EReference reference) { - if (reference == LfPackage.Literals.VAR_REF__VARIABLE) { - // Resolve hierarchical reference - Reactor reactor; - Mode mode = null; - if (variable.eContainer().eContainer() instanceof Reactor) { - reactor = (Reactor) variable.eContainer().eContainer(); - } else if (variable.eContainer().eContainer() instanceof Mode) { - mode = (Mode) variable.eContainer().eContainer(); - reactor = (Reactor) variable.eContainer().eContainer().eContainer(); - } else { - return Scopes.scopeFor(emptyList()); - } + protected IScope getScopeForVarRef(VarRef variable, EReference reference) { + if (reference == LfPackage.Literals.VAR_REF__VARIABLE) { + // Resolve hierarchical reference + Reactor reactor; + Mode mode = null; + if (variable.eContainer().eContainer() instanceof Reactor) { + reactor = (Reactor) variable.eContainer().eContainer(); + } else if (variable.eContainer().eContainer() instanceof Mode) { + mode = (Mode) variable.eContainer().eContainer(); + reactor = (Reactor) variable.eContainer().eContainer().eContainer(); + } else { + return Scopes.scopeFor(emptyList()); + } - RefType type = getRefType(variable); + RefType type = getRefType(variable); - if (variable.getContainer() != null) { // Resolve hierarchical port reference - var instanceName = nameProvider.getFullyQualifiedName(variable.getContainer()); - var instances = new ArrayList(reactor.getInstantiations()); - if (mode != null) { - instances.addAll(mode.getInstantiations()); - } + if (variable.getContainer() != null) { // Resolve hierarchical port reference + var instanceName = nameProvider.getFullyQualifiedName(variable.getContainer()); + var instances = new ArrayList(reactor.getInstantiations()); + if (mode != null) { + instances.addAll(mode.getInstantiations()); + } - if (instanceName != null) { - for (var instance : instances) { - var defn = toDefinition(instance.getReactorClass()); - if (defn != null && instance.getName().equals(instanceName.toString())) { - switch (type) { - case TRIGGER: - case SOURCE: - case CLEFT: - return Scopes.scopeFor(allOutputs(defn)); - case EFFECT: - case DEADLINE: - case CRIGHT: - return Scopes.scopeFor(allInputs(defn)); - } - } - } - } - return Scopes.scopeFor(emptyList()); - } else { - // Resolve local reference - switch (type) { - case TRIGGER: { - var candidates = new ArrayList(); - if (mode != null) { - candidates.addAll(mode.getActions()); - candidates.addAll(mode.getTimers()); - } - candidates.addAll(allInputs(reactor)); - candidates.addAll(allActions(reactor)); - candidates.addAll(allTimers(reactor)); - return Scopes.scopeFor(candidates); - } + if (instanceName != null) { + for (var instance : instances) { + var defn = toDefinition(instance.getReactorClass()); + if (defn != null && instance.getName().equals(instanceName.toString())) { + switch (type) { + case TRIGGER: case SOURCE: - return super.getScope(variable, reference); - case EFFECT: { - var candidates = new ArrayList(); - if (mode != null) { - candidates.addAll(mode.getActions()); - candidates.addAll(reactor.getModes()); - } - candidates.addAll(allOutputs(reactor)); - candidates.addAll(allActions(reactor)); - return Scopes.scopeFor(candidates); - } - case DEADLINE: case CLEFT: - return Scopes.scopeFor(allInputs(reactor)); + return Scopes.scopeFor(allOutputs(defn)); + case EFFECT: + case DEADLINE: case CRIGHT: - return Scopes.scopeFor(allOutputs(reactor)); - default: - return Scopes.scopeFor(emptyList()); - } + return Scopes.scopeFor(allInputs(defn)); + } } - } else { // Resolve instance - return super.getScope(variable, reference); + } } - } - - private RefType getRefType(VarRef variable) { - if (variable.eContainer() instanceof Deadline) { - return RefType.DEADLINE; - } else if (variable.eContainer() instanceof Reaction) { - var reaction = (Reaction) variable.eContainer(); - if (reaction.getTriggers().contains(variable)) { - return RefType.TRIGGER; - } else if (reaction.getSources().contains(variable)) { - return RefType.SOURCE; - } else if (reaction.getEffects().contains(variable)) { - return RefType.EFFECT; + return Scopes.scopeFor(emptyList()); + } else { + // Resolve local reference + switch (type) { + case TRIGGER: + { + var candidates = new ArrayList(); + if (mode != null) { + candidates.addAll(mode.getActions()); + candidates.addAll(mode.getTimers()); + } + candidates.addAll(allInputs(reactor)); + candidates.addAll(allActions(reactor)); + candidates.addAll(allTimers(reactor)); + candidates.addAll(allWatchdogs(reactor)); + return Scopes.scopeFor(candidates); } - } else if (variable.eContainer() instanceof Connection) { - var conn = (Connection) variable.eContainer(); - if (conn.getLeftPorts().contains(variable)) { - return RefType.CLEFT; - } else if (conn.getRightPorts().contains(variable)) { - return RefType.CRIGHT; + case SOURCE: + return super.getScope(variable, reference); + case EFFECT: + { + var candidates = new ArrayList(); + if (mode != null) { + candidates.addAll(mode.getActions()); + candidates.addAll(reactor.getModes()); + } + candidates.addAll(allOutputs(reactor)); + candidates.addAll(allActions(reactor)); + candidates.addAll(allWatchdogs(reactor)); + return Scopes.scopeFor(candidates); } + case WATCHDOG: + return Scopes.scopeFor(allWatchdogs(reactor)); + case DEADLINE: + case CLEFT: + return Scopes.scopeFor(allInputs(reactor)); + case CRIGHT: + return Scopes.scopeFor(allOutputs(reactor)); + default: + return Scopes.scopeFor(emptyList()); } - return RefType.NULL; + } + } else { // Resolve instance + return super.getScope(variable, reference); + } + } + + private RefType getRefType(VarRef variable) { + if (variable.eContainer() instanceof Deadline) { + return RefType.DEADLINE; + } else if (variable.eContainer() instanceof Reaction) { + var reaction = (Reaction) variable.eContainer(); + if (reaction.getTriggers().contains(variable)) { + return RefType.TRIGGER; + } else if (reaction.getSources().contains(variable)) { + return RefType.SOURCE; + } else if (reaction.getEffects().contains(variable)) { + return RefType.EFFECT; + } + } else if (variable.eContainer() instanceof Connection) { + var conn = (Connection) variable.eContainer(); + if (conn.getLeftPorts().contains(variable)) { + return RefType.CLEFT; + } else if (conn.getRightPorts().contains(variable)) { + return RefType.CRIGHT; + } } + return RefType.NULL; + } } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 7f1aa5c808..b0555dc4e0 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -2,17 +2,13 @@ /************* * Copyright (c) 2019-2020, The University of California at Berkeley. - * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, * 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 @@ -28,12 +24,10 @@ import static org.lflang.ASTUtils.inferPortWidth; import static org.lflang.ASTUtils.isGeneric; -import static org.lflang.ASTUtils.isInteger; -import static org.lflang.ASTUtils.isOfTimeType; -import static org.lflang.ASTUtils.isZero; import static org.lflang.ASTUtils.toDefinition; import static org.lflang.ASTUtils.toOriginalText; +import com.google.inject.Inject; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -45,7 +39,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; - import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EAttribute; @@ -72,7 +65,6 @@ import org.lflang.lf.BracedListExpression; import org.lflang.lf.BuiltinTrigger; import org.lflang.lf.BuiltinTriggerRef; -import org.lflang.lf.CodeExpr; import org.lflang.lf.Connection; import org.lflang.lf.Deadline; import org.lflang.lf.Expression; @@ -116,12 +108,10 @@ import org.lflang.lf.WidthTerm; import org.lflang.util.FileUtil; -import com.google.inject.Inject; - /** * Custom validation checks for Lingua Franca programs. * - * Also see: https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation + *

Also see: https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation * * @author Edward A. Lee * @author Marten Lohstroh @@ -132,1750 +122,1832 @@ */ public class LFValidator extends BaseLFValidator { - ////////////////////////////////////////////////////////////// - //// Public check methods. - - // These methods are automatically invoked on AST nodes matching - // the types of their arguments. - // CheckType.FAST ensures that these checks run whenever a file is modified. - // Alternatives are CheckType.NORMAL (when saving) and - // CheckType.EXPENSIVE (only when right-click, validate). - // FIXME: What is the default when nothing is specified? - - // These methods are listed in alphabetical order, and, although - // it is isn't strictly required, follow a naming convention - // checkClass, where Class is the AST class, where possible. - - @Check(CheckType.FAST) - public void checkAction(Action action) { - checkName(action.getName(), Literals.VARIABLE__NAME); - if (action.getOrigin() == ActionOrigin.NONE) { - error( - "Action must have modifier `logical` or `physical`.", - Literals.ACTION__ORIGIN - ); - } - if (action.getPolicy() != null && - !SPACING_VIOLATION_POLICIES.contains(action.getPolicy())) { - error( - "Unrecognized spacing violation policy: " + action.getPolicy() + - ". Available policies are: " + - String.join(", ", SPACING_VIOLATION_POLICIES) + ".", - Literals.ACTION__POLICY); - } - checkExpressionIsTime(action.getMinDelay(), Literals.ACTION__MIN_DELAY); - checkExpressionIsTime(action.getMinSpacing(), Literals.ACTION__MIN_SPACING); + ////////////////////////////////////////////////////////////// + //// Public check methods. + + // These methods are automatically invoked on AST nodes matching + // the types of their arguments. + // CheckType.FAST ensures that these checks run whenever a file is modified. + // Alternatives are CheckType.NORMAL (when saving) and + // CheckType.EXPENSIVE (only when right-click, validate). + // FIXME: What is the default when nothing is specified? + + // These methods are listed in alphabetical order, and, although + // it is isn't strictly required, follow a naming convention + // checkClass, where Class is the AST class, where possible. + + @Check(CheckType.FAST) + public void checkAction(Action action) { + checkName(action.getName(), Literals.VARIABLE__NAME); + if (action.getOrigin() == ActionOrigin.NONE) { + error("Action must have modifier `logical` or `physical`.", Literals.ACTION__ORIGIN); } - - - @Check(CheckType.FAST) - public void checkInitializer(Initializer init) { - if (init.isBraces() && target != Target.CPP) { - error("Brace initializers are only supported for the C++ target", Literals.INITIALIZER__BRACES); - } else if (init.isParens() && target.mandatesEqualsInitializers()) { - var message = "This syntax is deprecated in the " + target - + " target, use an equal sign instead of parentheses for assignment."; - if (init.getExprs().size() == 1) { - message += " (run the formatter to fix this automatically)"; - } - warning(message, Literals.INITIALIZER__PARENS); - } else if (!init.isAssign() && init.eContainer() instanceof Assignment) { - var feature = init.isBraces() ? Literals.INITIALIZER__BRACES - : Literals.INITIALIZER__PARENS; - var message = "This syntax is deprecated, do not use parentheses or braces but an equal sign."; - if (init.getExprs().size() == 1) { - message += " (run the formatter to fix this automatically)"; - } - warning(message, feature); - } + if (action.getPolicy() != null && !SPACING_VIOLATION_POLICIES.contains(action.getPolicy())) { + error( + "Unrecognized spacing violation policy: " + + action.getPolicy() + + ". Available policies are: " + + String.join(", ", SPACING_VIOLATION_POLICIES) + + ".", + Literals.ACTION__POLICY); } - - @Check(CheckType.FAST) - public void checkBracedExpression(BracedListExpression expr) { - if (!target.allowsBracedListExpressions()) { - var message = "Braced expression lists are not a valid expression for the " + target - + " target."; - error(message, Literals.BRACED_LIST_EXPRESSION.eContainmentFeature()); - } + checkExpressionIsTime(action.getMinDelay(), Literals.ACTION__MIN_DELAY); + checkExpressionIsTime(action.getMinSpacing(), Literals.ACTION__MIN_SPACING); + } + + @Check(CheckType.FAST) + public void checkInitializer(Initializer init) { + if (init.isBraces() && target != Target.CPP) { + error( + "Brace initializers are only supported for the C++ target", Literals.INITIALIZER__BRACES); + } else if (init.isParens() && target.mandatesEqualsInitializers()) { + var message = + "This syntax is deprecated in the " + + target + + " target, use an equal sign instead of parentheses for assignment."; + if (init.getExprs().size() == 1) { + message += " (run the formatter to fix this automatically)"; + } + warning(message, Literals.INITIALIZER__PARENS); + } else if (!init.isAssign() && init.eContainer() instanceof Assignment) { + var feature = init.isBraces() ? Literals.INITIALIZER__BRACES : Literals.INITIALIZER__PARENS; + var message = + "This syntax is deprecated, do not use parentheses or braces but an equal sign."; + if (init.getExprs().size() == 1) { + message += " (run the formatter to fix this automatically)"; + } + warning(message, feature); } - - @Check(CheckType.FAST) - public void checkAssignment(Assignment assignment) { - - // If the left-hand side is a time parameter, make sure the assignment has units - typeCheck(assignment.getRhs(), ASTUtils.getInferredType(assignment.getLhs()), Literals.ASSIGNMENT__RHS); - // If this assignment overrides a parameter that is used in a deadline, - // report possible overflow. - if (isCBasedTarget() && - this.info.overflowingAssignments.contains(assignment)) { - error( - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.ASSIGNMENT__RHS); - } - + } + + @Check(CheckType.FAST) + public void checkBracedExpression(BracedListExpression expr) { + if (!target.allowsBracedListExpressions()) { + var message = + "Braced expression lists are not a valid expression for the " + target + " target."; + error(message, Literals.BRACED_LIST_EXPRESSION.eContainmentFeature()); } + } + + @Check(CheckType.FAST) + public void checkAssignment(Assignment assignment) { + + // If the left-hand side is a time parameter, make sure the assignment has units + typeCheck( + assignment.getRhs(), + ASTUtils.getInferredType(assignment.getLhs()), + Literals.ASSIGNMENT__RHS); + // If this assignment overrides a parameter that is used in a deadline, + // report possible overflow. + if (isCBasedTarget() && this.info.overflowingAssignments.contains(assignment)) { + error( + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + + " nanoseconds.", + Literals.ASSIGNMENT__RHS); + } + } - @Check(CheckType.FAST) - public void checkConnection(Connection connection) { + @Check(CheckType.FAST) + public void checkConnection(Connection connection) { - // Report if connection is part of a cycle. - Set> cycles = this.info.topologyCycles(); - for (VarRef lp : connection.getLeftPorts()) { - for (VarRef rp : connection.getRightPorts()) { - boolean leftInCycle = false; + // Report if connection is part of a cycle. + Set> cycles = this.info.topologyCycles(); + for (VarRef lp : connection.getLeftPorts()) { + for (VarRef rp : connection.getRightPorts()) { + boolean leftInCycle = false; - for (NamedInstance it : cycles) { - if ((lp.getContainer() == null && it.getDefinition().equals(lp.getVariable())) - || (it.getDefinition().equals(lp.getVariable()) && it.getParent().equals(lp.getContainer()))) { - leftInCycle = true; - break; - } - } - - for (NamedInstance it : cycles) { - if ((rp.getContainer() == null && it.getDefinition().equals(rp.getVariable())) - || (it.getDefinition().equals(rp.getVariable()) && it.getParent().equals(rp.getContainer()))) { - if (leftInCycle) { - Reactor reactor = ASTUtils.getEnclosingReactor(connection); - String reactorName = reactor.getName(); - error(String.format("Connection in reactor %s creates", reactorName) + - String.format("a cyclic dependency between %s and %s.", toOriginalText(lp), toOriginalText(rp)), - Literals.CONNECTION__DELAY); - } - } - } - } - } - - // FIXME: look up all ReactorInstance objects that have a definition equal to the - // container of this connection. For each of those occurrences, the widths have to match. - // For the C target, since C has such a weak type system, check that - // the types on both sides of every connection match. For other languages, - // we leave type compatibility that language's compiler or interpreter. - if (isCBasedTarget()) { - Type type = (Type) null; - for (VarRef port : connection.getLeftPorts()) { - // If the variable is not a port, then there is some other - // error. Avoid a class cast exception. - if (port.getVariable() instanceof Port) { - if (type == null) { - type = ((Port) port.getVariable()).getType(); - } else { - // Unfortunately, xtext does not generate a suitable equals() - // method for AST types, so we have to manually check the types. - if (!sameType(type, ((Port) port.getVariable()).getType())) { - error("Types do not match.", Literals.CONNECTION__LEFT_PORTS); - } - } - } - } - for (VarRef port : connection.getRightPorts()) { - // If the variable is not a port, then there is some other - // error. Avoid a class cast exception. - if (port.getVariable() instanceof Port) { - if (type == null) { - type = ((Port) port.getVariable()).getType(); - } else { - if (!sameType(type, type = ((Port) port.getVariable()).getType())) { - error("Types do not match.", Literals.CONNECTION__RIGHT_PORTS); - } - } - } - } - } - - // Check whether the total width of the left side of the connection - // matches the total width of the right side. This cannot be determined - // here if the width is not given as a constant. In that case, it is up - // to the code generator to check it. - int leftWidth = 0; - for (VarRef port : connection.getLeftPorts()) { - int width = inferPortWidth(port, null, null); // null args imply incomplete check. - if (width < 0 || leftWidth < 0) { - // Cannot determine the width of the left ports. - leftWidth = -1; - } else { - leftWidth += width; - } - } - int rightWidth = 0; - for (VarRef port : connection.getRightPorts()) { - int width = inferPortWidth(port, null, null); // null args imply incomplete check. - if (width < 0 || rightWidth < 0) { - // Cannot determine the width of the left ports. - rightWidth = -1; - } else { - rightWidth += width; - } - } - - if (leftWidth != -1 && rightWidth != -1 && leftWidth != rightWidth) { - if (connection.isIterated()) { - if (leftWidth == 0 || rightWidth % leftWidth != 0) { - // FIXME: The second argument should be Literals.CONNECTION, but - // stupidly, xtext will not accept that. There seems to be no way to - // report an error for the whole connection statement. - warning(String.format("Left width %s does not divide right width %s", leftWidth, rightWidth), - Literals.CONNECTION__LEFT_PORTS - ); - } - } else { - // FIXME: The second argument should be Literals.CONNECTION, but - // stupidly, xtext will not accept that. There seems to be no way to - // report an error for the whole connection statement. - warning(String.format("Left width %s does not match right width %s", leftWidth, rightWidth), - Literals.CONNECTION__LEFT_PORTS - ); - } + for (NamedInstance it : cycles) { + if ((lp.getContainer() == null && it.getDefinition().equals(lp.getVariable())) + || (it.getDefinition().equals(lp.getVariable()) + && it.getParent().equals(lp.getContainer()))) { + leftInCycle = true; + break; + } } - Reactor reactor = ASTUtils.getEnclosingReactor(connection); - - // Make sure the right port is not already an effect of a reaction. - for (Reaction reaction : ASTUtils.allReactions(reactor)) { - for (VarRef effect : reaction.getEffects()) { - for (VarRef rightPort : connection.getRightPorts()) { - if (rightPort.getVariable().equals(effect.getVariable()) && // Refers to the same variable - rightPort.getContainer() == effect.getContainer() && // Refers to the same instance - ( reaction.eContainer() instanceof Reactor || // Either is not part of a mode - connection.eContainer() instanceof Reactor || - connection.eContainer() == reaction.eContainer() // Or they are in the same mode - )) { - error("Cannot connect: Port named '" + effect.getVariable().getName() + - "' is already effect of a reaction.", - Literals.CONNECTION__RIGHT_PORTS); - } - } + for (NamedInstance it : cycles) { + if ((rp.getContainer() == null && it.getDefinition().equals(rp.getVariable())) + || (it.getDefinition().equals(rp.getVariable()) + && it.getParent().equals(rp.getContainer()))) { + if (leftInCycle) { + Reactor reactor = ASTUtils.getEnclosingReactor(connection); + String reactorName = reactor.getName(); + error( + String.format("Connection in reactor %s creates", reactorName) + + String.format( + "a cyclic dependency between %s and %s.", + toOriginalText(lp), toOriginalText(rp)), + Literals.CONNECTION__DELAY); } + } } + } + } - // Check that the right port does not already have some other - // upstream connection. - for (Connection c : reactor.getConnections()) { - if (c != connection) { - for (VarRef thisRightPort : connection.getRightPorts()) { - for (VarRef thatRightPort : c.getRightPorts()) { - if (thisRightPort.getVariable().equals(thatRightPort.getVariable()) && // Refers to the same variable - thisRightPort.getContainer() == thatRightPort.getContainer() && // Refers to the same instance - ( connection.eContainer() instanceof Reactor || // Or either of the connections in not part of a mode - c.eContainer() instanceof Reactor || - connection.eContainer() == c.eContainer() // Or they are in the same mode - )) { - error( - "Cannot connect: Port named '" + thisRightPort.getVariable().getName() + - "' may only appear once on the right side of a connection.", - Literals.CONNECTION__RIGHT_PORTS); - } - } - } + // FIXME: look up all ReactorInstance objects that have a definition equal to the + // container of this connection. For each of those occurrences, the widths have to match. + // For the C target, since C has such a weak type system, check that + // the types on both sides of every connection match. For other languages, + // we leave type compatibility that language's compiler or interpreter. + if (isCBasedTarget()) { + Type type = (Type) null; + for (VarRef port : connection.getLeftPorts()) { + // If the variable is not a port, then there is some other + // error. Avoid a class cast exception. + if (port.getVariable() instanceof Port) { + if (type == null) { + type = ((Port) port.getVariable()).getType(); + } else { + // Unfortunately, xtext does not generate a suitable equals() + // method for AST types, so we have to manually check the types. + if (!sameType(type, ((Port) port.getVariable()).getType())) { + error("Types do not match.", Literals.CONNECTION__LEFT_PORTS); } + } } - - // Check the after delay - if (connection.getDelay() != null) { - final var delay = connection.getDelay(); - if (delay instanceof ParameterReference || delay instanceof Time || delay instanceof Literal) { - checkExpressionIsTime(delay, Literals.CONNECTION__DELAY); - } else { - error("After delays can only be given by time literals or parameters.", - Literals.CONNECTION__DELAY); + } + for (VarRef port : connection.getRightPorts()) { + // If the variable is not a port, then there is some other + // error. Avoid a class cast exception. + if (port.getVariable() instanceof Port) { + if (type == null) { + type = ((Port) port.getVariable()).getType(); + } else { + if (!sameType(type, type = ((Port) port.getVariable()).getType())) { + error("Types do not match.", Literals.CONNECTION__RIGHT_PORTS); } + } } + } } - @Check(CheckType.FAST) - public void checkDeadline(Deadline deadline) { - if (isCBasedTarget() && - this.info.overflowingDeadlines.contains(deadline)) { - error( - "Deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.DEADLINE__DELAY); - } - checkExpressionIsTime(deadline.getDelay(), Literals.DEADLINE__DELAY); + // Check whether the total width of the left side of the connection + // matches the total width of the right side. This cannot be determined + // here if the width is not given as a constant. In that case, it is up + // to the code generator to check it. + int leftWidth = 0; + for (VarRef port : connection.getLeftPorts()) { + int width = inferPortWidth(port, null, null); // null args imply incomplete check. + if (width < 0 || leftWidth < 0) { + // Cannot determine the width of the left ports. + leftWidth = -1; + } else { + leftWidth += width; + } + } + int rightWidth = 0; + for (VarRef port : connection.getRightPorts()) { + int width = inferPortWidth(port, null, null); // null args imply incomplete check. + if (width < 0 || rightWidth < 0) { + // Cannot determine the width of the left ports. + rightWidth = -1; + } else { + rightWidth += width; + } } - @Check(CheckType.FAST) - public void checkHost(Host host) { - String addr = host.getAddr(); - String user = host.getUser(); - if (user != null && !user.matches(USERNAME_REGEX)) { - warning( - "Invalid user name.", - Literals.HOST__USER - ); - } - if (host instanceof IPV4Host && !addr.matches(IPV4_REGEX)) { - warning( - "Invalid IP address.", - Literals.HOST__ADDR - ); - } else if (host instanceof IPV6Host && !addr.matches(IPV6_REGEX)) { - warning( - "Invalid IP address.", - Literals.HOST__ADDR - ); - } else if (host instanceof NamedHost && !addr.matches(HOST_OR_FQN_REGEX)) { - warning( - "Invalid host name or fully qualified domain name.", - Literals.HOST__ADDR - ); - } + if (leftWidth != -1 && rightWidth != -1 && leftWidth != rightWidth) { + if (connection.isIterated()) { + if (leftWidth == 0 || rightWidth % leftWidth != 0) { + // FIXME: The second argument should be Literals.CONNECTION, but + // stupidly, xtext will not accept that. There seems to be no way to + // report an error for the whole connection statement. + warning( + String.format("Left width %s does not divide right width %s", leftWidth, rightWidth), + Literals.CONNECTION__LEFT_PORTS); + } + } else { + // FIXME: The second argument should be Literals.CONNECTION, but + // stupidly, xtext will not accept that. There seems to be no way to + // report an error for the whole connection statement. + warning( + String.format("Left width %s does not match right width %s", leftWidth, rightWidth), + Literals.CONNECTION__LEFT_PORTS); + } } - @Check - public void checkImport(Import imp) { - if (toDefinition(imp.getReactorClasses().get(0)).eResource().getErrors().size() > 0) { - error("Error loading resource.", Literals.IMPORT__IMPORT_URI); // FIXME: print specifics. - return; + Reactor reactor = ASTUtils.getEnclosingReactor(connection); + + // Make sure the right port is not already an effect of a reaction. + for (Reaction reaction : ASTUtils.allReactions(reactor)) { + for (VarRef effect : reaction.getEffects()) { + for (VarRef rightPort : connection.getRightPorts()) { + if (rightPort.getVariable().equals(effect.getVariable()) + && // Refers to the same variable + rightPort.getContainer() == effect.getContainer() + && // Refers to the same instance + (reaction.eContainer() instanceof Reactor + || // Either is not part of a mode + connection.eContainer() instanceof Reactor + || connection.eContainer() + == reaction.eContainer() // Or they are in the same mode + )) { + error( + "Cannot connect: Port named '" + + effect.getVariable().getName() + + "' is already effect of a reaction.", + Literals.CONNECTION__RIGHT_PORTS); + } } + } + } - // FIXME: report error if resource cannot be resolved. - for (ImportedReactor reactor : imp.getReactorClasses()) { - if (!isUnused(reactor)) { - return; + // Check that the right port does not already have some other + // upstream connection. + for (Connection c : reactor.getConnections()) { + if (c != connection) { + for (VarRef thisRightPort : connection.getRightPorts()) { + for (VarRef thatRightPort : c.getRightPorts()) { + if (thisRightPort.getVariable().equals(thatRightPort.getVariable()) + && // Refers to the same variable + thisRightPort.getContainer() == thatRightPort.getContainer() + && // Refers to the same instance + (connection.eContainer() instanceof Reactor + || // Or either of the connections in not part of a mode + c.eContainer() instanceof Reactor + || connection.eContainer() == c.eContainer() // Or they are in the same mode + )) { + error( + "Cannot connect: Port named '" + + thisRightPort.getVariable().getName() + + "' may only appear once on the right side of a connection.", + Literals.CONNECTION__RIGHT_PORTS); } + } } - warning("Unused import.", Literals.IMPORT__IMPORT_URI); + } } - @Check - public void checkImportedReactor(ImportedReactor reactor) { - if (isUnused(reactor)) { - warning("Unused reactor class.", - Literals.IMPORTED_REACTOR__REACTOR_CLASS); - } - - if (info.instantiationGraph.hasCycles()) { - Set cycleSet = new HashSet<>(); - for (Set cycle : info.instantiationGraph.getCycles()) { - cycleSet.addAll(cycle); - } - if (dependsOnCycle(toDefinition(reactor), cycleSet, new HashSet<>())) { - error("Imported reactor '" + toDefinition(reactor).getName() + - "' has cyclic instantiation in it.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); - } - } + // Check the after delay + if (connection.getDelay() != null) { + final var delay = connection.getDelay(); + if (delay instanceof ParameterReference + || delay instanceof Time + || delay instanceof Literal) { + checkExpressionIsTime(delay, Literals.CONNECTION__DELAY); + } else { + error( + "After delays can only be given by time literals or parameters.", + Literals.CONNECTION__DELAY); + } } + } + + @Check(CheckType.FAST) + public void checkDeadline(Deadline deadline) { + if (isCBasedTarget() && this.info.overflowingDeadlines.contains(deadline)) { + error( + "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.DEADLINE__DELAY); + } + checkExpressionIsTime(deadline.getDelay(), Literals.DEADLINE__DELAY); + } + + @Check(CheckType.FAST) + public void checkHost(Host host) { + String addr = host.getAddr(); + String user = host.getUser(); + if (user != null && !user.matches(USERNAME_REGEX)) { + warning("Invalid user name.", Literals.HOST__USER); + } + if (host instanceof IPV4Host && !addr.matches(IPV4_REGEX)) { + warning("Invalid IP address.", Literals.HOST__ADDR); + } else if (host instanceof IPV6Host && !addr.matches(IPV6_REGEX)) { + warning("Invalid IP address.", Literals.HOST__ADDR); + } else if (host instanceof NamedHost && !addr.matches(HOST_OR_FQN_REGEX)) { + warning("Invalid host name or fully qualified domain name.", Literals.HOST__ADDR); + } + } - @Check(CheckType.FAST) - public void checkInput(Input input) { - Reactor parent = (Reactor)input.eContainer(); - if (parent.isMain() || parent.isFederated()) { - error("Main reactor cannot have inputs.", Literals.VARIABLE__NAME); - } - checkName(input.getName(), Literals.VARIABLE__NAME); - if (target.requiresTypes) { - if (input.getType() == null) { - error("Input must have a type.", Literals.TYPED_VARIABLE__TYPE); - } - } + @Check + public void checkImport(Import imp) { + if (toDefinition(imp.getReactorClasses().get(0)).eResource().getErrors().size() > 0) { + error("Error loading resource.", Literals.IMPORT__IMPORT_URI); // FIXME: print specifics. + return; + } - // mutable has no meaning in C++ - if (input.isMutable() && this.target == Target.CPP) { - warning( - "The mutable qualifier has no meaning for the C++ target and should be removed. " + - "In C++, any value can be made mutable by calling get_mutable_copy().", - Literals.INPUT__MUTABLE - ); - } + // FIXME: report error if resource cannot be resolved. + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (!isUnused(reactor)) { + return; + } + } + warning("Unused import.", Literals.IMPORT__IMPORT_URI); + } - // Variable width multiports are not supported (yet?). - if (input.getWidthSpec() != null && input.getWidthSpec().isOfVariableLength()) { - error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); - } + @Check + public void checkImportedReactor(ImportedReactor reactor) { + if (isUnused(reactor)) { + warning("Unused reactor class.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); } - @Check(CheckType.FAST) - public void checkInstantiation(Instantiation instantiation) { - checkName(instantiation.getName(), Literals.INSTANTIATION__NAME); - Reactor reactor = toDefinition(instantiation.getReactorClass()); - if (reactor.isMain() || reactor.isFederated()) { - error( - "Cannot instantiate a main (or federated) reactor: " + - instantiation.getReactorClass().getName(), - Literals.INSTANTIATION__REACTOR_CLASS - ); - } + if (info.instantiationGraph.hasCycles()) { + Set cycleSet = new HashSet<>(); + for (Set cycle : info.instantiationGraph.getCycles()) { + cycleSet.addAll(cycle); + } + if (dependsOnCycle(toDefinition(reactor), cycleSet, new HashSet<>())) { + error( + "Imported reactor '" + + toDefinition(reactor).getName() + + "' has cyclic instantiation in it.", + Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } + } + } - // Report error if this instantiation is part of a cycle. - // FIXME: improve error message. - // FIXME: Also report if there exists a cycle within. - if (this.info.instantiationGraph.getCycles().size() > 0) { - for (Set cycle : this.info.instantiationGraph.getCycles()) { - Reactor container = (Reactor) instantiation.eContainer(); - if (cycle.contains(container) && cycle.contains(reactor)) { - List names = new ArrayList<>(); - for (Reactor r : cycle) { - names.add(r.getName()); - } - - error( - "Instantiation is part of a cycle: " + String.join(", ", names) + ".", - Literals.INSTANTIATION__REACTOR_CLASS - ); - } - } - } - // Variable width multiports are not supported (yet?). - if (instantiation.getWidthSpec() != null - && instantiation.getWidthSpec().isOfVariableLength() - ) { - if (isCBasedTarget()) { - warning("Variable-width banks are for internal use only.", - Literals.INSTANTIATION__WIDTH_SPEC - ); - } else { - error("Variable-width banks are not supported.", - Literals.INSTANTIATION__WIDTH_SPEC - ); - } - } + @Check(CheckType.FAST) + public void checkInput(Input input) { + Reactor parent = (Reactor) input.eContainer(); + if (parent.isMain() || parent.isFederated()) { + error("Main reactor cannot have inputs.", Literals.VARIABLE__NAME); + } + checkName(input.getName(), Literals.VARIABLE__NAME); + if (target.requiresTypes) { + if (input.getType() == null) { + error("Input must have a type.", Literals.TYPED_VARIABLE__TYPE); + } } - /** Check target parameters, which are key-value pairs. */ - @Check(CheckType.FAST) - public void checkKeyValuePair(KeyValuePair param) { - // Check only if the container's container is a Target. - if (param.eContainer().eContainer() instanceof TargetDecl) { - TargetProperty prop = TargetProperty.forName(param.getName()); - - // Make sure the key is valid. - if (prop == null) { - String options = TargetProperty.getOptions().stream() - .map(p -> p.description).sorted() - .collect(Collectors.joining(", ")); - warning( - "Unrecognized target parameter: " + param.getName() + - ". Recognized parameters are: " + options, - Literals.KEY_VALUE_PAIR__NAME); - } else { - // Check whether the property is supported by the target. - if (!prop.supportedBy.contains(this.target)) { - warning( - "The target parameter: " + param.getName() + - " is not supported by the " + this.target + - " target and will thus be ignored.", - Literals.KEY_VALUE_PAIR__NAME); - } + // mutable has no meaning in C++ + if (input.isMutable() && this.target == Target.CPP) { + warning( + "The mutable qualifier has no meaning for the C++ target and should be removed. " + + "In C++, any value can be made mutable by calling get_mutable_copy().", + Literals.INPUT__MUTABLE); + } - // Report problem with the assigned value. - prop.type.check(param.getValue(), param.getName(), this); - } + // Variable width multiports are not supported (yet?). + if (input.getWidthSpec() != null && input.getWidthSpec().isOfVariableLength()) { + error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); + } + } + + @Check(CheckType.FAST) + public void checkInstantiation(Instantiation instantiation) { + checkName(instantiation.getName(), Literals.INSTANTIATION__NAME); + Reactor reactor = toDefinition(instantiation.getReactorClass()); + if (reactor.isMain() || reactor.isFederated()) { + error( + "Cannot instantiate a main (or federated) reactor: " + + instantiation.getReactorClass().getName(), + Literals.INSTANTIATION__REACTOR_CLASS); + } - for (String it : targetPropertyErrors) { - error(it, Literals.KEY_VALUE_PAIR__VALUE); - } - targetPropertyErrors.clear(); + // Report error if this instantiation is part of a cycle. + // FIXME: improve error message. + // FIXME: Also report if there exists a cycle within. + if (this.info.instantiationGraph.getCycles().size() > 0) { + for (Set cycle : this.info.instantiationGraph.getCycles()) { + Reactor container = (Reactor) instantiation.eContainer(); + if (cycle.contains(container) && cycle.contains(reactor)) { + List names = new ArrayList<>(); + for (Reactor r : cycle) { + names.add(r.getName()); + } - for (String it : targetPropertyWarnings) { - error(it, Literals.KEY_VALUE_PAIR__VALUE); - } - targetPropertyWarnings.clear(); + error( + "Instantiation is part of a cycle: " + String.join(", ", names) + ".", + Literals.INSTANTIATION__REACTOR_CLASS); } + } } - - @Check(CheckType.FAST) - public void checkModel(Model model) { - // Since we're doing a fast check, we only want to update - // if the model info hasn't been initialized yet. If it has, - // we use the old information and update it during a normal - // check (see below). - if (!info.updated) { - info.update(model, errorReporter); - } + // Variable width multiports are not supported (yet?). + if (instantiation.getWidthSpec() != null && instantiation.getWidthSpec().isOfVariableLength()) { + if (isCBasedTarget()) { + warning( + "Variable-width banks are for internal use only.", Literals.INSTANTIATION__WIDTH_SPEC); + } else { + error("Variable-width banks are not supported.", Literals.INSTANTIATION__WIDTH_SPEC); + } + } + } + + /** Check target parameters, which are key-value pairs. */ + @Check(CheckType.FAST) + public void checkKeyValuePair(KeyValuePair param) { + // Check only if the container's container is a Target. + if (param.eContainer().eContainer() instanceof TargetDecl) { + TargetProperty prop = TargetProperty.forName(param.getName()); + + // Make sure the key is valid. + if (prop == null) { + String options = + TargetProperty.getOptions().stream() + .map(p -> p.description) + .sorted() + .collect(Collectors.joining(", ")); + warning( + "Unrecognized target parameter: " + + param.getName() + + ". Recognized parameters are: " + + options, + Literals.KEY_VALUE_PAIR__NAME); + } else { + // Check whether the property is supported by the target. + if (!prop.supportedBy.contains(this.target)) { + warning( + "The target parameter: " + + param.getName() + + " is not supported by the " + + this.target + + " target and will thus be ignored.", + Literals.KEY_VALUE_PAIR__NAME); + } + + // Report problem with the assigned value. + prop.type.check(param.getValue(), param.getName(), this); + } + + for (String it : targetPropertyErrors) { + error(it, Literals.KEY_VALUE_PAIR__VALUE); + } + targetPropertyErrors.clear(); + + for (String it : targetPropertyWarnings) { + error(it, Literals.KEY_VALUE_PAIR__VALUE); + } + targetPropertyWarnings.clear(); + } + } + + @Check(CheckType.FAST) + public void checkModel(Model model) { + // Since we're doing a fast check, we only want to update + // if the model info hasn't been initialized yet. If it has, + // we use the old information and update it during a normal + // check (see below). + if (!info.updated) { + info.update(model, errorReporter); + } + } + + @Check(CheckType.NORMAL) + public void updateModelInfo(Model model) { + info.update(model, errorReporter); + } + + @Check(CheckType.FAST) + public void checkOutput(Output output) { + Reactor parent = (Reactor) output.eContainer(); + if (parent.isMain() || parent.isFederated()) { + error("Main reactor cannot have outputs.", Literals.VARIABLE__NAME); + } + checkName(output.getName(), Literals.VARIABLE__NAME); + if (this.target.requiresTypes) { + if (output.getType() == null) { + error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE); + } } - @Check(CheckType.NORMAL) - public void updateModelInfo(Model model) { - info.update(model, errorReporter); + // Variable width multiports are not supported (yet?). + if (output.getWidthSpec() != null && output.getWidthSpec().isOfVariableLength()) { + error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); } + } - @Check(CheckType.FAST) - public void checkOutput(Output output) { - Reactor parent = (Reactor)output.eContainer(); - if (parent.isMain() || parent.isFederated()) { - error("Main reactor cannot have outputs.", Literals.VARIABLE__NAME); - } - checkName(output.getName(), Literals.VARIABLE__NAME); - if (this.target.requiresTypes) { - if (output.getType() == null) { - error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE); - } - } + @Check(CheckType.FAST) + public void checkParameter(Parameter param) { + checkName(param.getName(), Literals.PARAMETER__NAME); - // Variable width multiports are not supported (yet?). - if (output.getWidthSpec() != null && output.getWidthSpec().isOfVariableLength()) { - error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); - } + if (param.getInit() == null) { + // todo make initialization non-mandatory + // https://github.com/lf-lang/lingua-franca/issues/623 + error("Parameter must have a default value.", Literals.PARAMETER__INIT); + return; } - @Check(CheckType.FAST) - public void checkParameter(Parameter param) { - checkName(param.getName(), Literals.PARAMETER__NAME); + if (this.target.requiresTypes) { + // Report missing target type. param.inferredType.undefine + if (ASTUtils.getInferredType(param).isUndefined()) { + error("Type declaration missing.", Literals.PARAMETER__TYPE); + } + } - if (param.getInit() == null) { - // todo make initialization non-mandatory - // https://github.com/lf-lang/lingua-franca/issues/623 - error("Parameter must have a default value.", Literals.PARAMETER__INIT); - return; - } + if (param.getType() != null) { + typeCheck(param.getInit(), ASTUtils.getInferredType(param), Literals.PARAMETER__INIT); + } - if (this.target.requiresTypes) { - // Report missing target type. param.inferredType.undefine - if (ASTUtils.getInferredType(param).isUndefined()) { - error("Type declaration missing.", Literals.PARAMETER__TYPE); - } + if (param.getInit() != null) { + for (Expression expr : param.getInit().getExprs()) { + if (expr instanceof ParameterReference) { + // Initialization using parameters is forbidden. + error("Parameter cannot be initialized using parameter.", Literals.PARAMETER__INIT); } + } + } - if (param.getType() != null) { - typeCheck(param.getInit(), ASTUtils.getInferredType(param), Literals.PARAMETER__INIT); - } + if (this.target == Target.CPP) { + EObject container = param.eContainer(); + Reactor reactor = (Reactor) container; + if (reactor.isMain()) { + // we need to check for the cli parameters that are always taken + List cliParams = List.of("t", "threads", "o", "timeout", "f", "fast", "help"); + if (cliParams.contains(param.getName())) { + error( + "Parameter '" + + param.getName() + + "' is already in use as command line argument by Lingua Franca,", + Literals.PARAMETER__NAME); + } + } + } - if (param.getInit() != null) { - for (Expression expr : param.getInit().getExprs()) { - if (expr instanceof ParameterReference) { - // Initialization using parameters is forbidden. - error("Parameter cannot be initialized using parameter.", - Literals.PARAMETER__INIT); - } - } + if (isCBasedTarget() && this.info.overflowingParameters.contains(param)) { + error( + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + + " nanoseconds.", + Literals.PARAMETER__INIT); + } + } + + @Check(CheckType.FAST) + public void checkPreamble(Preamble preamble) { + if (this.target == Target.CPP) { + if (preamble.getVisibility() == Visibility.NONE) { + error( + "Preambles for the C++ target need a visibility qualifier (private or public)!", + Literals.PREAMBLE__VISIBILITY); + } else if (preamble.getVisibility() == Visibility.PRIVATE) { + EObject container = preamble.eContainer(); + if (container != null && container instanceof Reactor) { + Reactor reactor = (Reactor) container; + if (isGeneric(reactor)) { + warning( + "Private preambles in generic reactors are not truly private. " + + "Since the generated code is placed in a *_impl.hh file, it will " + + "be visible on the public interface. Consider using a public " + + "preamble within the reactor or a private preamble on file scope.", + Literals.PREAMBLE__VISIBILITY); + } } + } + } else if (preamble.getVisibility() != Visibility.NONE) { + warning( + String.format( + "The %s qualifier has no meaning for the %s target. It should be removed.", + preamble.getVisibility(), this.target.name()), + Literals.PREAMBLE__VISIBILITY); + } + } - if (this.target == Target.CPP) { - EObject container = param.eContainer(); - Reactor reactor = (Reactor) container; - if (reactor.isMain()) { - // we need to check for the cli parameters that are always taken - List cliParams = List.of("t", "threads", "o", "timeout", "f", "fast", "help"); - if (cliParams.contains(param.getName())) { - error("Parameter '" + param.getName() - + "' is already in use as command line argument by Lingua Franca,", - Literals.PARAMETER__NAME); - } - } - } + @Check(CheckType.FAST) + public void checkReaction(Reaction reaction) { - if (isCBasedTarget() && - this.info.overflowingParameters.contains(param)) { + if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { + warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS); + } + HashSet triggers = new HashSet<>(); + // Make sure input triggers have no container and output sources do. + for (TriggerRef trigger : reaction.getTriggers()) { + if (trigger instanceof VarRef) { + VarRef triggerVarRef = (VarRef) trigger; + triggers.add(triggerVarRef.getVariable()); + if (triggerVarRef instanceof Input) { + if (triggerVarRef.getContainer() != null) { error( - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.PARAMETER__INIT); + String.format( + "Cannot have an input of a contained reactor as a trigger: %s.%s", + triggerVarRef.getContainer().getName(), triggerVarRef.getVariable().getName()), + Literals.REACTION__TRIGGERS); + } + } else if (triggerVarRef.getVariable() instanceof Output) { + if (triggerVarRef.getContainer() == null) { + error( + String.format( + "Cannot have an output of this reactor as a trigger: %s", + triggerVarRef.getVariable().getName()), + Literals.REACTION__TRIGGERS); + } } - + } } - @Check(CheckType.FAST) - public void checkPreamble(Preamble preamble) { - if (this.target == Target.CPP) { - if (preamble.getVisibility() == Visibility.NONE) { - error( - "Preambles for the C++ target need a visibility qualifier (private or public)!", - Literals.PREAMBLE__VISIBILITY - ); - } else if (preamble.getVisibility() == Visibility.PRIVATE) { - EObject container = preamble.eContainer(); - if (container != null && container instanceof Reactor) { - Reactor reactor = (Reactor) container; - if (isGeneric(reactor)) { - warning( - "Private preambles in generic reactors are not truly private. " + - "Since the generated code is placed in a *_impl.hh file, it will " + - "be visible on the public interface. Consider using a public " + - "preamble within the reactor or a private preamble on file scope.", - Literals.PREAMBLE__VISIBILITY); - } - } - } - } else if (preamble.getVisibility() != Visibility.NONE) { - warning( - String.format("The %s qualifier has no meaning for the %s target. It should be removed.", - preamble.getVisibility(), this.target.name()), - Literals.PREAMBLE__VISIBILITY - ); - } + // Make sure input sources have no container and output sources do. + // Also check that a source is not already listed as a trigger. + for (VarRef source : reaction.getSources()) { + if (triggers.contains(source.getVariable())) { + error( + String.format( + "Source is already listed as a trigger: %s", source.getVariable().getName()), + Literals.REACTION__SOURCES); + } + if (source.getVariable() instanceof Input) { + if (source.getContainer() != null) { + error( + String.format( + "Cannot have an input of a contained reactor as a source: %s.%s", + source.getContainer().getName(), source.getVariable().getName()), + Literals.REACTION__SOURCES); + } + } else if (source.getVariable() instanceof Output) { + if (source.getContainer() == null) { + error( + String.format( + "Cannot have an output of this reactor as a source: %s", + source.getVariable().getName()), + Literals.REACTION__SOURCES); + } + } } - @Check(CheckType.FAST) - public void checkReaction(Reaction reaction) { - - if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { - warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS); - } - HashSet triggers = new HashSet<>(); - // Make sure input triggers have no container and output sources do. - for (TriggerRef trigger : reaction.getTriggers()) { - if (trigger instanceof VarRef) { - VarRef triggerVarRef = (VarRef) trigger; - triggers.add(triggerVarRef.getVariable()); - if (triggerVarRef instanceof Input) { - if (triggerVarRef.getContainer() != null) { - error(String.format("Cannot have an input of a contained reactor as a trigger: %s.%s", triggerVarRef.getContainer().getName(), triggerVarRef.getVariable().getName()), - Literals.REACTION__TRIGGERS); - } - } else if (triggerVarRef.getVariable() instanceof Output) { - if (triggerVarRef.getContainer() == null) { - error(String.format("Cannot have an output of this reactor as a trigger: %s", triggerVarRef.getVariable().getName()), - Literals.REACTION__TRIGGERS); - } - } - } - } - - // Make sure input sources have no container and output sources do. - // Also check that a source is not already listed as a trigger. - for (VarRef source : reaction.getSources()) { - if (triggers.contains(source.getVariable())) { - error(String.format("Source is already listed as a trigger: %s", source.getVariable().getName()), - Literals.REACTION__SOURCES); - } - if (source.getVariable() instanceof Input) { - if (source.getContainer() != null) { - error(String.format("Cannot have an input of a contained reactor as a source: %s.%s", source.getContainer().getName(), source.getVariable().getName()), - Literals.REACTION__SOURCES); - } - } else if (source.getVariable() instanceof Output) { - if (source.getContainer() == null) { - error(String.format("Cannot have an output of this reactor as a source: %s", source.getVariable().getName()), - Literals.REACTION__SOURCES); - } - } - } + // Make sure output effects have no container and input effects do. + for (VarRef effect : reaction.getEffects()) { + if (effect.getVariable() instanceof Input) { + if (effect.getContainer() == null) { + error( + String.format( + "Cannot have an input of this reactor as an effect: %s", + effect.getVariable().getName()), + Literals.REACTION__EFFECTS); + } + } else if (effect.getVariable() instanceof Output) { + if (effect.getContainer() != null) { + error( + String.format( + "Cannot have an output of a contained reactor as an effect: %s.%s", + effect.getContainer().getName(), effect.getVariable().getName()), + Literals.REACTION__EFFECTS); + } + } + } - // Make sure output effects have no container and input effects do. - for (VarRef effect : reaction.getEffects()) { - if (effect.getVariable() instanceof Input) { - if (effect.getContainer() == null) { - error(String.format("Cannot have an input of this reactor as an effect: %s", effect.getVariable().getName()), - Literals.REACTION__EFFECTS); - } - } else if (effect.getVariable() instanceof Output) { - if (effect.getContainer() != null) { - error(String.format("Cannot have an output of a contained reactor as an effect: %s.%s", effect.getContainer().getName(), effect.getVariable().getName()), - Literals.REACTION__EFFECTS); - } - } + // // Report error if this reaction is part of a cycle. + Set> cycles = this.info.topologyCycles(); + Reactor reactor = ASTUtils.getEnclosingReactor(reaction); + boolean reactionInCycle = false; + for (NamedInstance it : cycles) { + if (it.getDefinition().equals(reaction)) { + reactionInCycle = true; + break; + } + } + if (reactionInCycle) { + // Report involved triggers. + List trigs = new ArrayList<>(); + for (TriggerRef t : reaction.getTriggers()) { + if (!(t instanceof VarRef)) { + continue; + } + VarRef tVarRef = (VarRef) t; + boolean triggerExistsInCycle = false; + for (NamedInstance c : cycles) { + if (c.getDefinition().equals(tVarRef.getVariable())) { + triggerExistsInCycle = true; + break; + } } - - // // Report error if this reaction is part of a cycle. - Set> cycles = this.info.topologyCycles(); - Reactor reactor = ASTUtils.getEnclosingReactor(reaction); - boolean reactionInCycle = false; - for (NamedInstance it : cycles) { - if (it.getDefinition().equals(reaction)) { - reactionInCycle = true; - break; - } + if (triggerExistsInCycle) { + trigs.add(toOriginalText(tVarRef)); + } + } + if (trigs.size() > 0) { + error( + String.format( + "Reaction triggers involved in cyclic dependency in reactor %s: %s.", + reactor.getName(), String.join(", ", trigs)), + Literals.REACTION__TRIGGERS); + } + + // Report involved sources. + List sources = new ArrayList<>(); + for (VarRef t : reaction.getSources()) { + boolean sourceExistInCycle = false; + for (NamedInstance c : cycles) { + if (c.getDefinition().equals(t.getVariable())) { + sourceExistInCycle = true; + break; + } } - if (reactionInCycle) { - // Report involved triggers. - List trigs = new ArrayList<>(); - for (TriggerRef t : reaction.getTriggers()) { - if (!(t instanceof VarRef)) { - continue; - } - VarRef tVarRef = (VarRef) t; - boolean triggerExistsInCycle = false; - for (NamedInstance c : cycles) { - if (c.getDefinition().equals(tVarRef.getVariable())) { - triggerExistsInCycle = true; - break; - } - } - if (triggerExistsInCycle) { - trigs.add(toOriginalText(tVarRef)); - } - } - if (trigs.size() > 0) { - error(String.format("Reaction triggers involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", trigs)), - Literals.REACTION__TRIGGERS); - } - - // Report involved sources. - List sources = new ArrayList<>(); - for (VarRef t : reaction.getSources()) { - boolean sourceExistInCycle = false; - for (NamedInstance c : cycles) { - if (c.getDefinition().equals(t.getVariable())) { - sourceExistInCycle = true; - break; - } - } - if (sourceExistInCycle) { - sources.add(toOriginalText(t)); - } - } - if (sources.size() > 0) { - error(String.format("Reaction sources involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", sources)), - Literals.REACTION__SOURCES); - } - - // Report involved effects. - List effects = new ArrayList<>(); - for (VarRef t : reaction.getEffects()) { - boolean effectExistInCycle = false; - for (NamedInstance c : cycles) { - if (c.getDefinition().equals(t.getVariable())) { - effectExistInCycle = true; - break; - } - } - if (effectExistInCycle) { - effects.add(toOriginalText(t)); - } - } - if (effects.size() > 0) { - error(String.format("Reaction effects involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", effects)), - Literals.REACTION__EFFECTS); - } - - if (trigs.size() + sources.size() == 0) { - error( - String.format("Cyclic dependency due to preceding reaction. Consider reordering reactions within reactor %s to avoid causality loop.", reactor.getName()), - reaction.eContainer(), - Literals.REACTOR__REACTIONS, - reactor.getReactions().indexOf(reaction) - ); - } else if (effects.size() == 0) { - error( - String.format("Cyclic dependency due to succeeding reaction. Consider reordering reactions within reactor %s to avoid causality loop.", reactor.getName()), - reaction.eContainer(), - Literals.REACTOR__REACTIONS, - reactor.getReactions().indexOf(reaction) - ); - } - // Not reporting reactions that are part of cycle _only_ due to reaction ordering. - // Moving them won't help solve the problem. + if (sourceExistInCycle) { + sources.add(toOriginalText(t)); + } + } + if (sources.size() > 0) { + error( + String.format( + "Reaction sources involved in cyclic dependency in reactor %s: %s.", + reactor.getName(), String.join(", ", sources)), + Literals.REACTION__SOURCES); + } + + // Report involved effects. + List effects = new ArrayList<>(); + for (VarRef t : reaction.getEffects()) { + boolean effectExistInCycle = false; + for (NamedInstance c : cycles) { + if (c.getDefinition().equals(t.getVariable())) { + effectExistInCycle = true; + break; + } } + if (effectExistInCycle) { + effects.add(toOriginalText(t)); + } + } + if (effects.size() > 0) { + error( + String.format( + "Reaction effects involved in cyclic dependency in reactor %s: %s.", + reactor.getName(), String.join(", ", effects)), + Literals.REACTION__EFFECTS); + } + + if (trigs.size() + sources.size() == 0) { + error( + String.format( + "Cyclic dependency due to preceding reaction. Consider reordering reactions within" + + " reactor %s to avoid causality loop.", + reactor.getName()), + reaction.eContainer(), + Literals.REACTOR__REACTIONS, + reactor.getReactions().indexOf(reaction)); + } else if (effects.size() == 0) { + error( + String.format( + "Cyclic dependency due to succeeding reaction. Consider reordering reactions within" + + " reactor %s to avoid causality loop.", + reactor.getName()), + reaction.eContainer(), + Literals.REACTOR__REACTIONS, + reactor.getReactions().indexOf(reaction)); + } + // Not reporting reactions that are part of cycle _only_ due to reaction ordering. + // Moving them won't help solve the problem. + } // FIXME: improve error message. + } + + @Check(CheckType.FAST) + public void checkReactor(Reactor reactor) throws IOException { + Set superClasses = ASTUtils.superClasses(reactor); + if (superClasses == null) { + error( + "Problem with superclasses: Either they form a cycle or are not defined", + Literals.REACTOR_DECL__NAME); + // Continue checks, but without any superclasses. + superClasses = new LinkedHashSet<>(); } - - @Check(CheckType.FAST) - public void checkReactor(Reactor reactor) throws IOException { - Set superClasses = ASTUtils.superClasses(reactor); - if (superClasses == null) { - error( - "Problem with superclasses: Either they form a cycle or are not defined", - Literals.REACTOR_DECL__NAME - ); - // Continue checks, but without any superclasses. - superClasses = new LinkedHashSet<>(); - } - String name = FileUtil.nameWithoutExtension(reactor.eResource()); - if (reactor.getName() == null) { - if (!reactor.isFederated() && !reactor.isMain()) { - error( - "Reactor must be named.", - Literals.REACTOR_DECL__NAME - ); - // Prevent NPE in tests below. - return; - } - } - TreeIterator iter = reactor.eResource().getAllContents(); - if (reactor.isFederated() || reactor.isMain()) { - if(reactor.getName() != null && !reactor.getName().equals(name)) { - // Make sure that if the name is given, it matches the expected name. - error( - "Name of main reactor must match the file name (or be omitted).", - Literals.REACTOR_DECL__NAME - ); - } - // Do not allow multiple main/federated reactors. - int nMain = countMainOrFederated(iter); - if (nMain > 1) { - EAttribute attribute = Literals.REACTOR__MAIN; - if (reactor.isFederated()) { - attribute = Literals.REACTOR__FEDERATED; - } - error( - "Multiple definitions of main or federated reactor.", - attribute - ); - } - } else { - // Not federated or main. - int nMain = countMainOrFederated(iter); - if (nMain > 0 && reactor.getName().equals(name)) { - error( - "Name conflict with main reactor.", - Literals.REACTOR_DECL__NAME - ); - } - } - - // Check for illegal names. - checkName(reactor.getName(), Literals.REACTOR_DECL__NAME); - - // C++ reactors may not be called 'preamble' - if (this.target == Target.CPP && reactor.getName().equalsIgnoreCase("preamble")) { - error( - "Reactor cannot be named '" + reactor.getName() + "'", - Literals.REACTOR_DECL__NAME - ); - } - - if (reactor.getHost() != null) { - if (!reactor.isFederated()) { - error( - "Cannot assign a host to reactor '" + reactor.getName() + - "' because it is not federated.", - Literals.REACTOR__HOST - ); - } - } - - List variables = new ArrayList<>(); - variables.addAll(reactor.getInputs()); - variables.addAll(reactor.getOutputs()); - variables.addAll(reactor.getActions()); - variables.addAll(reactor.getTimers()); - - // Perform checks on super classes. - for (Reactor superClass : superClasses) { - HashSet conflicts = new HashSet<>(); - - // Detect input conflicts - checkConflict(superClass.getInputs(), reactor.getInputs(), variables, conflicts); - // Detect output conflicts - checkConflict(superClass.getOutputs(), reactor.getOutputs(), variables, conflicts); - // Detect output conflicts - checkConflict(superClass.getActions(), reactor.getActions(), variables, conflicts); - // Detect conflicts - for (Timer timer : superClass.getTimers()) { - List filteredVariables = new ArrayList<>(variables); - filteredVariables.removeIf(it -> reactor.getTimers().contains(it)); - if (hasNameConflict(timer, filteredVariables)) { - conflicts.add(timer); - } else { - variables.add(timer); - } - } - - // Report conflicts. - if (conflicts.size() > 0) { - List names = new ArrayList<>(); - for (Variable it : conflicts) { - names.add(it.getName()); - } - error( - String.format("Cannot extend %s due to the following conflicts: %s.", - superClass.getName(), String.join(",", names)), - Literals.REACTOR__SUPER_CLASSES - ); - } - } - + String name = FileUtil.nameWithoutExtension(reactor.eResource()); + if (reactor.getName() == null) { + if (!reactor.isFederated() && !reactor.isMain()) { + error("Reactor must be named.", Literals.REACTOR_DECL__NAME); + // Prevent NPE in tests below. + return; + } + } + TreeIterator iter = reactor.eResource().getAllContents(); + if (reactor.isFederated() || reactor.isMain()) { + if (reactor.getName() != null && !reactor.getName().equals(name)) { + // Make sure that if the name is given, it matches the expected name. + error( + "Name of main reactor must match the file name (or be omitted).", + Literals.REACTOR_DECL__NAME); + } + // Do not allow multiple main/federated reactors. + int nMain = countMainOrFederated(iter); + if (nMain > 1) { + EAttribute attribute = Literals.REACTOR__MAIN; if (reactor.isFederated()) { - FedValidator.validateFederatedReactor(reactor, this.errorReporter); - } + attribute = Literals.REACTOR__FEDERATED; + } + error("Multiple definitions of main or federated reactor.", attribute); + } + } else { + // Not federated or main. + int nMain = countMainOrFederated(iter); + if (nMain > 0 && reactor.getName().equals(name)) { + error("Name conflict with main reactor.", Literals.REACTOR_DECL__NAME); + } } - /** - * Check if the requested serialization is supported. - */ - @Check(CheckType.FAST) - public void checkSerializer(Serializer serializer) { - boolean isValidSerializer = false; - for (SupportedSerializers method : SupportedSerializers.values()) { - if (method.name().equalsIgnoreCase(serializer.getType())){ - isValidSerializer = true; - } - } + // Check for illegal names. + checkName(reactor.getName(), Literals.REACTOR_DECL__NAME); - if (!isValidSerializer) { - error( - "Serializer can be " + Arrays.asList(SupportedSerializers.values()), - Literals.SERIALIZER__TYPE - ); - } + // C++ reactors may not be called 'preamble' + if (this.target == Target.CPP && reactor.getName().equalsIgnoreCase("preamble")) { + error("Reactor cannot be named '" + reactor.getName() + "'", Literals.REACTOR_DECL__NAME); } - @Check(CheckType.FAST) - public void checkState(StateVar stateVar) { - checkName(stateVar.getName(), Literals.STATE_VAR__NAME); - if (stateVar.getInit() != null && stateVar.getInit().getExprs().size() != 0) { - typeCheck(stateVar.getInit(), ASTUtils.getInferredType(stateVar), Literals.STATE_VAR__INIT); - } + if (reactor.getHost() != null) { + if (!reactor.isFederated()) { + error( + "Cannot assign a host to reactor '" + + reactor.getName() + + "' because it is not federated.", + Literals.REACTOR__HOST); + } + } - if (this.target.requiresTypes && ASTUtils.getInferredType(stateVar).isUndefined()) { - // Report if a type is missing - error("State must have a type.", Literals.STATE_VAR__TYPE); - } + List variables = new ArrayList<>(); + variables.addAll(reactor.getInputs()); + variables.addAll(reactor.getOutputs()); + variables.addAll(reactor.getActions()); + variables.addAll(reactor.getTimers()); + + // Perform checks on super classes. + for (Reactor superClass : superClasses) { + HashSet conflicts = new HashSet<>(); + + // Detect input conflicts + checkConflict(superClass.getInputs(), reactor.getInputs(), variables, conflicts); + // Detect output conflicts + checkConflict(superClass.getOutputs(), reactor.getOutputs(), variables, conflicts); + // Detect output conflicts + checkConflict(superClass.getActions(), reactor.getActions(), variables, conflicts); + // Detect conflicts + for (Timer timer : superClass.getTimers()) { + List filteredVariables = new ArrayList<>(variables); + filteredVariables.removeIf(it -> reactor.getTimers().contains(it)); + if (hasNameConflict(timer, filteredVariables)) { + conflicts.add(timer); + } else { + variables.add(timer); + } + } + + // Report conflicts. + if (conflicts.size() > 0) { + List names = new ArrayList<>(); + for (Variable it : conflicts) { + names.add(it.getName()); + } + error( + String.format( + "Cannot extend %s due to the following conflicts: %s.", + superClass.getName(), String.join(",", names)), + Literals.REACTOR__SUPER_CLASSES); + } + } - if (isCBasedTarget() - && ASTUtils.isListInitializer(stateVar.getInit()) - && stateVar.getInit().getExprs().stream().anyMatch(it -> it instanceof ParameterReference)) { - // In C, if initialization is done with a list, elements cannot - // refer to parameters. - error("List items cannot refer to a parameter.", - Literals.STATE_VAR__INIT); - } + if (reactor.isFederated()) { + FedValidator.validateFederatedReactor(reactor, this.errorReporter); + } + } + + /** Check if the requested serialization is supported. */ + @Check(CheckType.FAST) + public void checkSerializer(Serializer serializer) { + boolean isValidSerializer = false; + for (SupportedSerializers method : SupportedSerializers.values()) { + if (method.name().equalsIgnoreCase(serializer.getType())) { + isValidSerializer = true; + } + } + if (!isValidSerializer) { + error( + "Serializer can be " + Arrays.asList(SupportedSerializers.values()), + Literals.SERIALIZER__TYPE); } + } - @Check(CheckType.FAST) - public void checkSTPOffset(STP stp) { - if (isCBasedTarget() && - this.info.overflowingSTP.contains(stp)) { - error( - "STP offset exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.STP__VALUE); - } + @Check(CheckType.FAST) + public void checkState(StateVar stateVar) { + checkName(stateVar.getName(), Literals.STATE_VAR__NAME); + if (stateVar.getInit() != null && stateVar.getInit().getExprs().size() != 0) { + typeCheck(stateVar.getInit(), ASTUtils.getInferredType(stateVar), Literals.STATE_VAR__INIT); } - @Check(CheckType.FAST) - public void checkTargetDecl(TargetDecl target) throws IOException { - Optional targetOpt = Target.forName(target.getName()); - if (targetOpt.isEmpty()) { - error("Unrecognized target: " + target.getName(), - Literals.TARGET_DECL__NAME); - } else { - this.target = targetOpt.get(); - } - String lfFileName = FileUtil.nameWithoutExtension(target.eResource()); - if (Character.isDigit(lfFileName.charAt(0))) { - errorReporter.reportError("LF file names must not start with a number"); - } + if (this.target.requiresTypes && ASTUtils.getInferredType(stateVar).isUndefined()) { + // Report if a type is missing + error("State must have a type.", Literals.STATE_VAR__TYPE); } - /** - * Check for consistency of the target properties, which are - * defined as KeyValuePairs. - * - * @param targetProperties The target properties defined - * in the current Lingua Franca program. - */ - @Check(CheckType.EXPENSIVE) - public void checkTargetProperties(KeyValuePairs targetProperties) { - validateFastTargetProperty(targetProperties); - validateClockSyncTargetProperties(targetProperties); - validateSchedulerTargetProperties(targetProperties); - validateRos2TargetProperties(targetProperties); - validateKeepalive(targetProperties); - } - - private KeyValuePair getKeyValuePair(KeyValuePairs targetProperties, TargetProperty property) { - List properties = targetProperties.getPairs().stream() + if (isCBasedTarget() + && ASTUtils.isListInitializer(stateVar.getInit()) + && stateVar.getInit().getExprs().stream() + .anyMatch(it -> it instanceof ParameterReference)) { + // In C, if initialization is done with a list, elements cannot + // refer to parameters. + error("List items cannot refer to a parameter.", Literals.STATE_VAR__INIT); + } + } + + @Check(CheckType.FAST) + public void checkSTPOffset(STP stp) { + if (isCBasedTarget() && this.info.overflowingSTP.contains(stp)) { + error( + "STP offset exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.STP__VALUE); + } + } + + @Check(CheckType.FAST) + public void checkTargetDecl(TargetDecl target) throws IOException { + Optional targetOpt = Target.forName(target.getName()); + if (targetOpt.isEmpty()) { + error("Unrecognized target: " + target.getName(), Literals.TARGET_DECL__NAME); + } else { + this.target = targetOpt.get(); + } + String lfFileName = FileUtil.nameWithoutExtension(target.eResource()); + if (Character.isDigit(lfFileName.charAt(0))) { + errorReporter.reportError("LF file names must not start with a number"); + } + } + + /** + * Check for consistency of the target properties, which are defined as KeyValuePairs. + * + * @param targetProperties The target properties defined in the current Lingua Franca program. + */ + @Check(CheckType.EXPENSIVE) + public void checkTargetProperties(KeyValuePairs targetProperties) { + validateFastTargetProperty(targetProperties); + validateClockSyncTargetProperties(targetProperties); + validateSchedulerTargetProperties(targetProperties); + validateRos2TargetProperties(targetProperties); + validateKeepalive(targetProperties); + } + + private KeyValuePair getKeyValuePair(KeyValuePairs targetProperties, TargetProperty property) { + List properties = + targetProperties.getPairs().stream() .filter(pair -> pair.getName().equals(property.description)) .toList(); - assert (properties.size() <= 1); - return properties.size() > 0 ? properties.get(0) : null; - } - private void validateFastTargetProperty(KeyValuePairs targetProperties) { - KeyValuePair fastTargetProperty = getKeyValuePair(targetProperties, TargetProperty.FAST); - - if (fastTargetProperty != null) { - // Check for federated - for (Reactor reactor : info.model.getReactors()) { - // Check to see if the program has a federated reactor - if (reactor.isFederated()) { - error( - "The fast target property is incompatible with federated programs.", - fastTargetProperty, - Literals.KEY_VALUE_PAIR__NAME - ); - break; - } - } - - // Check for physical actions - for (Reactor reactor : info.model.getReactors()) { - // Check to see if the program has a physical action in a reactor - for (Action action : reactor.getActions()) { - if (action.getOrigin().equals(ActionOrigin.PHYSICAL)) { - error( - "The fast target property is incompatible with physical actions.", - fastTargetProperty, - Literals.KEY_VALUE_PAIR__NAME - ); - break; - } - } - } - } - } + assert (properties.size() <= 1); + return properties.size() > 0 ? properties.get(0) : null; + } - private void validateClockSyncTargetProperties(KeyValuePairs targetProperties) { - KeyValuePair clockSyncTargetProperty = getKeyValuePair(targetProperties, TargetProperty.CLOCK_SYNC); + private void validateFastTargetProperty(KeyValuePairs targetProperties) { + KeyValuePair fastTargetProperty = getKeyValuePair(targetProperties, TargetProperty.FAST); - if (clockSyncTargetProperty != null) { - boolean federatedExists = false; - for (Reactor reactor : info.model.getReactors()) { - if (reactor.isFederated()) { - federatedExists = true; - } - } - if (!federatedExists) { - warning( - "The clock-sync target property is incompatible with non-federated programs.", - clockSyncTargetProperty, - Literals.KEY_VALUE_PAIR__NAME - ); - } + if (fastTargetProperty != null) { + // Check for federated + for (Reactor reactor : info.model.getReactors()) { + // Check to see if the program has a federated reactor + if (reactor.isFederated()) { + error( + "The fast target property is incompatible with federated programs.", + fastTargetProperty, + Literals.KEY_VALUE_PAIR__NAME); + break; + } + } + + // Check for physical actions + for (Reactor reactor : info.model.getReactors()) { + // Check to see if the program has a physical action in a reactor + for (Action action : reactor.getActions()) { + if (action.getOrigin().equals(ActionOrigin.PHYSICAL)) { + error( + "The fast target property is incompatible with physical actions.", + fastTargetProperty, + Literals.KEY_VALUE_PAIR__NAME); + break; + } } + } } + } - private void validateSchedulerTargetProperties(KeyValuePairs targetProperties) { - KeyValuePair schedulerTargetProperty = getKeyValuePair(targetProperties, TargetProperty.SCHEDULER); - if (schedulerTargetProperty != null) { - String schedulerName = ASTUtils.elementToSingleString(schedulerTargetProperty.getValue()); - try { - if (!TargetProperty.SchedulerOption.valueOf(schedulerName) - .prioritizesDeadline()) { - // Check if a deadline is assigned to any reaction - // Filter reactors that contain at least one reaction that - // has a deadline handler. - if (info.model.getReactors().stream().anyMatch( - // Filter reactors that contain at least one reaction that - // has a deadline handler. - reactor -> ASTUtils.allReactions(reactor).stream().anyMatch( - reaction -> reaction.getDeadline() != null - )) - ) { - warning("This program contains deadlines, but the chosen " - + schedulerName - + " scheduler does not prioritize reaction execution " - + "based on deadlines. This might result in a sub-optimal " - + "scheduling.", schedulerTargetProperty, - Literals.KEY_VALUE_PAIR__VALUE); - } - } - } catch (IllegalArgumentException e) { - // the given scheduler is invalid, but this is already checked by - // checkTargetProperties - } - } - } + private void validateClockSyncTargetProperties(KeyValuePairs targetProperties) { + KeyValuePair clockSyncTargetProperty = + getKeyValuePair(targetProperties, TargetProperty.CLOCK_SYNC); - private void validateKeepalive(KeyValuePairs targetProperties) { - KeyValuePair keepalive = getKeyValuePair(targetProperties, TargetProperty.KEEPALIVE); - if (keepalive != null && target == Target.CPP) { - warning("The keepalive property is inferred automatically by the C++ " + - "runtime and the value given here is ignored", keepalive, Literals.KEY_VALUE_PAIR__NAME); - } + if (clockSyncTargetProperty != null) { + boolean federatedExists = false; + for (Reactor reactor : info.model.getReactors()) { + if (reactor.isFederated()) { + federatedExists = true; + } + } + if (!federatedExists) { + warning( + "The clock-sync target property is incompatible with non-federated programs.", + clockSyncTargetProperty, + Literals.KEY_VALUE_PAIR__NAME); + } } - - private void validateRos2TargetProperties(KeyValuePairs targetProperties) { - KeyValuePair ros2 = getKeyValuePair(targetProperties, TargetProperty.ROS2); - KeyValuePair ros2Dependencies = getKeyValuePair(targetProperties, TargetProperty.ROS2_DEPENDENCIES); - if (ros2Dependencies != null && (ros2 == null || !ASTUtils.toBoolean(ros2.getValue()))) { + } + + private void validateSchedulerTargetProperties(KeyValuePairs targetProperties) { + KeyValuePair schedulerTargetProperty = + getKeyValuePair(targetProperties, TargetProperty.SCHEDULER); + if (schedulerTargetProperty != null) { + String schedulerName = ASTUtils.elementToSingleString(schedulerTargetProperty.getValue()); + try { + if (!TargetProperty.SchedulerOption.valueOf(schedulerName).prioritizesDeadline()) { + // Check if a deadline is assigned to any reaction + // Filter reactors that contain at least one reaction that + // has a deadline handler. + if (info.model.getReactors().stream() + .anyMatch( + // Filter reactors that contain at least one reaction that + // has a deadline handler. + reactor -> + ASTUtils.allReactions(reactor).stream() + .anyMatch(reaction -> reaction.getDeadline() != null))) { warning( - "Ignoring ros2-dependencies as ros2 compilation is disabled", - ros2Dependencies, - Literals.KEY_VALUE_PAIR__NAME - ); + "This program contains deadlines, but the chosen " + + schedulerName + + " scheduler does not prioritize reaction execution " + + "based on deadlines. This might result in a sub-optimal " + + "scheduling.", + schedulerTargetProperty, + Literals.KEY_VALUE_PAIR__VALUE); + } } + } catch (IllegalArgumentException e) { + // the given scheduler is invalid, but this is already checked by + // checkTargetProperties + } } - - @Check(CheckType.FAST) - public void checkTimer(Timer timer) { - checkName(timer.getName(), Literals.VARIABLE__NAME); - checkExpressionIsTime(timer.getOffset(), Literals.TIMER__OFFSET); - checkExpressionIsTime(timer.getPeriod(), Literals.TIMER__PERIOD); + } + + private void validateKeepalive(KeyValuePairs targetProperties) { + KeyValuePair keepalive = getKeyValuePair(targetProperties, TargetProperty.KEEPALIVE); + if (keepalive != null && target == Target.CPP) { + warning( + "The keepalive property is inferred automatically by the C++ " + + "runtime and the value given here is ignored", + keepalive, + Literals.KEY_VALUE_PAIR__NAME); } - - @Check(CheckType.FAST) - public void checkType(Type type) { - // FIXME: disallow the use of generics in C - if (this.target == Target.Python) { - if (type != null) { - error( - "Types are not allowed in the Python target", - Literals.TYPE__ID - ); - } - } + } + + private void validateRos2TargetProperties(KeyValuePairs targetProperties) { + KeyValuePair ros2 = getKeyValuePair(targetProperties, TargetProperty.ROS2); + KeyValuePair ros2Dependencies = + getKeyValuePair(targetProperties, TargetProperty.ROS2_DEPENDENCIES); + if (ros2Dependencies != null && (ros2 == null || !ASTUtils.toBoolean(ros2.getValue()))) { + warning( + "Ignoring ros2-dependencies as ros2 compilation is disabled", + ros2Dependencies, + Literals.KEY_VALUE_PAIR__NAME); } - - @Check(CheckType.FAST) - public void checkVarRef(VarRef varRef) { - // check correct usage of interleaved - if (varRef.isInterleaved()) { - var supportedTargets = List.of(Target.CPP, Target.Python, Target.Rust); - if (!supportedTargets.contains(this.target) && !isCBasedTarget()) { - error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED); - } - if (!(varRef.eContainer() instanceof Connection)) { - error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED); - } - - if (varRef.getVariable() instanceof Port) { - // This test only works correctly if the variable is actually a port. If it is not a port, other - // validator rules will produce error messages. - if (varRef.getContainer() == null || varRef.getContainer().getWidthSpec() == null || - ((Port) varRef.getVariable()).getWidthSpec() == null - ) { - error("interleaved can only be used for multiports contained within banks.", Literals.VAR_REF__INTERLEAVED); - } - } - } + } + + @Check(CheckType.FAST) + public void checkTimer(Timer timer) { + checkName(timer.getName(), Literals.VARIABLE__NAME); + checkExpressionIsTime(timer.getOffset(), Literals.TIMER__OFFSET); + checkExpressionIsTime(timer.getPeriod(), Literals.TIMER__PERIOD); + } + + @Check(CheckType.FAST) + public void checkType(Type type) { + // FIXME: disallow the use of generics in C + if (this.target == Target.Python) { + if (type != null) { + error("Types are not allowed in the Python target", Literals.TYPE__ID); + } } - - /** - * Check whether an attribute is supported - * and the validity of the attribute. - * - * @param attr The attribute being checked - */ - @Check(CheckType.FAST) - public void checkAttributes(Attribute attr) { - String name = attr.getAttrName().toString(); - AttributeSpec spec = AttributeSpec.ATTRIBUTE_SPECS_BY_NAME.get(name); - if (spec == null) { - error("Unknown attribute.", Literals.ATTRIBUTE__ATTR_NAME); - return; - } - // Check the validity of the attribute. - spec.check(this, attr); + } + + @Check(CheckType.FAST) + public void checkVarRef(VarRef varRef) { + // check correct usage of interleaved + if (varRef.isInterleaved()) { + var supportedTargets = List.of(Target.CPP, Target.Python, Target.Rust); + if (!supportedTargets.contains(this.target) && !isCBasedTarget()) { + error( + "This target does not support interleaved port references.", + Literals.VAR_REF__INTERLEAVED); + } + if (!(varRef.eContainer() instanceof Connection)) { + error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED); + } + + if (varRef.getVariable() instanceof Port) { + // This test only works correctly if the variable is actually a port. If it is not a port, + // other + // validator rules will produce error messages. + if (varRef.getContainer() == null + || varRef.getContainer().getWidthSpec() == null + || ((Port) varRef.getVariable()).getWidthSpec() == null) { + error( + "interleaved can only be used for multiports contained within banks.", + Literals.VAR_REF__INTERLEAVED); + } + } } - - @Check(CheckType.FAST) - public void checkWidthSpec(WidthSpec widthSpec) { - if (!this.target.supportsMultiports()) { - error("Multiports and banks are currently not supported by the given target.", + } + + /** + * Check whether an attribute is supported and the validity of the attribute. + * + * @param attr The attribute being checked + */ + @Check(CheckType.FAST) + public void checkAttributes(Attribute attr) { + String name = attr.getAttrName().toString(); + AttributeSpec spec = AttributeSpec.ATTRIBUTE_SPECS_BY_NAME.get(name); + if (spec == null) { + error("Unknown attribute.", Literals.ATTRIBUTE__ATTR_NAME); + return; + } + // Check the validity of the attribute. + spec.check(this, attr); + } + + @Check(CheckType.FAST) + public void checkWidthSpec(WidthSpec widthSpec) { + if (!this.target.supportsMultiports()) { + error( + "Multiports and banks are currently not supported by the given target.", + Literals.WIDTH_SPEC__TERMS); + } else { + for (WidthTerm term : widthSpec.getTerms()) { + if (term.getParameter() != null) { + if (!this.target.supportsParameterizedWidths()) { + error( + "Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS); - } else { - for (WidthTerm term : widthSpec.getTerms()) { - if (term.getParameter() != null) { - if (!this.target.supportsParameterizedWidths()) { - error("Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS); - } - } else if (term.getPort() != null) { - // Widths given with `widthof()` are not supported (yet?). - // This feature is currently only used for after delays. - error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS); - } else if (term.getCode() != null) { - if (this.target != Target.CPP) { - error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS); - } - } else if (term.getWidth() < 0) { - error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS); - } - } + } + } else if (term.getPort() != null) { + // Widths given with `widthof()` are not supported (yet?). + // This feature is currently only used for after delays. + error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS); + } else if (term.getCode() != null) { + if (this.target != Target.CPP) { + error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS); + } + } else if (term.getWidth() < 0) { + error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS); } + } } - - @Check(CheckType.FAST) - public void checkReactorIconAttribute(Reactor reactor) { - var path = AttributeUtils.getIconPath(reactor); - if (path != null) { - var param = AttributeUtils.findAttributeByName(reactor, "icon").getAttrParms().get(0); - // Check file extension - var validExtensions = Set.of("bmp", "png", "gif", "ico", "jpeg"); - var extensionStrart = path.lastIndexOf("."); - var extension = extensionStrart != -1 ? path.substring(extensionStrart + 1) : ""; - if (!validExtensions.contains(extension.toLowerCase())) { - warning("File extension '" + extension + "' is not supported. Provide any of: " + String.join(", ", validExtensions), - param, Literals.ATTR_PARM__VALUE); - return; - } - - // Check file location - var iconLocation = FileUtil.locateFile(path, reactor.eResource()); - if (iconLocation == null) { - warning("Cannot locate icon file.", param, Literals.ATTR_PARM__VALUE); - } - if (("file".equals(iconLocation.getScheme()) || iconLocation.getScheme() == null) && !(new File(iconLocation.getPath()).exists())) { - warning("Icon does not exist.", param, Literals.ATTR_PARM__VALUE); - } - } + } + + @Check(CheckType.FAST) + public void checkReactorIconAttribute(Reactor reactor) { + var path = AttributeUtils.getIconPath(reactor); + if (path != null) { + var param = AttributeUtils.findAttributeByName(reactor, "icon").getAttrParms().get(0); + // Check file extension + var validExtensions = Set.of("bmp", "png", "gif", "ico", "jpeg"); + var extensionStrart = path.lastIndexOf("."); + var extension = extensionStrart != -1 ? path.substring(extensionStrart + 1) : ""; + if (!validExtensions.contains(extension.toLowerCase())) { + warning( + "File extension '" + + extension + + "' is not supported. Provide any of: " + + String.join(", ", validExtensions), + param, + Literals.ATTR_PARM__VALUE); + return; + } + + // Check file location + var iconLocation = FileUtil.locateFile(path, reactor.eResource()); + if (iconLocation == null) { + warning("Cannot locate icon file.", param, Literals.ATTR_PARM__VALUE); + } + if (("file".equals(iconLocation.getScheme()) || iconLocation.getScheme() == null) + && !(new File(iconLocation.getPath()).exists())) { + warning("Icon does not exist.", param, Literals.ATTR_PARM__VALUE); + } } - - @Check(CheckType.FAST) - public void checkInitialMode(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - long initialModesCount = reactor.getModes().stream().filter(m -> m.isInitial()).count(); - if (initialModesCount == 0) { - error("Every modal reactor requires one initial mode.", Literals.REACTOR__MODES, 0); - } else if (initialModesCount > 1) { - reactor.getModes().stream().filter(m -> m.isInitial()).skip(1).forEach(m -> { - error("A modal reactor can only have one initial mode.", - Literals.REACTOR__MODES, reactor.getModes().indexOf(m)); + } + + @Check(CheckType.FAST) + public void checkInitialMode(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + long initialModesCount = reactor.getModes().stream().filter(m -> m.isInitial()).count(); + if (initialModesCount == 0) { + error("Every modal reactor requires one initial mode.", Literals.REACTOR__MODES, 0); + } else if (initialModesCount > 1) { + reactor.getModes().stream() + .filter(m -> m.isInitial()) + .skip(1) + .forEach( + m -> { + error( + "A modal reactor can only have one initial mode.", + Literals.REACTOR__MODES, + reactor.getModes().indexOf(m)); }); - } + } + } + } + + @Check(CheckType.FAST) + public void checkModeStateNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getStateVars().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var stateVar : mode.getStateVars()) { + if (names.contains(stateVar.getName())) { + error( + String.format( + "Duplicate state variable '%s'. (State variables are currently scoped on" + + " reactor level not modes)", + stateVar.getName()), + stateVar, + Literals.STATE_VAR__NAME); + } + names.add(stateVar.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeStateNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getStateVars().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var stateVar : mode.getStateVars()) { - if (names.contains(stateVar.getName())) { - error(String.format("Duplicate state variable '%s'. (State variables are currently scoped on reactor level not modes)", - stateVar.getName()), stateVar, Literals.STATE_VAR__NAME); - } - names.add(stateVar.getName()); - } - } + } + + @Check(CheckType.FAST) + public void checkModeTimerNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getTimers().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var timer : mode.getTimers()) { + if (names.contains(timer.getName())) { + error( + String.format( + "Duplicate Timer '%s'. (Timers are currently scoped on reactor level not" + + " modes)", + timer.getName()), + timer, + Literals.VARIABLE__NAME); + } + names.add(timer.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeTimerNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getTimers().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var timer : mode.getTimers()) { - if (names.contains(timer.getName())) { - error(String.format("Duplicate Timer '%s'. (Timers are currently scoped on reactor level not modes)", - timer.getName()), timer, Literals.VARIABLE__NAME); - } - names.add(timer.getName()); - } - } + } + + @Check(CheckType.FAST) + public void checkModeActionNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var action : mode.getActions()) { + if (names.contains(action.getName())) { + error( + String.format( + "Duplicate Action '%s'. (Actions are currently scoped on reactor level not" + + " modes)", + action.getName()), + action, + Literals.VARIABLE__NAME); + } + names.add(action.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeActionNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var action : mode.getActions()) { - if (names.contains(action.getName())) { - error(String.format("Duplicate Action '%s'. (Actions are currently scoped on reactor level not modes)", - action.getName()), action, Literals.VARIABLE__NAME); - } - names.add(action.getName()); - } - } + } + + @Check(CheckType.FAST) + public void checkModeInstanceNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var instantiation : mode.getInstantiations()) { + if (names.contains(instantiation.getName())) { + error( + String.format( + "Duplicate Instantiation '%s'. (Instantiations are currently scoped on reactor" + + " level not modes)", + instantiation.getName()), + instantiation, + Literals.INSTANTIATION__NAME); + } + names.add(instantiation.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeInstanceNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var instantiation : mode.getInstantiations()) { - if (names.contains(instantiation.getName())) { - error(String.format("Duplicate Instantiation '%s'. (Instantiations are currently scoped on reactor level not modes)", - instantiation.getName()), instantiation, Literals.INSTANTIATION__NAME); - } - names.add(instantiation.getName()); - } + } + + @Check(CheckType.FAST) + public void checkMissingStateResetInMode(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var resetModes = new HashSet(); + // Collect all modes that may be reset + for (var m : reactor.getModes()) { + for (var r : m.getReactions()) { + for (var e : r.getEffects()) { + if (e.getVariable() instanceof Mode && e.getTransition() != ModeTransition.HISTORY) { + resetModes.add((Mode) e.getVariable()); } + } } - } - - @Check(CheckType.FAST) - public void checkMissingStateResetInMode(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var resetModes = new HashSet(); - // Collect all modes that may be reset - for (var m : reactor.getModes()) { - for (var r : m.getReactions()) { - for (var e : r.getEffects()) { - if (e.getVariable() instanceof Mode && e.getTransition() != ModeTransition.HISTORY) { - resetModes.add((Mode) e.getVariable()); - } - } - } + } + for (var m : resetModes) { + // Check state variables in this mode + if (!m.getStateVars().isEmpty()) { + var hasResetReaction = + m.getReactions().stream() + .anyMatch( + r -> + r.getTriggers().stream() + .anyMatch( + t -> + (t instanceof BuiltinTriggerRef + && ((BuiltinTriggerRef) t).getType() + == BuiltinTrigger.RESET))); + if (!hasResetReaction) { + for (var s : m.getStateVars()) { + if (!s.isReset()) { + error( + "State variable is not reset upon mode entry. It is neither marked for" + + " automatic reset nor is there a reset reaction.", + m, + Literals.MODE__STATE_VARS, + m.getStateVars().indexOf(s)); + } } - for (var m : resetModes) { - // Check state variables in this mode - if (!m.getStateVars().isEmpty()) { - var hasResetReaction = m.getReactions().stream().anyMatch( - r -> r.getTriggers().stream().anyMatch( - t -> (t instanceof BuiltinTriggerRef && - ((BuiltinTriggerRef) t).getType() == BuiltinTrigger.RESET))); - if (!hasResetReaction) { - for (var s : m.getStateVars()) { - if (!s.isReset()) { - error("State variable is not reset upon mode entry. It is neither marked for automatic reset nor is there a reset reaction.", - m, Literals.MODE__STATE_VARS, m.getStateVars().indexOf(s)); - } - } - } + } + } + // Check state variables in instantiated reactors + if (!m.getInstantiations().isEmpty()) { + for (var i : m.getInstantiations()) { + var error = new LinkedHashSet(); + var checked = new HashSet(); + var toCheck = new LinkedList(); + toCheck.add((Reactor) i.getReactorClass()); + while (!toCheck.isEmpty()) { + var check = toCheck.pop(); + checked.add(check); + if (!check.getStateVars().isEmpty()) { + var hasResetReaction = + check.getReactions().stream() + .anyMatch( + r -> + r.getTriggers().stream() + .anyMatch( + t -> + (t instanceof BuiltinTriggerRef + && ((BuiltinTriggerRef) t).getType() + == BuiltinTrigger.RESET))); + if (!hasResetReaction) { + // Add state vars that are not self-resetting to the error + check.getStateVars().stream() + .filter(s -> !s.isReset()) + .forEachOrdered(error::add); } - // Check state variables in instantiated reactors - if (!m.getInstantiations().isEmpty()) { - for (var i : m.getInstantiations()) { - var error = new LinkedHashSet(); - var checked = new HashSet(); - var toCheck = new LinkedList(); - toCheck.add((Reactor) i.getReactorClass()); - while (!toCheck.isEmpty()) { - var check = toCheck.pop(); - checked.add(check); - if (!check.getStateVars().isEmpty()) { - var hasResetReaction = check.getReactions().stream().anyMatch( - r -> r.getTriggers().stream().anyMatch( - t -> (t instanceof BuiltinTriggerRef && - ((BuiltinTriggerRef) t).getType() == BuiltinTrigger.RESET))); - if (!hasResetReaction) { - // Add state vars that are not self-resetting to the error - check.getStateVars().stream().filter(s -> !s.isReset()).forEachOrdered(error::add); - } - } - // continue with inner - for (var innerInstance : check.getInstantiations()) { - var next = (Reactor) innerInstance.getReactorClass(); - if (!checked.contains(next)) { - toCheck.push(next); - } - } - } - if (!error.isEmpty()) { - error("This reactor contains state variables that are not reset upon mode entry: " - + error.stream().map(e -> e.getName() + " in " - + ASTUtils.getEnclosingReactor(e).getName()).collect(Collectors.joining(", ")) - + ".\nThe state variables are neither marked for automatic reset nor have a dedicated reset reaction. " - + "It is unsafe to instantiate this reactor inside a mode entered with reset.", - m, Literals.MODE__INSTANTIATIONS, - m.getInstantiations().indexOf(i)); - } - } + } + // continue with inner + for (var innerInstance : check.getInstantiations()) { + var next = (Reactor) innerInstance.getReactorClass(); + if (!checked.contains(next)) { + toCheck.push(next); } + } + } + if (!error.isEmpty()) { + error( + "This reactor contains state variables that are not reset upon mode entry: " + + error.stream() + .map( + e -> e.getName() + " in " + ASTUtils.getEnclosingReactor(e).getName()) + .collect(Collectors.joining(", ")) + + ".\n" + + "The state variables are neither marked for automatic reset nor have a" + + " dedicated reset reaction. It is unsafe to instantiate this reactor inside" + + " a mode entered with reset.", + m, + Literals.MODE__INSTANTIATIONS, + m.getInstantiations().indexOf(i)); } + } } + } } - - @Check(CheckType.FAST) - public void checkStateResetWithoutInitialValue(StateVar state) { - if (state.isReset() && (state.getInit() == null || state.getInit().getExprs().isEmpty())) { - error("The state variable can not be automatically reset without an initial value.", state, Literals.STATE_VAR__RESET); - } + } + + @Check(CheckType.FAST) + public void checkStateResetWithoutInitialValue(StateVar state) { + if (state.isReset() && (state.getInit() == null || state.getInit().getExprs().isEmpty())) { + error( + "The state variable can not be automatically reset without an initial value.", + state, + Literals.STATE_VAR__RESET); } - - @Check(CheckType.FAST) - public void checkUnspecifiedTransitionType(Reaction reaction) { - for (var effect : reaction.getEffects()) { - var variable = effect.getVariable(); - if (variable instanceof Mode) { - // The transition type is always set to default by Xtext. - // Hence, check if there is an explicit node for the transition type in the AST. - var transitionAssignment = NodeModelUtils.findNodesForFeature((EObject) effect, Literals.VAR_REF__TRANSITION); - if (transitionAssignment.isEmpty()) { // Transition type not explicitly specified. - var mode = (Mode) variable; - // Check if reset or history transition would make a difference. - var makesDifference = !mode.getStateVars().isEmpty() - || !mode.getTimers().isEmpty() - || !mode.getActions().isEmpty() - || mode.getConnections().stream().anyMatch(c -> c.getDelay() != null); - if (!makesDifference && !mode.getInstantiations().isEmpty()) { - // Also check instantiated reactors - for (var i : mode.getInstantiations()) { - var checked = new HashSet(); - var toCheck = new LinkedList(); - toCheck.add((Reactor) i.getReactorClass()); - while (!toCheck.isEmpty() && !makesDifference) { - var check = toCheck.pop(); - checked.add(check); - - makesDifference |= !check.getModes().isEmpty() - || !ASTUtils.allStateVars(check).isEmpty() - || !ASTUtils.allTimers(check).isEmpty() - || !ASTUtils.allActions(check).isEmpty() - || ASTUtils.allConnections(check).stream().anyMatch(c -> c.getDelay() != null); - - // continue with inner - for (var innerInstance : check.getInstantiations()) { - var next = (Reactor) innerInstance.getReactorClass(); - if (!checked.contains(next)) { - toCheck.push(next); - } - } - } - } - } - if (makesDifference) { - warning("You should specify a transition type! " - + "Reset and history transitions have different effects on this target mode. " - + "Currently, a reset type is implicitly assumed.", - reaction, Literals.REACTION__EFFECTS, reaction.getEffects().indexOf(effect)); - } + } + + @Check(CheckType.FAST) + public void checkUnspecifiedTransitionType(Reaction reaction) { + for (var effect : reaction.getEffects()) { + var variable = effect.getVariable(); + if (variable instanceof Mode) { + // The transition type is always set to default by Xtext. + // Hence, check if there is an explicit node for the transition type in the AST. + var transitionAssignment = + NodeModelUtils.findNodesForFeature((EObject) effect, Literals.VAR_REF__TRANSITION); + if (transitionAssignment.isEmpty()) { // Transition type not explicitly specified. + var mode = (Mode) variable; + // Check if reset or history transition would make a difference. + var makesDifference = + !mode.getStateVars().isEmpty() + || !mode.getTimers().isEmpty() + || !mode.getActions().isEmpty() + || mode.getConnections().stream().anyMatch(c -> c.getDelay() != null); + if (!makesDifference && !mode.getInstantiations().isEmpty()) { + // Also check instantiated reactors + for (var i : mode.getInstantiations()) { + var checked = new HashSet(); + var toCheck = new LinkedList(); + toCheck.add((Reactor) i.getReactorClass()); + while (!toCheck.isEmpty() && !makesDifference) { + var check = toCheck.pop(); + checked.add(check); + + makesDifference |= + !check.getModes().isEmpty() + || !ASTUtils.allStateVars(check).isEmpty() + || !ASTUtils.allTimers(check).isEmpty() + || !ASTUtils.allActions(check).isEmpty() + || ASTUtils.allConnections(check).stream() + .anyMatch(c -> c.getDelay() != null); + + // continue with inner + for (var innerInstance : check.getInstantiations()) { + var next = (Reactor) innerInstance.getReactorClass(); + if (!checked.contains(next)) { + toCheck.push(next); + } } + } } + } + if (makesDifference) { + warning( + "You should specify a transition type! " + + "Reset and history transitions have different effects on this target mode. " + + "Currently, a reset type is implicitly assumed.", + reaction, + Literals.REACTION__EFFECTS, + reaction.getEffects().indexOf(effect)); + } } + } + } + } + + ////////////////////////////////////////////////////////////// + //// Public methods. + + /** Return the error reporter for this validator. */ + public ValidatorErrorReporter getErrorReporter() { + return this.errorReporter; + } + + /** Implementation required by xtext to report validation errors. */ + @Override + public ValidationMessageAcceptor getMessageAcceptor() { + return messageAcceptor == null ? this : messageAcceptor; + } + + /** Return a list of error messages for the target declaration. */ + public List getTargetPropertyErrors() { + return this.targetPropertyErrors; + } + + ////////////////////////////////////////////////////////////// + //// Protected methods. + + /** Generate an error message for an AST node. */ + @Override + protected void error(java.lang.String message, org.eclipse.emf.ecore.EStructuralFeature feature) { + super.error(message, feature); + } + + ////////////////////////////////////////////////////////////// + //// Private methods. + + /** + * For each input, report a conflict if: 1) the input exists and the type doesn't match; or 2) the + * input has a name clash with variable that is not an input. + * + * @param superVars List of typed variables of a particular kind (i.e., inputs, outputs, or + * actions), found in a super class. + * @param sameKind Typed variables of the same kind, found in the subclass. + * @param allOwn Accumulator of non-conflicting variables incorporated in the subclass. + * @param conflicts Set of variables that are in conflict, to be used by this function to report + * conflicts. + */ + private void checkConflict( + EList superVars, EList sameKind, List allOwn, HashSet conflicts) { + for (T superVar : superVars) { + T match = null; + for (T it : sameKind) { + if (it.getName().equals(superVar.getName())) { + match = it; + break; + } + } + List rest = new ArrayList<>(allOwn); + rest.removeIf(it -> sameKind.contains(it)); + + if ((match != null && superVar.getType() != match.getType()) + || hasNameConflict(superVar, rest)) { + conflicts.add(superVar); + } else { + allOwn.add(superVar); + } + } + } + + /** + * Check the name of a feature for illegal substrings such as reserved identifiers and names with + * double leading underscores. + * + * @param name The name. + * @param feature The feature containing the name (for error reporting). + */ + private void checkName(String name, EStructuralFeature feature) { + + // Raises an error if the string starts with two underscores. + if (name.length() >= 2 && name.substring(0, 2).equals("__")) { + error(UNDERSCORE_MESSAGE + name, feature); } - ////////////////////////////////////////////////////////////// - //// Public methods. - - /** - * Return the error reporter for this validator. - */ - public ValidatorErrorReporter getErrorReporter() { - return this.errorReporter; - } - - /** - * Implementation required by xtext to report validation errors. - */ - @Override - public ValidationMessageAcceptor getMessageAcceptor() { - return messageAcceptor == null ? this : messageAcceptor; - } - - /** - * Return a list of error messages for the target declaration. - */ - public List getTargetPropertyErrors() { - return this.targetPropertyErrors; - } - - ////////////////////////////////////////////////////////////// - //// Protected methods. - - /** - * Generate an error message for an AST node. - */ - @Override - protected void error(java.lang.String message, - org.eclipse.emf.ecore.EStructuralFeature feature) { - super.error(message, feature); - } - - ////////////////////////////////////////////////////////////// - //// Private methods. - - /** - * For each input, report a conflict if: - * 1) the input exists and the type doesn't match; or - * 2) the input has a name clash with variable that is not an input. - * @param superVars List of typed variables of a particular kind (i.e., - * inputs, outputs, or actions), found in a super class. - * @param sameKind Typed variables of the same kind, found in the subclass. - * @param allOwn Accumulator of non-conflicting variables incorporated in the - * subclass. - * @param conflicts Set of variables that are in conflict, to be used by this - * function to report conflicts. - */ - private void checkConflict ( - EList superVars, EList sameKind, List allOwn, HashSet conflicts - ) { - for (T superVar : superVars) { - T match = null; - for (T it : sameKind) { - if (it.getName().equals(superVar.getName())) { - match = it; - break; - } - } - List rest = new ArrayList<>(allOwn); - rest.removeIf(it -> sameKind.contains(it)); + if (this.target.isReservedIdent(name)) { + error(RESERVED_MESSAGE + name, feature); + } - if ((match != null && superVar.getType() != match.getType()) || hasNameConflict(superVar, rest)) { - conflicts.add(superVar); - } else { - allOwn.add(superVar); - } - } + if (this.target == Target.TS) { + // "actions" is a reserved word within a TS reaction + if (name.equals("actions")) { + error(ACTIONS_MESSAGE + name, feature); + } + } + } + + /** + * Check that the initializer is compatible with the type. Note that if the type is inferred it + * will necessarily be compatible so this method is not harmful. + */ + public void typeCheck(Initializer init, InferredType type, EStructuralFeature feature) { + if (init == null) { + return; } - /** - * Check the name of a feature for illegal substrings such as reserved - * identifiers and names with double leading underscores. - * @param name The name. - * @param feature The feature containing the name (for error reporting). - */ - private void checkName(String name, EStructuralFeature feature) { + // TODO: + // type is list => init is list + // type is fixed with size n => init is fixed with size n + // Specifically for C: list can only be literal or time lists - // Raises an error if the string starts with two underscores. - if (name.length() >= 2 && name.substring(0, 2).equals("__")) { - error(UNDERSCORE_MESSAGE + name, feature); + if (type.isTime) { + if (type.isList) { + // list of times + var exprs = init.getExprs(); + if (exprs.isEmpty()) { + error("Expected at least one time value.", feature); + return; } - - if (this.target.isReservedIdent(name)) { - error(RESERVED_MESSAGE + name, feature); + if (exprs.size() == 1 && exprs.get(0) instanceof BracedListExpression) { + exprs = ((BracedListExpression) exprs.get(0)).getItems(); } - - if (this.target == Target.TS) { - // "actions" is a reserved word within a TS reaction - if (name.equals("actions")) { - error(ACTIONS_MESSAGE + name, feature); - } + for (var component : exprs) { + checkExpressionIsTime(component, feature); } + } else { + checkExpressionIsTime(init, feature); + } } + } - - /** - * Check that the initializer is compatible with the type. - * Note that if the type is inferred it will necessarily be compatible - * so this method is not harmful. - */ - public void typeCheck(Initializer init, InferredType type, EStructuralFeature feature) { - if (init == null) { - return; - } - - // TODO: - // type is list => init is list - // type is fixed with size n => init is fixed with size n - // Specifically for C: list can only be literal or time lists - - if (type.isTime) { - if (type.isList) { - // list of times - var exprs = init.getExprs(); - if (exprs.isEmpty()) { - error("Expected at least one time value.", feature); - return; - } - if (exprs.size() == 1 && exprs.get(0) instanceof BracedListExpression) { - exprs = ((BracedListExpression) exprs.get(0)).getItems(); - } - for (var component : exprs) { - checkExpressionIsTime(component, feature); - } - } else { - checkExpressionIsTime(init, feature); - } - } + private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) { + if (init == null) { + return; } - private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) { - if (init == null) { - return; - } - - if (init.getExprs().size() != 1) { - error("Expected exactly one time value.", feature); - } else { - checkExpressionIsTime(ASTUtils.asSingleExpr(init), feature); - } + if (init.getExprs().size() != 1) { + error("Expected exactly one time value.", feature); + } else { + checkExpressionIsTime(ASTUtils.asSingleExpr(init), feature); } + } - private void checkExpressionIsTime(Expression value, EStructuralFeature feature) { - if (value == null || value instanceof Time) { - return; - } - - if (value instanceof ParameterReference) { - if (!ASTUtils.isOfTimeType(((ParameterReference) value).getParameter()) - && target.requiresTypes) { - error("Referenced parameter is not of time type.", feature); - } - return; - } else if (value instanceof Literal) { - if (ASTUtils.isZero(((Literal) value).getLiteral())) { - return; - } - - if (ASTUtils.isInteger(((Literal) value).getLiteral())) { - error("Missing time unit.", feature); - return; - } - // fallthrough - } - - error("Invalid time value.", feature); + private void checkExpressionIsTime(Expression value, EStructuralFeature feature) { + if (value == null || value instanceof Time) { + return; } - /** - * Return the number of main or federated reactors declared. - * - * @param iter An iterator over all objects in the resource. - */ - private int countMainOrFederated(TreeIterator iter) { - int nMain = 0; - while (iter.hasNext()) { - EObject obj = iter.next(); - if (!(obj instanceof Reactor)) { - continue; - } - Reactor r = (Reactor) obj; - if (r.isMain() || r.isFederated()) { - nMain++; - } - } - return nMain; - } - - /** - * Report whether a given reactor has dependencies on a cyclic - * instantiation pattern. This means the reactor has an instantiation - * in it -- directly or in one of its contained reactors -- that is - * self-referential. - * @param reactor The reactor definition to find out whether it has any - * dependencies on cyclic instantiations. - * @param cycleSet The set of all reactors that are part of an - * instantiation cycle. - * @param visited The set of nodes already visited in this graph traversal. - */ - private boolean dependsOnCycle( - Reactor reactor, Set cycleSet, Set visited - ) { - Set origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor); - if (visited.contains(reactor)) { - return false; - } else { - visited.add(reactor); - for (Reactor it : origins) { - if (cycleSet.contains(it) || dependsOnCycle(it, cycleSet, visited)) { - // Reached a cycle. - return true; - } - } - } - return false; - } - - /** - * Report whether the name of the given element matches any variable in - * the ones to check against. - * @param element The element to compare against all variables in the given iterable. - * @param toCheckAgainst Iterable variables to compare the given element against. - */ - private boolean hasNameConflict(Variable element, - Iterable toCheckAgainst) { - int numNameConflicts = 0; - for (Variable it : toCheckAgainst) { - if (it.getName().equals(element.getName())) { - numNameConflicts++; - } - } - return numNameConflicts > 0; + if (value instanceof ParameterReference) { + if (!ASTUtils.isOfTimeType(((ParameterReference) value).getParameter()) + && target.requiresTypes) { + error("Referenced parameter is not of time type.", feature); + } + return; + } else if (value instanceof Literal) { + if (ASTUtils.isZero(((Literal) value).getLiteral())) { + return; + } + + if (ASTUtils.isInteger(((Literal) value).getLiteral())) { + error("Missing time unit.", feature); + return; + } + // fallthrough } - /** - * Return true if target is C or a C-based target like CCpp. - */ - private boolean isCBasedTarget() { - return (this.target == Target.C || this.target == Target.CCPP); + error("Invalid time value.", feature); + } + + /** + * Return the number of main or federated reactors declared. + * + * @param iter An iterator over all objects in the resource. + */ + private int countMainOrFederated(TreeIterator iter) { + int nMain = 0; + while (iter.hasNext()) { + EObject obj = iter.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor r = (Reactor) obj; + if (r.isMain() || r.isFederated()) { + nMain++; + } } - - /** - * Report whether a given imported reactor is used in this resource or not. - * @param reactor The imported reactor to check whether it is used. - */ - private boolean isUnused(ImportedReactor reactor) { - TreeIterator instantiations = reactor.eResource().getAllContents(); - TreeIterator subclasses = reactor.eResource().getAllContents(); - - boolean instantiationsCheck = true; - while (instantiations.hasNext() && instantiationsCheck) { - EObject obj = instantiations.next(); - if (!(obj instanceof Instantiation)) { - continue; - } - Instantiation inst = (Instantiation) obj; - instantiationsCheck &= (inst.getReactorClass() != reactor && inst.getReactorClass() != reactor.getReactorClass()); - } - - boolean subclassesCheck = true; - while (subclasses.hasNext() && subclassesCheck) { - EObject obj = subclasses.next(); - if (!(obj instanceof Reactor)) { - continue; - } - Reactor subclass = (Reactor) obj; - for (ReactorDecl decl : subclass.getSuperClasses()) { - subclassesCheck &= (decl != reactor && decl != reactor.getReactorClass()); - } - } - return instantiationsCheck && subclassesCheck; + return nMain; + } + + /** + * Report whether a given reactor has dependencies on a cyclic instantiation pattern. This means + * the reactor has an instantiation in it -- directly or in one of its contained reactors -- that + * is self-referential. + * + * @param reactor The reactor definition to find out whether it has any dependencies on cyclic + * instantiations. + * @param cycleSet The set of all reactors that are part of an instantiation cycle. + * @param visited The set of nodes already visited in this graph traversal. + */ + private boolean dependsOnCycle(Reactor reactor, Set cycleSet, Set visited) { + Set origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor); + if (visited.contains(reactor)) { + return false; + } else { + visited.add(reactor); + for (Reactor it : origins) { + if (cycleSet.contains(it) || dependsOnCycle(it, cycleSet, visited)) { + // Reached a cycle. + return true; + } + } } - - /** - * Return true if the two types match. Unfortunately, xtext does not - * seem to create a suitable equals() method for Type, so we have to - * do this manually. - */ - private boolean sameType(Type type1, Type type2) { - if (type1 == null) { - return type2 == null; - } - if (type2 == null) { - return type1 == null; - } - // Most common case first. - if (type1.getId() != null) { - if (type1.getStars() != null) { - if (type2.getStars() == null) return false; - if (type1.getStars().size() != type2.getStars().size()) return false; - } - return (type1.getId().equals(type2.getId())); - } - - // Type specification in the grammar is: - // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); - if (type1.isTime()) { - if (!type2.isTime()) return false; - // Ignore the arraySpec because that is checked when connection - // is checked for balance. - return true; - } - // Type must be given in a code body - return type1.getCode().getBody().equals(type2.getCode().getBody()); + return false; + } + + /** + * Report whether the name of the given element matches any variable in the ones to check against. + * + * @param element The element to compare against all variables in the given iterable. + * @param toCheckAgainst Iterable variables to compare the given element against. + */ + private boolean hasNameConflict(Variable element, Iterable toCheckAgainst) { + int numNameConflicts = 0; + for (Variable it : toCheckAgainst) { + if (it.getName().equals(element.getName())) { + numNameConflicts++; + } + } + return numNameConflicts > 0; + } + + /** Return true if target is C or a C-based target like CCpp. */ + private boolean isCBasedTarget() { + return (this.target == Target.C || this.target == Target.CCPP); + } + + /** + * Report whether a given imported reactor is used in this resource or not. + * + * @param reactor The imported reactor to check whether it is used. + */ + private boolean isUnused(ImportedReactor reactor) { + TreeIterator instantiations = reactor.eResource().getAllContents(); + TreeIterator subclasses = reactor.eResource().getAllContents(); + + boolean instantiationsCheck = true; + while (instantiations.hasNext() && instantiationsCheck) { + EObject obj = instantiations.next(); + if (!(obj instanceof Instantiation)) { + continue; + } + Instantiation inst = (Instantiation) obj; + instantiationsCheck &= + (inst.getReactorClass() != reactor + && inst.getReactorClass() != reactor.getReactorClass()); } - ////////////////////////////////////////////////////////////// - //// Private fields. - - /** The error reporter. */ - private ValidatorErrorReporter errorReporter - = new ValidatorErrorReporter(getMessageAcceptor(), new ValidatorStateAccess()); - - /** Helper class containing information about the model. */ - private ModelInfo info = new ModelInfo(); - - @Inject(optional = true) - private ValidationMessageAcceptor messageAcceptor; - - /** The declared target. */ - private Target target; - - private List targetPropertyErrors = new ArrayList<>(); - - private List targetPropertyWarnings = new ArrayList<>(); - - ////////////////////////////////////////////////////////////// - //// Private static constants. - - private static String ACTIONS_MESSAGE - = "\"actions\" is a reserved word for the TypeScript target for objects " - + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, " - + "and reactor instantiation): "; - - private static String HOST_OR_FQN_REGEX - = "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; - - /** - * Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). - */ - private static String IPV4_REGEX = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; - - /** - * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), - * with minor adjustment to allow up to six IPV6 segments (without truncation) in front - * of an embedded IPv4-address. - **/ - private static String IPV6_REGEX = - "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,7}:|" + - "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + - "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + - "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + - "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + - "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + - ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + - "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + - "::(ffff(:0{1,4}){0,1}:){0,1}" + IPV4_REGEX + "|" + - "([0-9a-fA-F]{1,4}:){1,4}:" + IPV4_REGEX + "|" + - "([0-9a-fA-F]{1,4}:){1,6}" + IPV4_REGEX + ")"; - - private static String RESERVED_MESSAGE = "Reserved words in the target language are not allowed for objects " - + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; - - private static List SPACING_VIOLATION_POLICIES = List.of("defer", "drop", "replace"); - - private static String UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, " - + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; - - private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; + boolean subclassesCheck = true; + while (subclasses.hasNext() && subclassesCheck) { + EObject obj = subclasses.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor subclass = (Reactor) obj; + for (ReactorDecl decl : subclass.getSuperClasses()) { + subclassesCheck &= (decl != reactor && decl != reactor.getReactorClass()); + } + } + return instantiationsCheck && subclassesCheck; + } + + /** + * Return true if the two types match. Unfortunately, xtext does not seem to create a suitable + * equals() method for Type, so we have to do this manually. + */ + private boolean sameType(Type type1, Type type2) { + if (type1 == null) { + return type2 == null; + } + if (type2 == null) { + return type1 == null; + } + // Most common case first. + if (type1.getId() != null) { + if (type1.getStars() != null) { + if (type2.getStars() == null) return false; + if (type1.getStars().size() != type2.getStars().size()) return false; + } + return (type1.getId().equals(type2.getId())); + } + // Type specification in the grammar is: + // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' + // typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); + if (type1.isTime()) { + if (!type2.isTime()) return false; + // Ignore the arraySpec because that is checked when connection + // is checked for balance. + return true; + } + // Type must be given in a code body + return type1.getCode().getBody().equals(type2.getCode().getBody()); + } + + ////////////////////////////////////////////////////////////// + //// Private fields. + + /** The error reporter. */ + private ValidatorErrorReporter errorReporter = + new ValidatorErrorReporter(getMessageAcceptor(), new ValidatorStateAccess()); + + /** Helper class containing information about the model. */ + private ModelInfo info = new ModelInfo(); + + @Inject(optional = true) + private ValidationMessageAcceptor messageAcceptor; + + /** The declared target. */ + private Target target; + + private List targetPropertyErrors = new ArrayList<>(); + + private List targetPropertyWarnings = new ArrayList<>(); + + ////////////////////////////////////////////////////////////// + //// Private static constants. + + private static String ACTIONS_MESSAGE = + "\"actions\" is a reserved word for the TypeScript target for objects " + + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, " + + "and reactor instantiation): "; + + private static String HOST_OR_FQN_REGEX = + "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; + + /** Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). */ + private static String IPV4_REGEX = + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; + + /** + * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), with minor + * adjustment to allow up to six IPV6 segments (without truncation) in front of an embedded + * IPv4-address. + */ + private static String IPV6_REGEX = + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,7}:|" + + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + + "::(ffff(:0{1,4}){0,1}:){0,1}" + + IPV4_REGEX + + "|" + + "([0-9a-fA-F]{1,4}:){1,4}:" + + IPV4_REGEX + + "|" + + "([0-9a-fA-F]{1,4}:){1,6}" + + IPV4_REGEX + + ")"; + + private static String RESERVED_MESSAGE = + "Reserved words in the target language are not allowed for objects (inputs, outputs, actions," + + " timers, parameters, state, reactor definitions, and reactor instantiation): "; + + private static List SPACING_VIOLATION_POLICIES = List.of("defer", "drop", "replace"); + + private static String UNDERSCORE_MESSAGE = + "Names of objects (inputs, outputs, actions, timers, parameters, " + + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; + + private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; } diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf new file mode 100644 index 0000000000..e40157a9e6 --- /dev/null +++ b/test/C/src/Watchdog.lf @@ -0,0 +1,64 @@ +/** + * Test watchdog. This test starts a watchdog timer of 150ms every 100ms. Half + * the time, it then sleeps after starting the watchdog so that the watchdog + * expires. There should be a total of five watchdog expirations. + * @author Benjamin Asch + * @author Edward A. Lee + */ +target C { + timeout: 1100 ms +} + +reactor Watcher(timeout: time = 150 ms) { + timer t(100 ms, 100 ms) // Offset ameliorates startup time. + output d: int // Produced if the watchdog triggers. + state alternating: bool = false + state count: int = 0 + + watchdog poodle(timeout) {= + instant_t p = lf_time_physical_elapsed() - lf_time_logical_elapsed(); + lf_print("Watchdog timed out! Lag: %lld (too late by " PRINTF_TIME " ns)", p, p - self->timeout); + lf_print("At logical time inside watchdog panic: " PRINTF_TIME, lf_time_logical_elapsed()); + self->count++; + =} + + reaction(t) -> poodle, d {= + lf_watchdog_start(poodle, 0); + lf_print("Watchdog started at physical time " PRINTF_TIME, lf_time_physical_elapsed()); + lf_print("Will expire at " PRINTF_TIME, lf_time_logical_elapsed() + self->timeout); + if (self->alternating) { + lf_sleep(MSEC(160)); + } + self->alternating = !self->alternating; + =} + + reaction(poodle) -> d {= + lf_print("Reaction poodle was called."); + lf_set(d, 1); + =} + + reaction(shutdown) -> poodle {= + lf_watchdog_stop(poodle); + if (self->count != 5) { + lf_print_error_and_exit("Watchdog expired %d times. Expected 5.", self->count); + } + =} +} + +main reactor { + logical action a + state count: int = 0 + + w = new Watcher() + + reaction(w.d) {= + lf_print("*** Watcher reactor produced an output."); + self->count++; + =} + + reaction(shutdown) {= + if (self->count != 5) { + lf_print_error_and_exit("Watchdog produced output %d times. Expected 5.", self->count); + } + =} +}