From 718eaab1236352d0cb1c1eef26ea6cf29a8f725b Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Sun, 25 Dec 2022 16:29:10 -0800 Subject: [PATCH 001/108] added func for watchdog name, created watchdog instance class --- org.lflang/src/org/lflang/LinguaFranca.xtext | 8 ++- .../lflang/generator/ReactionInstance.java | 6 ++ .../lflang/generator/WatchdogInstance.java | 59 +++++++++++++++++++ .../generator/c/CReactionGenerator.java | 11 ++++ 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/WatchdogInstance.java diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 38dc520c0b..c28f35065e 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -201,7 +201,8 @@ Reaction: ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? code=Code (stp=STP)? - (deadline=Deadline)?; + (deadline=Deadline)? + (watchdog=Watchdog)?; TriggerRef: BuiltinTriggerRef | VarRef; @@ -211,6 +212,9 @@ BuiltinTriggerRef: Deadline: 'deadline' '(' delay=Expression ')' code=Code; + +Watchdog: + 'watchdog' nameID=Expression '(' delay=Expression ')' code=Code; STP: 'STP' '(' value=Expression ')' code=Code; @@ -486,7 +490,7 @@ 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' | + 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' // Other terminals NEGINT | TRUE | FALSE | // Action origins diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index f20962d70d..737d814049 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -223,6 +223,12 @@ public ReactionInstance( */ public DeadlineInstance declaredDeadline; + //FIXME: modif4watchdog + /** + * Watchdog for this reaction instance, if declared. + */ + public WatchdogInstance declaredWatchdog; + /** * Inferred deadline. Defaults to the maximum long value. */ 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..40b02edd66 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -0,0 +1,59 @@ +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 } + */ +//FIXME: modif4watchdogs +public class WatchdogInstance { + + /** + * Create a new watchdog instance associated with the given reaction + * instance. + */ + public WatchdogInstance(Watchdog definition, ReactionInstance reaction) { + if (definition.getTimeout() != null) { + this.timeout = reaction.parent.getTimeValue(definition.getTimeout()); + } else { + this.timeout = TimeValue.ZERO; + } + + if (definition.getCode() != null) { + this.watchdogHandler = definition.getCode(); + } else { + this.watchdogHandler = new CodeBuilder(); + } + } + + ////////////////////////////////////////////////////// + //// Public fields. + + /** + * The timeout, L, associated with this deadline. The physical timer gets set + * to expire at physical time, P, equal to the current logical time, t, plus the timeout. + * + * In other words, the watchdog condition is met iff P < t + L while the watchdog has not + * been stopped or reset. + */ + public final TimeValue timeout; + + /** + * The code body for the function that is executed upon calling the watchdog. + */ + public final CodeBuilder watchdogHandler; + + ////////////////////////////////////////////////////// + //// Public methods. + + //FIXME: unsure of use or need for watchdogs + @Override + public String toString() { + return "WatchdogInstance " + timeout.toString(); + } +} diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index cac6594edc..3fa09cb494 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -1132,6 +1132,17 @@ public static String generateFunction(String header, String init, Code code) { return function.toString(); } + // FIXME: modif4watchdogs + /** + * Returns the name of the watchdog function for reaction. + * @param decl The reactor with the watchdog + * @param reactionIndex The number assigned to this reaction watchdog + * @return Name of the watchdog function for reaction + */ + public static String generateWatchdogFunctionName(ReactorDecl decl, int reactionIndex) { + return decl.getName().toLowerCase() + "_watchdog_function" + reactionIndex; + } + /** * Returns the name of the deadline function for reaction. * @param decl The reactor with the deadline From 43bb491830d62be9c3747aafc17a219a4f1d3fed Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 5 Jan 2023 00:01:10 -0800 Subject: [PATCH 002/108] made some progress on code generation --- .../src/org/lflang/generator/WatchdogInstance.java | 3 +++ org.lflang/src/org/lflang/generator/c/CGenerator.java | 5 +++++ .../src/org/lflang/generator/c/CReactionGenerator.java | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index 40b02edd66..290c7144a9 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -11,6 +11,7 @@ * @author{Benjamin Asch } */ //FIXME: modif4watchdogs +//FIXME: functions in constructor not defined public class WatchdogInstance { /** @@ -29,6 +30,8 @@ public WatchdogInstance(Watchdog definition, ReactionInstance reaction) { } else { this.watchdogHandler = new CodeBuilder(); } + + this.name = definition.getName(); } ////////////////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index ec2b7986c3..6913d7bed7 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -929,6 +929,11 @@ private boolean hasDeadlines(List reactors) { return false; } + //FIXME: modif4watchdogs + private boolean hasWatchdogs(List reactors) { + + } + /** * Look at the 'reactor' eResource. * If it is an imported .lf file, incorporate it into the current diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 3fa09cb494..5f017e89a2 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -759,6 +759,14 @@ public static void generateReactionAndTriggerStructs( deadlineFunctionPointer = "&" + deadlineFunctionName; } + // FIXME: modif4watchdogs + // FIXME: '.getWatchdog()' not implemented + var watchdogFunctionPointer = "NULL"; + if (reaction.getWatchdog() != null) { + var watchdogFunctionName = generateWatchdogFunctionName(decl, reactionCount); + watchdogFunctionPointer = "&" + watchdogFunctionName; + } + // Assign the STP handler var STPFunctionPointer = "NULL"; if (reaction.getStp() != null) { @@ -780,6 +788,7 @@ public static void generateReactionAndTriggerStructs( "self->_lf__reaction_"+reactionCount+".function = "+ CReactionGenerator.generateReactionFunctionName(decl, reactionCount)+";", "self->_lf__reaction_"+reactionCount+".self = self;", "self->_lf__reaction_"+reactionCount+".deadline_violation_handler = "+deadlineFunctionPointer+";", + "self->_lf__reaction_"+reactionCount+".watchdog_handler = "+watchdogFunctionPointer+";", "self->_lf__reaction_"+reactionCount+".STP_handler = "+STPFunctionPointer+";", "self->_lf__reaction_"+reactionCount+".name = "+addDoubleQuotes("?")+";", (reaction.eContainer() instanceof Mode ? From 20531f07fd21ebbfbf4fa904d347b1858d54e983 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 5 Jan 2023 12:48:54 -0800 Subject: [PATCH 003/108] more functions added for code generation --- org.lflang/src/org/lflang/ASTUtils.java | 13 ++++++++ .../lflang/generator/ReactionInstance.java | 8 +++++ .../org/lflang/generator/c/CGenerator.java | 10 ++++-- .../generator/c/CReactionGenerator.java | 31 +++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index dc15877879..181eb5c81a 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -144,6 +144,19 @@ public static Iterable getAllReactors(Resource resource) { .collect(Collectors.toList()); } + /** + * Get all watchdogs defined in the given resource. + * @param resource the resource to extract watchdogs from + * @return An iterable over all watchdogs found in the resource + */ + //FIXME: modif4watchdogs + public static Iterable getAllWatchdogs(Resource resource) { + return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) + .filter(Watchdog.class::isInstance) + .map(Watchdog.class::cast) + .collect(Collectors.toList()); + } + /** * Find connections in the given resource that have a delay associated with them, * and reroute them via a generated delay reactor. diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 737d814049..b7fffe132f 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -187,6 +187,14 @@ public ReactionInstance( this.declaredDeadline = new DeadlineInstance( this.definition.getDeadline(), this); } + + //FIXME: modif4watchdogs + // getWatchdog not implemented + // need to implement matching reaction output with watchdog + if (this.definition.getWatchdog() != null) { + this.declaredWatchdog = new WatchdogInstance( + this.definition.getWatchdog(), this); + } } ////////////////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 6913d7bed7..d022942dce 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -930,8 +930,14 @@ private boolean hasDeadlines(List reactors) { } //FIXME: modif4watchdogs - private boolean hasWatchdogs(List reactors) { - + //FIXME: getWatchdog() has not been implemented + private boolean hasWatchdogs(Reactor reactor) { + for (Reaction reaction : allReactions(reactor)) { + if (reaction.getWatchdog() != null) { + return true; + } + } + return false; } /** diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 5f017e89a2..97f69baeae 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -1005,6 +1005,16 @@ public static String generateLfTriggerStartupReactions(int startupReactionCount, return s.toString(); } + /** + * Generate _lf_initialize_watchdog_mutexes function. + */ + //FIXME: modif4watchdogs + //FIXME: finish implementing + public static String generateLfInitializeWatchdogMutexes(List reactors) { + // need to find way to assign get watchdog from AST + // need to assign watchdog to correct reaction + } + /** * Generate the _lf_trigger_shutdown_reactions function. */ @@ -1122,6 +1132,15 @@ public static String generateReaction( generateDeadlineFunctionHeader(decl, reactionIndex), init, reaction.getDeadline().getCode())); } + + // FIXME:modif4watchdogs + // Now generate code for the watchdog handler function, if there is one. + if (reaction.getWatchdog() != null) { + code.pr(generateFunction( + generateWatchdogFunctionHeader(decl, reactionIndex), + init, reaction.getWatchdog().getCode())); + } + CMethodGenerator.generateMacroUndefsForMethods(ASTUtils.toDefinition(decl), code); code.pr( "#include " + StringUtil.addDoubleQuotes( @@ -1192,6 +1211,18 @@ public static String generateDeadlineFunctionHeader(ReactorDecl decl, return generateFunctionHeader(functionName); } + /** Return the top level C function header for the watchdog function numbered "reactionIndex" in "decl" + * @param decl The reactor declaration + * @param reactionIndex The reaction index. + * @return The function name for the watchdog function. + */ + //FIXME: modif4watchdogs + public static String generateWatchdogFunctionHeader(ReactorDecl decl, + int reactionIndex) { + String functionName = generateWatchdogFunctionName(decl, reactionIndex); + return generateFunctionHeader(functionName); + } + /** Return the top level C function header for the reaction numbered "reactionIndex" in "decl" * @param decl The reactor declaration * @param reactionIndex The reaction index. From 371385e003015e3670546e23f6ebf3d469d53ffb Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Sun, 8 Jan 2023 11:28:32 -0800 Subject: [PATCH 004/108] saving progress before pushing --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 4 ++++ org.lflang/src/org/lflang/generator/c/CReactionGenerator.java | 3 +++ 2 files changed, 7 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index d022942dce..af62bd8b8f 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -840,6 +840,10 @@ private void generateCodeForCurrentFederate( // Generate function to schedule timers for all reactors. code.pr(CTimerGenerator.generateLfInitializeTimer(timerCount)); + //FIXME:modif4watchdogs + // Generate function to initialize mutexes for all reactors with watchdogs. + code.pr(CReactionGenerator.generateLfInitializeWatchdogMutexes()); + // 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 diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 97f69baeae..06bbaf853c 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -1013,6 +1013,9 @@ public static String generateLfTriggerStartupReactions(int startupReactionCount, public static String generateLfInitializeWatchdogMutexes(List reactors) { // need to find way to assign get watchdog from AST // need to assign watchdog to correct reaction + var s = new StringBuilder(); + s.append("void _lf_initialize_watchdog_mutexes() {\n"); + } /** From 86c369d36b0736223789544361fa751d4cabe1bb Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Sun, 8 Jan 2023 14:55:02 -0800 Subject: [PATCH 005/108] fixed grammar(added watchdog to 'varreformodetransition', removed watchdog from reaction) --- org.lflang/src/org/lflang/LinguaFranca.xtext | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index c28f35065e..f39eb724cb 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -201,8 +201,7 @@ Reaction: ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? code=Code (stp=STP)? - (deadline=Deadline)? - (watchdog=Watchdog)?; + (deadline=Deadline)?; TriggerRef: BuiltinTriggerRef | VarRef; @@ -214,7 +213,7 @@ Deadline: 'deadline' '(' delay=Expression ')' code=Code; Watchdog: - 'watchdog' nameID=Expression '(' delay=Expression ')' code=Code; + 'watchdog' name=Expression '(' timeout=Expression ')' code=Code; STP: 'STP' '(' value=Expression ')' code=Code; @@ -283,7 +282,7 @@ VarRef: | interleaved?='interleaved' '(' (variable=[Variable] | container=[Instantiation] '.' variable=[Variable]) ')' ; VarRefOrModeTransition returns VarRef: - VarRef | transition=ModeTransition '(' variable=[Mode] ')'; + VarRef | Watchdog | transition=ModeTransition '(' variable=[Mode] ')'; Assignment: (lhs=[Parameter] ( From 15fc495b762139bbf327b2ba1698a695a0b5a453 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Sun, 8 Jan 2023 14:58:40 -0800 Subject: [PATCH 006/108] added watchdog grammar to reactor --- org.lflang/src/org/lflang/LinguaFranca.xtext | 1 + 1 file changed, 1 insertion(+) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index f39eb724cb..718382335d 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -91,6 +91,7 @@ Reactor: | (connections+=Connection) | (reactions+=Reaction) | (modes+=Mode) + (watchdogs+=Watchdog) )* '}'; From 6124cae9328288ce6bf8ee387997b44087681c86 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 12 Jan 2023 15:29:17 -0800 Subject: [PATCH 007/108] making sure updated before push --- org.lflang/src/org/lflang/ASTUtils.java | 28 ++-- org.lflang/src/org/lflang/LinguaFranca.xtext | 4 +- .../lflang/generator/ReactionInstance.java | 16 +- .../org/lflang/generator/ReactorInstance.java | 4 + .../org/lflang/generator/c/CGenerator.java | 19 ++- .../generator/c/CReactionGenerator.java | 89 +++++------ .../generator/c/CWatchdogGenerator.java | 140 ++++++++++++++++++ 7 files changed, 212 insertions(+), 88 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 181eb5c81a..b87dc94479 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -122,13 +122,16 @@ public class ASTUtils { /** * A mapping from Reactor features to corresponding Mode features for collecting contained elements. */ + //FIXME: modif4watchdogs + // added 'featurePackage.getReactor_Watchdogs' 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_Timers(), featurePackage.getMode_Timers(), + featurePackage.getReactor_Watchdogs() ); @@ -144,19 +147,6 @@ public static Iterable getAllReactors(Resource resource) { .collect(Collectors.toList()); } - /** - * Get all watchdogs defined in the given resource. - * @param resource the resource to extract watchdogs from - * @return An iterable over all watchdogs found in the resource - */ - //FIXME: modif4watchdogs - public static Iterable getAllWatchdogs(Resource resource) { - return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) - .filter(Watchdog.class::isInstance) - .map(Watchdog.class::cast) - .collect(Collectors.toList()); - } - /** * Find connections in the given resource that have a delay associated with them, * and reroute them via a generated delay reactor. @@ -686,6 +676,16 @@ public static List allParameters(Reactor 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 + */ + // FIXME: modif4watchdogs + 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, diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 718382335d..e624e61c53 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -214,7 +214,9 @@ Deadline: 'deadline' '(' delay=Expression ')' code=Code; Watchdog: - 'watchdog' name=Expression '(' timeout=Expression ')' code=Code; + 'watchdog' name=Expression '(' timeout=Expression ')' + ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? + code=Code; STP: 'STP' '(' value=Expression ')' code=Code; diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index b7fffe132f..13dc1e1c52 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -187,14 +187,6 @@ public ReactionInstance( this.declaredDeadline = new DeadlineInstance( this.definition.getDeadline(), this); } - - //FIXME: modif4watchdogs - // getWatchdog not implemented - // need to implement matching reaction output with watchdog - if (this.definition.getWatchdog() != null) { - this.declaredWatchdog = new WatchdogInstance( - this.definition.getWatchdog(), this); - } } ////////////////////////////////////////////////////// @@ -230,13 +222,7 @@ public ReactionInstance( * Deadline for this reaction instance, if declared. */ public DeadlineInstance declaredDeadline; - - //FIXME: modif4watchdog - /** - * Watchdog for this reaction instance, if declared. - */ - public WatchdogInstance declaredWatchdog; - + /** * Inferred deadline. Defaults to the maximum long value. */ diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index ae953e9370..dbed569762 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -150,6 +150,10 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re /** List of reaction instances for this reactor instance. */ public final List reactions = new ArrayList<>(); + /** List of watchdog instances for this reactor instance. */ + //FIXME: modif4watchdogs + public final List watchdogs = new ArrayList<>(); + /** The timer instances belonging to this reactor instance. */ public final List timers = new ArrayList<>(); diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index af62bd8b8f..4579e32534 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -934,12 +934,9 @@ private boolean hasDeadlines(List reactors) { } //FIXME: modif4watchdogs - //FIXME: getWatchdog() has not been implemented private boolean hasWatchdogs(Reactor reactor) { - for (Reaction reaction : allReactions(reactor)) { - if (reaction.getWatchdog() != null) { - return true; - } + if (ASTUtils.allWatchdogs(reactor) != null) { + return true; } return false; } @@ -1532,6 +1529,18 @@ protected void generateSelfStructExtension( // Do nothing } + /** + * Generate watchdog functions definition for a reactor. + * @param decl The reactor + * @param federate The federate, or null if this is not + * federated or not the main reactor and watchdogs should be + * unconditionally generated. + */ + //FIXME: modif4watchdogs + public void generateWatchdogFunctions(ReactorDecl decl, FederateInstance federate) { + + } + /** 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), diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 06bbaf853c..f5aff5d55e 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -195,6 +195,15 @@ public static String generateInitializationForReaction(String body, effect.getContainer(), (Input) variable ); + } else if (variable instanceof Watchdog) { + //FIXME: modif4watchdogs + // How does it know if instance of watchdog? + reactionInitialization.pr(generateWatchdogVariablesInReaction( + effect, + decl, + errorReporter, + requiresTypes + )); } else { errorReporter.reportError( reaction, @@ -679,6 +688,31 @@ public static String generateOutputVariablesInReaction( } } + /** + * 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. + */ + //FIXME: modif4watchdogs + // Fine to have watchdog be in reactor self struct? + public static String generateWatchdogVariablesInReaction( + VarRef effect, + ReactorDecl decl, + ErrorReporter errorReporter, + boolean requiresTypes + ) { + Watchdog watchdog = (Watchdog) effect.getVariable(); + String watchdogName = watchdog.getName(); + if (watchdog.getType() == null && requiresTypes) { + errorReporter.reportError(watchdog, "Watchdog is required to have a type: " + watchdogName); + return ""; + } else { + return "watchdog_t* "+watchdogName+" = &self->_lf_watchdog_"+watchdogName+";"; + } + } + /** * 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 @@ -759,14 +793,6 @@ public static void generateReactionAndTriggerStructs( deadlineFunctionPointer = "&" + deadlineFunctionName; } - // FIXME: modif4watchdogs - // FIXME: '.getWatchdog()' not implemented - var watchdogFunctionPointer = "NULL"; - if (reaction.getWatchdog() != null) { - var watchdogFunctionName = generateWatchdogFunctionName(decl, reactionCount); - watchdogFunctionPointer = "&" + watchdogFunctionName; - } - // Assign the STP handler var STPFunctionPointer = "NULL"; if (reaction.getStp() != null) { @@ -1005,19 +1031,6 @@ public static String generateLfTriggerStartupReactions(int startupReactionCount, return s.toString(); } - /** - * Generate _lf_initialize_watchdog_mutexes function. - */ - //FIXME: modif4watchdogs - //FIXME: finish implementing - public static String generateLfInitializeWatchdogMutexes(List reactors) { - // need to find way to assign get watchdog from AST - // need to assign watchdog to correct reaction - var s = new StringBuilder(); - s.append("void _lf_initialize_watchdog_mutexes() {\n"); - - } - /** * Generate the _lf_trigger_shutdown_reactions function. */ @@ -1136,14 +1149,6 @@ public static String generateReaction( init, reaction.getDeadline().getCode())); } - // FIXME:modif4watchdogs - // Now generate code for the watchdog handler function, if there is one. - if (reaction.getWatchdog() != null) { - code.pr(generateFunction( - generateWatchdogFunctionHeader(decl, reactionIndex), - init, reaction.getWatchdog().getCode())); - } - CMethodGenerator.generateMacroUndefsForMethods(ASTUtils.toDefinition(decl), code); code.pr( "#include " + StringUtil.addDoubleQuotes( @@ -1163,17 +1168,6 @@ public static String generateFunction(String header, String init, Code code) { return function.toString(); } - // FIXME: modif4watchdogs - /** - * Returns the name of the watchdog function for reaction. - * @param decl The reactor with the watchdog - * @param reactionIndex The number assigned to this reaction watchdog - * @return Name of the watchdog function for reaction - */ - public static String generateWatchdogFunctionName(ReactorDecl decl, int reactionIndex) { - return decl.getName().toLowerCase() + "_watchdog_function" + reactionIndex; - } - /** * Returns the name of the deadline function for reaction. * @param decl The reactor with the deadline @@ -1214,18 +1208,6 @@ public static String generateDeadlineFunctionHeader(ReactorDecl decl, return generateFunctionHeader(functionName); } - /** Return the top level C function header for the watchdog function numbered "reactionIndex" in "decl" - * @param decl The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the watchdog function. - */ - //FIXME: modif4watchdogs - public static String generateWatchdogFunctionHeader(ReactorDecl decl, - int reactionIndex) { - String functionName = generateWatchdogFunctionName(decl, reactionIndex); - return generateFunctionHeader(functionName); - } - /** Return the top level C function header for the reaction numbered "reactionIndex" in "decl" * @param decl The reactor declaration * @param reactionIndex The reaction index. @@ -1243,7 +1225,8 @@ public static String generateStpFunctionHeader(ReactorDecl decl, return generateFunctionHeader(functionName); } - private static String generateFunctionHeader(String functionName) { + //FIXME: modif4watchdogs (changed from private to public to access in CWatchdogGenerator) + 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..1c31ee5779 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -0,0 +1,140 @@ +package org.lflang.generator.c; + +import org.lflang.federated.FederateInstance; +import org.lflang.generator.CodeBuilder; +import org.lflang.lf.ReactorDecl; +import org.lflang.lf.CReactionGenerator; + +/** + * Generates necessary C code for watchdogs. + * + * @author{Benjamin Asch } + */ +//FIXME: modif4watchdogs +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(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);" + )); + } + + // 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." + // ); + // } + } + } + } + + // 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 CReactionGenerator.generateFunction(generateWatchdogFunctionHeader(watchdog, decl), + generateInitializationForWatchdog(watchdog, decl), + watchdog.getCode()); + } + + /** + * Generate a constructor for the specified reactor in the specified federate. + * @param reactor The parsed reactor data structure. + * @param federate A federate name, or null to unconditionally generate. + * @param constructorCode Lines of code previously generated that need to + * go into the constructor. + */ + //FIXME: this is just the old constructor meant for reactors + public static String generateWatchdogConstructor( + ReactorDecl reactor, + FederateInstance federate, + String constructorCode + ) { + var structType = CUtil.selfType(reactor); + var code = new CodeBuilder(); + code.pr(structType+"* new_"+reactor.getName()+"() {"); + code.indent(); + code.pr(structType+"* self = ("+structType+"*)_lf_new_reactor(sizeof("+structType+"));"); + code.pr(constructorCode); + code.pr("return self;"); + code.unindent(); + code.pr("}"); + return code.toString(); + } + + /** + * Generate _lf_initialize_watchdog_mutexes function. + */ + //FIXME: finish implementing + public static String generateLfInitializeWatchdogMutexes(List reactors) { + // need to find way to assign get watchdog from AST + // need to assign watchdog to correct reaction + var s = new StringBuilder(); + s.append("void _lf_initialize_watchdog_mutexes() {\n"); + + } +} From 3798f3a62f73be544e9c82d6a721c74f56a677c8 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 18 Jan 2023 14:20:03 -0800 Subject: [PATCH 008/108] code generation steps (excluding mutex initialization) should be done, I think --- .../org/lflang/generator/ReactorInstance.java | 22 +++++ .../lflang/generator/WatchdogInstance.java | 23 ++--- .../org/lflang/generator/c/CGenerator.java | 40 +++++++++ .../generator/c/CReactionGenerator.java | 5 +- .../generator/c/CWatchdogGenerator.java | 85 +++++++++++++++---- 5 files changed, 144 insertions(+), 31 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index dbed569762..46b34c662f 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -720,6 +720,24 @@ protected void createReactionInstances() { } } + /** + * Create all the watchdog instances of this reactor instance. + */ + // FIXME: modif4watchdogs + 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); + } + } + } + /** * Returns the built-in trigger or create a new one if none exists. */ @@ -834,6 +852,10 @@ private ReactorInstance( // Note that this can only happen _after_ the children, // port, action, and timer instances have been created. createReactionInstances(); + + // Create the reaction instances in this reactor instance. + // FIXME: modif4watchdogs + createWatchdogInstances(); // Instantiate modes for this reactor instance // This must come after the child elements (reactions, etc) of this reactor diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index 290c7144a9..ff9bc139df 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -18,22 +18,23 @@ public class WatchdogInstance { * Create a new watchdog instance associated with the given reaction * instance. */ - public WatchdogInstance(Watchdog definition, ReactionInstance reaction) { + public WatchdogInstance(Watchdog definition, ReactorInstance reactor) { if (definition.getTimeout() != null) { - this.timeout = reaction.parent.getTimeValue(definition.getTimeout()); + // WATCHDOG QUESTION + // How does this .getTimeValue work? Where is expression coming from + // versus other time parameters? + this.timeout = reactor.getTimeValue(definition.getTimeout()); } else { this.timeout = TimeValue.ZERO; } - if (definition.getCode() != null) { - this.watchdogHandler = definition.getCode(); - } else { - this.watchdogHandler = new CodeBuilder(); - } - this.name = definition.getName(); } + public String getName() { + return this.name; + } + ////////////////////////////////////////////////////// //// Public fields. @@ -46,10 +47,10 @@ public WatchdogInstance(Watchdog definition, ReactionInstance reaction) { */ public final TimeValue timeout; - /** - * The code body for the function that is executed upon calling the watchdog. + /** + * The watchdog name. */ - public final CodeBuilder watchdogHandler; + public final String name; ////////////////////////////////////////////////////// //// Public methods. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 4579e32534..f27c16ecc1 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1213,6 +1213,8 @@ private void generateReactorClass(ReactorDecl reactor) { generateAuxiliaryStructs(reactor); generateSelfStruct(reactor, constructorCode); generateMethods(reactor); + // FIXME: modif4watchdogs + generateWatchdogs(reactor, currentFederate); generateReactions(reactor, currentFederate); generateConstructor(reactor, currentFederate, constructorCode); @@ -1584,6 +1586,42 @@ protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactio )); } + // FIXME: modif4watchdogs + /** 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. + * @param federate The federate, or null if this is not + * federated or not the main reactor and reactions should be + * unconditionally generated. + */ + public void generateWatchdogs(ReactorDecl decl, FederateInstance federate) { + // WATCHDOG QUESTION: A similar question is asked somewhere else for a + // different function - Do we need to check if this federate contains the + // watchdog? This is done in the code generation for reactions. + var reactor = ASTUtils.toDefinition(decl); + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + if (federate == null || federate.contains(watchdog)) { + generateWatchdog(watchdog, decl); + } + } + } + + /** 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(Watchdog watchdog, ReactorDecl decl) { + code.pr(CWatchdogGenerator.generateWatchdog( + watchdog, + decl + )); + } + /** * Record startup, shutdown, and reset reactions. * @param instance A reactor instance. @@ -2077,6 +2115,8 @@ protected void generateStateVariableInitializations(ReactorInstance instance) { * specified reactor instance. * @param instance The reactor instance. */ + // WATCHDOG QUESTION: Why do we wait to set the deadline instead of defining + // it in the constructor of the reactor? private void generateSetDeadline(ReactorInstance instance) { for (ReactionInstance reaction : instance.reactions) { if (currentFederate.contains(reaction.getDefinition())) { diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index f5aff5d55e..0f1d0ee2fb 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -197,7 +197,6 @@ public static String generateInitializationForReaction(String body, ); } else if (variable instanceof Watchdog) { //FIXME: modif4watchdogs - // How does it know if instance of watchdog? reactionInitialization.pr(generateWatchdogVariablesInReaction( effect, decl, @@ -745,6 +744,9 @@ public static void generateReactionAndTriggerStructs( var startupReactions = new LinkedHashSet(); var shutdownReactions = new LinkedHashSet(); var resetReactions = new LinkedHashSet(); + // WATCHDOG QUESTION: Why need to grab all reactions from reactor only to check + // if it exists in currentFederate? Maybe real question is what is difference between + // currentfederate and reactor? for (Reaction reaction : ASTUtils.allReactions(reactor)) { if (currentFederate.contains(reaction)) { // Create the reaction_t struct. @@ -814,7 +816,6 @@ public static void generateReactionAndTriggerStructs( "self->_lf__reaction_"+reactionCount+".function = "+ CReactionGenerator.generateReactionFunctionName(decl, reactionCount)+";", "self->_lf__reaction_"+reactionCount+".self = self;", "self->_lf__reaction_"+reactionCount+".deadline_violation_handler = "+deadlineFunctionPointer+";", - "self->_lf__reaction_"+reactionCount+".watchdog_handler = "+watchdogFunctionPointer+";", "self->_lf__reaction_"+reactionCount+".STP_handler = "+STPFunctionPointer+";", "self->_lf__reaction_"+reactionCount+".name = "+addDoubleQuotes("?")+";", (reaction.eContainer() instanceof Mode ? diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 1c31ee5779..578e6bc7a5 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -1,5 +1,6 @@ package org.lflang.generator.c; +import org.lflang.ASTUtils; import org.lflang.federated.FederateInstance; import org.lflang.generator.CodeBuilder; import org.lflang.lf.ReactorDecl; @@ -102,27 +103,75 @@ public static String generateWatchdogFunction(Watchdog watchdog, } /** - * Generate a constructor for the specified reactor in the specified federate. - * @param reactor The parsed reactor data structure. - * @param federate A federate name, or null to unconditionally generate. - * @param constructorCode Lines of code previously generated that need to - * go into the constructor. + * Generate watchdog definition in parent struct. */ - //FIXME: this is just the old constructor meant for reactors - public static String generateWatchdogConstructor( - ReactorDecl reactor, - FederateInstance federate, - String constructorCode + public static void generateWatchdogStruct( + FederateInstance currentFederate, + CodeBuilder body, + ReactorDecl decl, + CodeBuilder constructorCode + ) { + var reactor = ASTUtils.toDefinition(decl); + + // WATCHDOG QUESTION 1: I followed similar format to + // `CReactionGenerator.generateReactionAndTriggerStructs` + // but am not sure why we need to check if watchdog exists in the + // current federate. + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + if (currentFederate.contains(watchdog)) { + + String watchdogName = watchdog.getName(); + // Create pointer to the watchdog_t struct + // WATCHDOG QUESTION 2: Why need to put object at beginning of + // `.pr` func call? + + // WATCHDOG QUESTION 3: Is the space for this struct automatically allocated + // through `_lf_new_reactor`? `_lf__startup_reaction` is also a pointer in self struct + // but does not seem to have a separate allocation call. + body.pr(watchdog, "watchdog_t* _lf_watchdog_"+watchdogName+";"); + + // WATCHDOG QUESTION 4: Not sure if this is correct, may need to use + // 'getTargetTime' instead. watchdog timeout is listed as "Expression" + // in the grammar, so I'm not sure if it is reading the timeout as + // a Time class or TimeValue class. + var min_expiration = GeneratorBase.timeInTargetLanguage(watchdog.getTimeout()); + + // watchdog function name + var watchdogFunctionName = generateWatchdogFunctionName(watchdog, decl); + // Set values of watchdog_t struct in the reactor's constructor + // WATCHDOG QUESTION 5: should I be defining these in the constructor of the reactor? + constructorCode.pr(watchdog, String.join("\n", + "self->_lf_watchdog_"+watchdogName+".self = self;", + "self->_lf_watchdog_"+watchdogName+".expiration = NEVER;", + "self->_lf_watchdog_"+watchdogName+".min_expiration = "+min_expiration+";", + "self->_lf_watchdog_"+watchdogName+".watchdog_function = "+watchdogFunctionName+";" + )); + + // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? + } + } + } + + /** 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 structType = CUtil.selfType(reactor); var code = new CodeBuilder(); - code.pr(structType+"* new_"+reactor.getName()+"() {"); - code.indent(); - code.pr(structType+"* self = ("+structType+"*)_lf_new_reactor(sizeof("+structType+"));"); - code.pr(constructorCode); - code.pr("return self;"); - code.unindent(); - code.pr("}"); + + // WATCHDOG QUESTION: Do I need this header? What it for? + code.pr( + "#include " + StringUtil.addDoubleQuotes( + CCoreFilesUtils.getCTargetSetHeader())); + + code.pr(generateWatchdogFunction(watchdog, decl)); + return code.toString(); } From bf4fd4cfd5af0e084cc267efa99cdc54f82bdcc1 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 18 Jan 2023 14:54:07 -0800 Subject: [PATCH 009/108] generatewatchdogstruct in generateselfstruct --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 8 ++++++++ .../src/org/lflang/generator/c/CReactionGenerator.java | 3 +-- .../src/org/lflang/generator/c/CWatchdogGenerator.java | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index f27c16ecc1..9dfffdd039 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1369,6 +1369,14 @@ private void generateSelfStruct(ReactorDecl decl, CodeBuilder constructorCode) { isFederatedAndDecentralized() ); + // Generate the fields needed for each watchdog. + CWatchdogGenerator.generateWatchdogStruct( + currentFederate, + body, + decl, + constructorCode + ); + // Next, generate fields for modes CModesGenerator.generateDeclarations(reactor, body, constructorCode); diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 0f1d0ee2fb..4cebf42a8f 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -745,8 +745,7 @@ public static void generateReactionAndTriggerStructs( var shutdownReactions = new LinkedHashSet(); var resetReactions = new LinkedHashSet(); // WATCHDOG QUESTION: Why need to grab all reactions from reactor only to check - // if it exists in currentFederate? Maybe real question is what is difference between - // currentfederate and reactor? + // if it exists in currentFederate? for (Reaction reaction : ASTUtils.allReactions(reactor)) { if (currentFederate.contains(reaction)) { // Create the reaction_t struct. diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 578e6bc7a5..b8b78803c3 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -113,7 +113,7 @@ public static void generateWatchdogStruct( ) { var reactor = ASTUtils.toDefinition(decl); - // WATCHDOG QUESTION 1: I followed similar format to + // WATCHDOG QUESTION 1: I followed similar structure to // `CReactionGenerator.generateReactionAndTriggerStructs` // but am not sure why we need to check if watchdog exists in the // current federate. From b87327f2c786d112b34951ab8ad006c14f3f0d48 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 1 Feb 2023 10:00:02 -0800 Subject: [PATCH 010/108] save changes to watchdog --- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/org/lflang/ASTUtils.java | 4 +- org.lflang/src/org/lflang/LinguaFranca.xtext | 4 +- .../lflang/federated/FederateInstance.java | 18 +++++++ .../org/lflang/generator/ReactorInstance.java | 2 + .../lflang/generator/WatchdogInstance.java | 28 +++++++--- .../org/lflang/generator/c/CGenerator.java | 51 ++++++++++++++----- .../generator/c/CReactionGenerator.java | 4 +- .../generator/c/CWatchdogGenerator.java | 51 ++++++++++++++----- 9 files changed, 124 insertions(+), 40 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 5b772a5322..050abfa4fa 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 5b772a5322b46b0738fcbd31e33f1175fde3c236 +Subproject commit 050abfa4fa2dab3cc7bc078be3556a239312ac16 diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index b87dc94479..eb0ca085cb 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -90,6 +90,8 @@ import org.lflang.lf.TypeParm; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +//FIXME: modif4watchdogs +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.util.StringUtil; @@ -131,7 +133,7 @@ public class ASTUtils { featurePackage.getReactor_Reactions(), featurePackage.getMode_Reactions(), featurePackage.getReactor_StateVars(), featurePackage.getMode_StateVars(), featurePackage.getReactor_Timers(), featurePackage.getMode_Timers(), - featurePackage.getReactor_Watchdogs() + featurePackage.getReactor_Watchdogs(), featurePackage.getMode_Watchdogs() ); diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index e624e61c53..7325401322 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -278,14 +278,14 @@ TypedVariable: ; Variable: - TypedVariable | Timer | Mode; + TypedVariable | Timer | Mode | Watchdog; VarRef: variable=[Variable] | container=[Instantiation] '.' variable=[Variable] | interleaved?='interleaved' '(' (variable=[Variable] | container=[Instantiation] '.' variable=[Variable]) ')' ; VarRefOrModeTransition returns VarRef: - VarRef | Watchdog | transition=ModeTransition '(' variable=[Mode] ')'; + VarRef | transition=ModeTransition '(' variable=[Mode] ')'; Assignment: (lhs=[Parameter] ( diff --git a/org.lflang/src/org/lflang/federated/FederateInstance.java b/org.lflang/src/org/lflang/federated/FederateInstance.java index c32095acfb..cb29977d01 100644 --- a/org.lflang/src/org/lflang/federated/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/FederateInstance.java @@ -327,6 +327,24 @@ public boolean contains(Reaction reaction) { return !excludeReactions.contains(reaction); } + + //FIXME: modif4watchdogs + public boolean contains(Watchdog watchdog) { + Reactor reactor = ASTUtils.getEnclosingReactor(watchdog); + if (!reactor.isFederated() || this.isSingleton()) { + return true; + } + + if (!reactor.getWatchdogs().contains(watchdog)) { + return false; + } + + if (networkReactions.contains(watchdog)) { + return true; + } + + return false; + } /** * Return true if the specified reactor instance or any parent diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 46b34c662f..3df4517d55 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -58,6 +58,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.WidthSpec; +//FIXME: modif4watchdogs +import org.lflang.lf.Watchdog; /** diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index ff9bc139df..0fb3701ecc 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -28,23 +28,31 @@ public WatchdogInstance(Watchdog definition, ReactorInstance reactor) { this.timeout = TimeValue.ZERO; } - this.name = definition.getName(); + this.name = definition.getName().toString(); + this.definition = definition; + this.reactor = reactor; } public String getName() { return this.name; } + public Watchdog getDefinition() { + return this.definition; + } + + public TimeValue getTimeout() { + return this.timeout; + } + + public ReactorInstance getReactor() { + return this.reactor; + } + ////////////////////////////////////////////////////// //// Public fields. - /** - * The timeout, L, associated with this deadline. The physical timer gets set - * to expire at physical time, P, equal to the current logical time, t, plus the timeout. - * - * In other words, the watchdog condition is met iff P < t + L while the watchdog has not - * been stopped or reset. - */ + public final TimeValue timeout; /** @@ -52,6 +60,10 @@ public String getName() { */ public final String name; + public final Watchdog definition; + + public final ReactorInstance reactor; + ////////////////////////////////////////////////////// //// Public methods. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 9dfffdd039..45657c6e38 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -80,6 +80,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; +// FIXME: modif4watchdogs +import org.lflang.generator.WatchdogInstance; import org.lflang.generator.SubContext; import org.lflang.generator.TargetTypes; import org.lflang.generator.TimerInstance; @@ -101,6 +103,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.util.FileUtil; +// FIXME: modif4watchdogs +import org.lflang.lf.Watchdog; import com.google.common.base.Objects; import com.google.common.collect.Iterables; @@ -376,6 +380,8 @@ public class CGenerator extends GeneratorBase { private int resetReactionCount = 0; private int modalReactorCount = 0; private int modalStateResetCount = 0; + // FIXME: modif4watchdogs + private int watchdogCount = 0; // Indicate whether the generator is in Cpp mode or not private final boolean CCppMode; @@ -748,7 +754,9 @@ private void generateCodeForCurrentFederate( "int _lf_tokens_with_ref_count_count = 0;", "SUPPRESS_UNUSED_WARNING(_lf_tokens_with_ref_count_count);", "int bank_index;", - "SUPPRESS_UNUSED_WARNING(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)); @@ -773,6 +781,10 @@ private void generateCodeForCurrentFederate( // If there are reset reactions, create a table of triggers. code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); + //FIXME: modif4watchdogs + // 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, @@ -842,7 +854,8 @@ private void generateCodeForCurrentFederate( //FIXME:modif4watchdogs // Generate function to initialize mutexes for all reactors with watchdogs. - code.pr(CReactionGenerator.generateLfInitializeWatchdogMutexes()); + // needs to be implemented still + code.pr(CWatchdogGenerator.generateLfInitializeWatchdogMutexes(watchdogCount)); // Generate a function that will either do nothing // (if there is only one federate or the coordination @@ -1370,6 +1383,7 @@ private void generateSelfStruct(ReactorDecl decl, CodeBuilder constructorCode) { ); // Generate the fields needed for each watchdog. + // FIXME: modif4watchdogs CWatchdogGenerator.generateWatchdogStruct( currentFederate, body, @@ -1539,18 +1553,6 @@ protected void generateSelfStructExtension( // Do nothing } - /** - * Generate watchdog functions definition for a reactor. - * @param decl The reactor - * @param federate The federate, or null if this is not - * federated or not the main reactor and watchdogs should be - * unconditionally generated. - */ - //FIXME: modif4watchdogs - public void generateWatchdogFunctions(ReactorDecl decl, FederateInstance federate) { - - } - /** 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), @@ -1676,6 +1678,25 @@ private void recordBuiltinTriggers(ReactorInstance instance) { } } + // FIXME: modif4watchdogs + private void recordWatchdogs(ReactorInstance instance) { + var foundOne = false; + var temp = new CodeBuilder(); + var reactorRef = CUtil.reactorRef(instance); + + for (WatchdogInstance watchdog : instance.watchdogs) { + if (currentFederate.contains(watchdog.getDefinition())) { + temp.pr("_lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); + temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); + temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); + watchdogCount += currentFederate.numRuntimeInstances(reactor); + foundOne = true; + } + } + if (foundOne) { + initializeTriggerObjects.pr(temp.toString()); + } + } /** @@ -1968,6 +1989,8 @@ public void generateReactorInstance(ReactorInstance instance) { initializeOutputMultiports(instance); initializeInputMultiports(instance); recordBuiltinTriggers(instance); + // FIXME: modif4watchdogs + recordWatchdogs(instance); // Next, initialize the "self" struct with state variables. // These values may be expressions that refer to the parameter values defined above. diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 4cebf42a8f..5b765483e0 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -34,6 +34,8 @@ import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.util.StringUtil; +// FIXME: modif4watchdogs +import org.lflang.lf.Watchdog; public class CReactionGenerator { protected static String DISABLE_REACTION_INITIALIZATION_MARKER @@ -708,7 +710,7 @@ public static String generateWatchdogVariablesInReaction( errorReporter.reportError(watchdog, "Watchdog is required to have a type: " + watchdogName); return ""; } else { - return "watchdog_t* "+watchdogName+" = &self->_lf_watchdog_"+watchdogName+";"; + return "watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");"; } } diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index b8b78803c3..41d8d3addc 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -4,7 +4,15 @@ import org.lflang.federated.FederateInstance; import org.lflang.generator.CodeBuilder; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.CReactionGenerator; +import org.lflang.lf.Reactor; +import org.lflang.lf.VarRef; +import org.lflang.lf.Variable; +import org.lflang.lf.Mode; +import org.lflang.lf.ModeTransition; +import org.lflang.lf.Watchdog; +import org.lflang.generator.GeneratorBase; +import org.lflang.generator.c.CReactionGenerator; +import java.util.List; /** * Generates necessary C code for watchdogs. @@ -140,10 +148,12 @@ public static void generateWatchdogStruct( var watchdogFunctionName = generateWatchdogFunctionName(watchdog, decl); // Set values of watchdog_t struct in the reactor's constructor // WATCHDOG QUESTION 5: should I be defining these in the constructor of the reactor? + //FIXME: update parameters constructorCode.pr(watchdog, String.join("\n", - "self->_lf_watchdog_"+watchdogName+".self = self;", + "self->_lf_watchdog_"+watchdogName+".base = &(self->base);", "self->_lf_watchdog_"+watchdogName+".expiration = NEVER;", - "self->_lf_watchdog_"+watchdogName+".min_expiration = "+min_expiration+";", + "self->_lf_watchdog_"+watchdogName+".thread_active = false;", + // "self->_lf_watchdog_"+watchdogName+".min_expiration = "+min_expiration+";", "self->_lf_watchdog_"+watchdogName+".watchdog_function = "+watchdogFunctionName+";" )); @@ -165,25 +175,40 @@ public static String generateWatchdog( ) { var code = new CodeBuilder(); - // WATCHDOG QUESTION: Do I need this header? What it for? - code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetHeader())); - 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.", + (count > 0 ? + "watchdog_t* _lf_"+name+"s["+count+"]" : + "int _lf_"+name+"_number = "+count+";" + ))); + } + /** * Generate _lf_initialize_watchdog_mutexes function. */ //FIXME: finish implementing - public static String generateLfInitializeWatchdogMutexes(List reactors) { - // need to find way to assign get watchdog from AST - // need to assign watchdog to correct reaction + public static String generateLfInitializeWatchdogMutexes(int watchdogCount) { var s = new StringBuilder(); - s.append("void _lf_initialize_watchdog_mutexes() {\n"); - + 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_numer; i++) {", + " self_base_t* current_base = _lf_watchdogs[i]->base;", + " if (&(current_base->watchdog_mutex) == NULL) {", + " lf_mutex_init(&(current_base->watchdog_mutex));", + " }", + " }" + )); + } + s.append("\n"); + s.append("}\n"); + return s.toString(); } } From c8c569c596212e1c66d12ee8a8d2f17b42becbfa Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 7 Feb 2023 23:45:57 -0800 Subject: [PATCH 011/108] Added a few missing imports, but plenty compile errors remain --- org.lflang/src/org/lflang/ASTUtils.java | 1 + org.lflang/src/org/lflang/generator/ReactorInstance.java | 1 + org.lflang/src/org/lflang/generator/c/CGenerator.java | 1 + org.lflang/src/org/lflang/generator/c/CReactionGenerator.java | 1 + org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java | 2 ++ 5 files changed, 6 insertions(+) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index b87dc94479..b281ac1802 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -90,6 +90,7 @@ import org.lflang.lf.TypeParm; 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; diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 46b34c662f..d7589910d2 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -57,6 +57,7 @@ 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; diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 9dfffdd039..46271e96bf 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -100,6 +100,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.StateVar; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.util.FileUtil; import com.google.common.base.Objects; diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 4cebf42a8f..9c9ff93d96 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -33,6 +33,7 @@ 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 { diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index b8b78803c3..ec67a8bed7 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -3,8 +3,10 @@ import org.lflang.ASTUtils; import org.lflang.federated.FederateInstance; import org.lflang.generator.CodeBuilder; +import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.CReactionGenerator; +import org.lflang.lf.Watchdog; /** * Generates necessary C code for watchdogs. From 10cf7411201e75fe601a5b194b0ca5a7c223cbfd Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 8 Feb 2023 12:28:59 -0800 Subject: [PATCH 012/108] saving before merging --- org.lflang/src/org/lflang/LinguaFranca.xtext | 2 +- .../src/org/lflang/federated/generator/FederateInstance.java | 1 + org.lflang/src/org/lflang/generator/c/CGenerator.java | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index e9af0b7805..9615b538eb 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -280,7 +280,7 @@ TypedVariable: ; Variable: - TypedVariable | Timer | Mode | Watchdog; + TypedVariable | Timer | Mode; VarRef: variable=[Variable] | container=[Instantiation] '.' variable=[Variable] diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index 805643b5cb..bf3b144b2b 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -67,6 +67,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import com.google.common.base.Objects; diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index ff40f79786..c622ba64a1 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -71,7 +71,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 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; From 90d002fb9ae09bafd05795beb2f4aa5aa2418e47 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 8 Feb 2023 14:28:29 -0800 Subject: [PATCH 013/108] at least it compiles now --- org.lflang/src/org/lflang/LinguaFranca.xtext | 9 +-- .../federated/generator/FederateInstance.java | 32 ++++----- .../lflang/generator/WatchdogInstance.java | 2 +- .../org/lflang/generator/c/CGenerator.java | 21 +++--- .../generator/c/CReactionGenerator.java | 15 +---- .../generator/c/CWatchdogGenerator.java | 67 +++++++++---------- 6 files changed, 64 insertions(+), 82 deletions(-) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 9615b538eb..c0bfa322d8 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -91,7 +91,7 @@ Reactor: | (connections+=Connection) | (reactions+=Reaction) | (modes+=Mode) - (watchdogs+=Watchdog) + | (watchdogs+=Watchdog) )* '}'; @@ -177,7 +177,8 @@ Mode: (actions+=Action) | (instantiations+=Instantiation) | (connections+=Connection) | - (reactions+=Reaction) + (reactions+=Reaction) | + (watchdogs+=Watchdog) )* '}'; // Action that has either a physical or logical origin. @@ -216,7 +217,7 @@ Deadline: 'deadline' '(' delay=Expression ')' code=Code; Watchdog: - 'watchdog' name=Expression '(' timeout=Expression ')' + 'watchdog' name=ID '(' timeout=Expression ')' ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? code=Code; @@ -280,7 +281,7 @@ TypedVariable: ; Variable: - TypedVariable | Timer | Mode; + TypedVariable | Timer | Mode | Watchdog; VarRef: variable=[Variable] | container=[Instantiation] '.' variable=[Variable] diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index bf3b144b2b..f13313b033 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -467,22 +467,22 @@ private boolean contains(Timer timer) { //FIXME:modif4watchdogs - public boolean contains(Watchdog watchdog) { - Reactor reactor = ASTUtils.getEnclosingReactor(watchdog); - if (!reactor.isFederated() || this.isSingleton()) { - return true; - } - - if (!reactor.getWatchdogs().contains(watchdog)) { - return false; - } - - if (networkReactions.contains(watchdog)) { - return true; - } - - return false; - } + // public boolean contains(Watchdog watchdog) { + // Reactor reactor = ASTUtils.getEnclosingReactor(watchdog); + // if (!reactor.isFederated() || this.isSingleton()) { + // return true; + // } + + // if (!reactor.getWatchdogs().contains(watchdog)) { + // return false; + // } + + // if (networkReactions.contains(watchdog)) { + // return true; + // } + + // return false; + // } /** * Return true if the specified reactor instance or any parent diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index 0fb3701ecc..780113a80f 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -42,7 +42,7 @@ public Watchdog getDefinition() { } public TimeValue getTimeout() { - return this.timeout; + return (TimeValue) this.timeout; } public ReactorInstance getReactor() { diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index d7268b0ab7..e9cb80c095 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1072,7 +1072,7 @@ private void generateReactorClass(ReactorDecl reactor) { generateMethods(reactor); // FIXME: modif4watchdogs - generateWatchdogs(reactor, currentFederate); + generateWatchdogs(reactor); generateReactions(reactor); generateConstructor(reactor, constructorCode); @@ -1226,7 +1226,6 @@ private void generateSelfStruct(ReactorDecl decl, CodeBuilder constructorCode) { // Generate the fields needed for each watchdog. // FIXME: modif4watchdogs CWatchdogGenerator.generateWatchdogStruct( - currentFederate, body, decl, constructorCode @@ -1445,15 +1444,13 @@ protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactio * federated or not the main reactor and reactions should be * unconditionally generated. */ - public void generateWatchdogs(ReactorDecl decl, FederateInstance federate) { + public void generateWatchdogs(ReactorDecl decl) { // WATCHDOG QUESTION: A similar question is asked somewhere else for a // different function - Do we need to check if this federate contains the // watchdog? This is done in the code generation for reactions. var reactor = ASTUtils.toDefinition(decl); for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - if (federate == null || federate.contains(watchdog)) { - generateWatchdog(watchdog, decl); - } + generateWatchdog(watchdog, decl); } } @@ -1522,13 +1519,11 @@ private void recordWatchdogs(ReactorInstance instance) { var reactorRef = CUtil.reactorRef(instance); for (WatchdogInstance watchdog : instance.watchdogs) { - if (currentFederate.contains(watchdog.getDefinition())) { - temp.pr("_lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); - temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); - temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); - watchdogCount += currentFederate.numRuntimeInstances(reactor); - foundOne = true; - } + temp.pr("_lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); + temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); + temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); + watchdogCount += 1; + foundOne = true; } if (foundOne) { initializeTriggerObjects.pr(temp.toString()); diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index b27f2e7594..6eaa7506b1 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -202,9 +202,7 @@ public static String generateInitializationForReaction(String body, //FIXME: modif4watchdogs reactionInitialization.pr(generateWatchdogVariablesInReaction( effect, - decl, - errorReporter, - requiresTypes + decl )); } else { errorReporter.reportError( @@ -684,18 +682,11 @@ public static String generateOutputVariablesInReaction( // Fine to have watchdog be in reactor self struct? public static String generateWatchdogVariablesInReaction( VarRef effect, - ReactorDecl decl, - ErrorReporter errorReporter, - boolean requiresTypes + ReactorDecl decl ) { Watchdog watchdog = (Watchdog) effect.getVariable(); String watchdogName = watchdog.getName(); - if (watchdog.getType() == null && requiresTypes) { - errorReporter.reportError(watchdog, "Watchdog is required to have a type: " + watchdogName); - return ""; - } else { - return "watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");"; - } + return "watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");"; } /** diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 842c145a61..8bdf90b3fe 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -1,7 +1,6 @@ package org.lflang.generator.c; import org.lflang.ASTUtils; -import org.lflang.federated.FederateInstance; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; @@ -15,7 +14,6 @@ import org.lflang.generator.GeneratorBase; import org.lflang.generator.c.CReactionGenerator; import java.util.List; -import org.lflang.lf.CReactionGenerator; import org.lflang.lf.Watchdog; /** @@ -118,7 +116,6 @@ public static String generateWatchdogFunction(Watchdog watchdog, * Generate watchdog definition in parent struct. */ public static void generateWatchdogStruct( - FederateInstance currentFederate, CodeBuilder body, ReactorDecl decl, CodeBuilder constructorCode @@ -130,39 +127,37 @@ public static void generateWatchdogStruct( // but am not sure why we need to check if watchdog exists in the // current federate. for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - if (currentFederate.contains(watchdog)) { - - String watchdogName = watchdog.getName(); - // Create pointer to the watchdog_t struct - // WATCHDOG QUESTION 2: Why need to put object at beginning of - // `.pr` func call? - - // WATCHDOG QUESTION 3: Is the space for this struct automatically allocated - // through `_lf_new_reactor`? `_lf__startup_reaction` is also a pointer in self struct - // but does not seem to have a separate allocation call. - body.pr(watchdog, "watchdog_t* _lf_watchdog_"+watchdogName+";"); - - // WATCHDOG QUESTION 4: Not sure if this is correct, may need to use - // 'getTargetTime' instead. watchdog timeout is listed as "Expression" - // in the grammar, so I'm not sure if it is reading the timeout as - // a Time class or TimeValue class. - var min_expiration = GeneratorBase.timeInTargetLanguage(watchdog.getTimeout()); - - // watchdog function name - var watchdogFunctionName = generateWatchdogFunctionName(watchdog, decl); - // Set values of watchdog_t struct in the reactor's constructor - // WATCHDOG QUESTION 5: should I be defining these in the constructor of the reactor? - //FIXME: update parameters - 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+";" - )); - - // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? - } + String watchdogName = watchdog.getName(); + // Create pointer to the watchdog_t struct + // WATCHDOG QUESTION 2: Why need to put object at beginning of + // `.pr` func call? + + // WATCHDOG QUESTION 3: Is the space for this struct automatically allocated + // through `_lf_new_reactor`? `_lf__startup_reaction` is also a pointer in self struct + // but does not seem to have a separate allocation call. + body.pr(watchdog, "watchdog_t* _lf_watchdog_"+watchdogName+";"); + + // WATCHDOG QUESTION 4: Not sure if this is correct, may need to use + // 'getTargetTime' instead. watchdog timeout is listed as "Expression" + // in the grammar, so I'm not sure if it is reading the timeout as + // a Time class or TimeValue class. + // var min_expiration = GeneratorBase.timeInTargetLanguage(watchdog.getTimeout()); + + // watchdog function name + var watchdogFunctionName = generateWatchdogFunctionName(watchdog, decl); + // Set values of watchdog_t struct in the reactor's constructor + // WATCHDOG QUESTION 5: should I be defining these in the constructor of the reactor? + //FIXME: update parameters + 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+";" + )); + + // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? + } } From 4db9e0f78db724dc8106658ea225f1c0d187d532 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 13 Feb 2023 11:49:38 -0800 Subject: [PATCH 014/108] fixed build problems --- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/lib/cpp/reactor-cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 92160a8c22..7fd67a5e59 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 92160a8c22d543eb78f7e9815041606a0aef4e23 +Subproject commit 7fd67a5e599f36d65ab21600e228af7c8217ac98 diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp index 3a38f9c65f..fc4f38c4e3 160000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 3a38f9c65fafd2d2f11416fef0ddc69c1a16aa77 +Subproject commit fc4f38c4e3988145d2d3b775b79e436f9f7339ec From f7b177e10bb175e97adec3f9a2f7b7b98c3cf63a Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 13 Feb 2023 12:41:19 -0800 Subject: [PATCH 015/108] tried, unsuccessfully, to fix variable grammar error --- org.lflang/src/org/lflang/LinguaFranca.xtext | 6 +++--- org.lflang/src/org/lflang/ast/IsEqual.java | 5 ++++- org.lflang/src/org/lflang/validation/LFValidator.java | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index c0bfa322d8..8b44ac6e1c 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -217,7 +217,7 @@ Deadline: 'deadline' '(' delay=Expression ')' code=Code; Watchdog: - 'watchdog' name=ID '(' timeout=Expression ')' + 'watchdog ' name=ID '(' timeout=Expression ')' ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? code=Code; @@ -456,7 +456,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 '=}' ; @@ -503,7 +503,7 @@ 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' | 'watchdog' + 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' | // 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 b4614edcf0..bd1b55026e 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -61,6 +61,8 @@ import org.lflang.lf.Variable; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; +//FIXME: modif4watchdogs +import org.lflang.lf.Watchdog; import org.lflang.lf.util.LfSwitch; /** @@ -397,7 +399,8 @@ public Boolean caseTypedVariable(TypedVariable object) { @Override public Boolean caseVariable(Variable object) { - throw thereIsAMoreSpecificCase(Variable.class, TypedVariable.class, Timer.class, Mode.class); + //FIXME: modif4watchdogs + throw thereIsAMoreSpecificCase(Variable.class, TypedVariable.class, Timer.class, Mode.class, Watchdog.class); } @Override diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index f53987e2b5..847bd7320d 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -113,6 +113,8 @@ import org.lflang.lf.Visibility; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; +//FIXME: modif4watchdogs +import org.lflang.lf.Watchdog; import org.lflang.util.FileUtil; import com.google.inject.Inject; From 0487e8e4df285f08c731649440cfd18189146aa4 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 17 Feb 2023 12:19:17 -0800 Subject: [PATCH 016/108] watchdog example test --- test/C/src/Watchdog.lf | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/C/src/Watchdog.lf diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf new file mode 100644 index 0000000000..9bdec7f63b --- /dev/null +++ b/test/C/src/Watchdog.lf @@ -0,0 +1,32 @@ +target C; + +reactor Watcher { + input x:int; + output d:int; // Produced if the deadline is violated. + + watchdog poodle(10 msec) {= + instant_t p = lf_time_physical_elapsed() - lf_time_logical_elapsed(); + printf("Deadline missed! Lag: %lld (too late by %lld nsecs)\n", p, p-500000); + =} + + reaction(x) -> d, poodle {= + lf_watchdog_start(poodle, 0); + printf("Normal reaction.\n"); + =} +} + +main reactor { + logical action a; + w = new Watcher(); + reaction(startup) -> w.x, a {= + lf_set(w.x, 0); + lf_schedule(a, 0); + =} + reaction(a) -> w.x {= + lf_set(w.x, 0); + lf_nanosleep(MSEC(40)); + =} + reaction(w.d) {= + printf("Deadline reactor produced an output.\n"); + =} +} From ca781252c166c17cf3af09b915c268731a5cb592 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 17 Feb 2023 13:38:17 -0800 Subject: [PATCH 017/108] saving reactorc and changes to scopeprovider --- org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java index ae268a9327..745345fc52 100644 --- a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java +++ b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java @@ -78,6 +78,7 @@ enum RefType { TRIGGER, SOURCE, EFFECT, + WATCHDOG, DEADLINE, CLEFT, CRIGHT @@ -240,6 +241,8 @@ protected IScope getScopeForVarRef(VarRef variable, EReference reference) { candidates.addAll(allActions(reactor)); return Scopes.scopeFor(candidates); } + case WATCHDOG: + return Scopes.scopeFor(allWatchdogs(reactor)); case DEADLINE: case CLEFT: return Scopes.scopeFor(allInputs(reactor)); From c36cc80b711ea6040fd1e9051a3d7e5d318e8759 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 17 Feb 2023 15:13:07 -0800 Subject: [PATCH 018/108] saving changes to scope provider --- org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java index ae268a9327..745345fc52 100644 --- a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java +++ b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java @@ -78,6 +78,7 @@ enum RefType { TRIGGER, SOURCE, EFFECT, + WATCHDOG, DEADLINE, CLEFT, CRIGHT @@ -240,6 +241,8 @@ protected IScope getScopeForVarRef(VarRef variable, EReference reference) { candidates.addAll(allActions(reactor)); return Scopes.scopeFor(candidates); } + case WATCHDOG: + return Scopes.scopeFor(allWatchdogs(reactor)); case DEADLINE: case CLEFT: return Scopes.scopeFor(allInputs(reactor)); From 94185e7bf134ee747cbb9e52557daa598c20c5b8 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Fri, 17 Feb 2023 16:04:53 -0800 Subject: [PATCH 019/108] Fixed the scope provider --- org.lflang/src/org/lflang/LinguaFranca.xtext | 2 +- org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 8b44ac6e1c..55f5fb0a97 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -217,7 +217,7 @@ Deadline: 'deadline' '(' delay=Expression ')' code=Code; Watchdog: - 'watchdog ' name=ID '(' timeout=Expression ')' + 'watchdog' name=ID '(' timeout=Expression ')' ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? code=Code; diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java index 745345fc52..c6bd97a622 100644 --- a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java +++ b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java @@ -227,6 +227,7 @@ protected IScope getScopeForVarRef(VarRef variable, EReference reference) { candidates.addAll(allInputs(reactor)); candidates.addAll(allActions(reactor)); candidates.addAll(allTimers(reactor)); + candidates.addAll(allWatchdogs(reactor)); return Scopes.scopeFor(candidates); } case SOURCE: @@ -239,10 +240,9 @@ protected IScope getScopeForVarRef(VarRef variable, EReference reference) { } 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)); From 17261240f413f68e1d34b9ee40f46564bb60c0d7 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 20 Feb 2023 15:41:14 -0800 Subject: [PATCH 020/108] fixed trigger table --- .../src/org/lflang/generator/c/CWatchdogGenerator.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 8bdf90b3fe..0ab08229c4 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -135,7 +135,7 @@ public static void generateWatchdogStruct( // WATCHDOG QUESTION 3: Is the space for this struct automatically allocated // through `_lf_new_reactor`? `_lf__startup_reaction` is also a pointer in self struct // but does not seem to have a separate allocation call. - body.pr(watchdog, "watchdog_t* _lf_watchdog_"+watchdogName+";"); + body.pr(watchdog, "watchdog_t _lf_watchdog_"+watchdogName+";"); // WATCHDOG QUESTION 4: Not sure if this is correct, may need to use // 'getTargetTime' instead. watchdog timeout is listed as "Expression" @@ -184,8 +184,9 @@ public static String generateBuiltinTriggersTable(int count, String name) { "// Array of pointers to "+name+" triggers.", (count > 0 ? "watchdog_t* _lf_"+name+"s["+count+"]" : + "watchdog_t* _lf_"+name+"s = NULL") + ";", "int _lf_"+name+"_number = "+count+";" - ))); + )); } /** @@ -198,7 +199,7 @@ public static String generateLfInitializeWatchdogMutexes(int watchdogCount) { if (watchdogCount > 0) { s.append("\n"); s.append(String.join("\n", - " for (int i = 0; i < _lf_watchdog_numer; i++) {", + " 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));", From 6994d4633605b8b29448c832721ac24475670bb2 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Sun, 26 Feb 2023 15:05:32 -0800 Subject: [PATCH 021/108] saved? --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 7fd67a5e59..b97d084109 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 7fd67a5e599f36d65ab21600e228af7c8217ac98 +Subproject commit b97d08410979bdb2466d6714743bca8d1dc24c76 From 3393c588b42e34b9c5ddc193c2d558262c1aba80 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 3 Mar 2023 15:40:46 -0800 Subject: [PATCH 022/108] fixed compile issues --- base | 0 org.lflang.tests/.classpath | 19 ++++++------------- org.lflang.tests/.project | 19 ++++++++++++++++++- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/lib/rs/reactor-rs | 2 +- .../src/lib/rs/runtime-version.properties | 2 +- .../org/lflang/generator/c/CGenerator.java | 7 +++---- .../generator/c/CWatchdogGenerator.java | 8 +++++--- thread_active | 0 watchdog_function | 0 10 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 base create mode 100644 thread_active create mode 100644 watchdog_function diff --git a/base b/base new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.lflang.tests/.classpath b/org.lflang.tests/.classpath index e46db665de..e025b430eb 100644 --- a/org.lflang.tests/.classpath +++ b/org.lflang.tests/.classpath @@ -1,20 +1,13 @@ - - - - - - - - - - - - + + + - + + + diff --git a/org.lflang.tests/.project b/org.lflang.tests/.project index f17ca7cd7c..7acfd6f031 100644 --- a/org.lflang.tests/.project +++ b/org.lflang.tests/.project @@ -5,13 +5,18 @@ + + org.eclipse.jdt.core.javabuilder + + + org.eclipse.xtext.ui.shared.xtextBuilder - org.eclipse.jdt.core.javabuilder + org.eclipse.buildship.core.gradleprojectbuilder @@ -30,5 +35,17 @@ org.eclipse.xtext.ui.shared.xtextNature org.eclipse.jdt.core.javanature org.eclipse.pde.PluginNature + org.eclipse.buildship.core.gradleprojectnature + + + 1677834823336 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index c00691a80a..8a8827fa77 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit c00691a80a20ff48486bc0c42d5da6ea175c1eed +Subproject commit 8a8827fa77dc935676fbc148ecd83fffd3feb151 diff --git a/org.lflang/src/lib/rs/reactor-rs b/org.lflang/src/lib/rs/reactor-rs index 28f9460724..7685764194 160000 --- a/org.lflang/src/lib/rs/reactor-rs +++ b/org.lflang/src/lib/rs/reactor-rs @@ -1 +1 @@ -Subproject commit 28f9460724b266c263f4fdf8146b2029f4ff9d30 +Subproject commit 76857641941aa49c766ba23da63db4f7650a4c77 diff --git a/org.lflang/src/lib/rs/runtime-version.properties b/org.lflang/src/lib/rs/runtime-version.properties index 35b335b731..4449aaf2fd 100644 --- a/org.lflang/src/lib/rs/runtime-version.properties +++ b/org.lflang/src/lib/rs/runtime-version.properties @@ -1 +1 @@ -rs = 28f9460724b266c263f4fdf8146b2029f4ff9d30 +rs = 76857641941aa49c766ba23da63db4f7650a4c77 diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index f2a74c3b5b..3e0cbdb590 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1518,11 +1518,10 @@ private void recordWatchdogs(ReactorInstance instance) { var foundOne = false; var temp = new CodeBuilder(); var reactorRef = CUtil.reactorRef(instance); - for (WatchdogInstance watchdog : instance.watchdogs) { - temp.pr("_lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); - temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); - temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); + temp.pr(" _lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); + temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); + temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); watchdogCount += 1; foundOne = true; } diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 0ab08229c4..5b0343ecc7 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -182,10 +182,12 @@ public static String generateWatchdog( 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+";" + " watchdog_t* _lf_"+name+"s["+count+"]" : + " watchdog_t* _lf_"+name+"s = NULL") + ";", + " int _lf_"+name+"_number = "+count+";", + "#endif" )); } diff --git a/thread_active b/thread_active new file mode 100644 index 0000000000..e69de29bb2 diff --git a/watchdog_function b/watchdog_function new file mode 100644 index 0000000000..e69de29bb2 From 976b1026641b525db07b23143f16fb8a4826a0f6 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 8 Mar 2023 16:57:48 -0800 Subject: [PATCH 023/108] added casewatchdogs, still not fixed --- org.lflang/src/org/lflang/ASTUtils.java | 5 +++ org.lflang/src/org/lflang/ast/IsEqual.java | 12 ++++++++ org.lflang/src/org/lflang/ast/ToLf.java | 36 ++++++++++++++++++++++ test/C/src/Watchdog.lf | 2 +- 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index fe1ba25536..896765f459 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -1075,6 +1075,11 @@ public static InferredType getInferredType(Port p) { return getInferredType(p.getType(), null); } + //FIXME: modif4watchdogs + // public static InferredType getInferredType(Watchdog w) { + // return getInferredType(w.getType(), null); + // } + /** diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index bd1b55026e..1af1eed81a 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -394,6 +394,7 @@ public Boolean caseElement(Element object) { @Override public Boolean caseTypedVariable(TypedVariable object) { + //FIXME: modif4watchdogs throw thereIsAMoreSpecificCase(TypedVariable.class, Port.class, Action.class); } @@ -490,6 +491,17 @@ public Boolean caseArraySpec(ArraySpec object) { .conclusion; } + //FIXME: modif4watchdogs + @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) diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 7582ee3a59..03a08ec4b8 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -73,6 +73,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; @@ -653,6 +654,41 @@ public MalleableString caseDeadline(Deadline object) { return handler(object, "deadline", Deadline::getDelay, Deadline::getCode); } + //FIXME: modif4watchdogs + @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 diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index 9bdec7f63b..3ae61579f8 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -9,7 +9,7 @@ reactor Watcher { printf("Deadline missed! Lag: %lld (too late by %lld nsecs)\n", p, p-500000); =} - reaction(x) -> d, poodle {= + reaction(x) -> poodle, d {= lf_watchdog_start(poodle, 0); printf("Normal reaction.\n"); =} From 0d31103fbe5d0c342b2263a5a02de781fb9837f9 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 8 Mar 2023 18:22:49 -0800 Subject: [PATCH 024/108] Also produce code for watchdogs in reactor definition --- org.lflang/src/org/lflang/ast/ToLf.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 03a08ec4b8..6f9d369dcc 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -391,6 +391,7 @@ public MalleableString caseReactor(Reactor object) { // | (outputs+=Output) // | (timers+=Timer) // | (actions+=Action) + // | (watchdogs+=Watchdog) // | (instantiations+=Instantiation) // | (connections+=Connection) // | (reactions+=Reaction) @@ -408,6 +409,7 @@ public MalleableString caseReactor(Reactor object) { object.getOutputs(), object.getTimers(), object.getActions(), + object.getWatchdogs(), object.getInstantiations(), object.getConnections(), object.getStateVars()), From 59b3737d10fa7353d95a4a577bf6d9e0b8be2856 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 8 Mar 2023 18:29:30 -0800 Subject: [PATCH 025/108] Apply formatter to test --- test/C/src/Watchdog.lf | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index 3ae61579f8..0dd2436478 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -1,8 +1,8 @@ -target C; +target C reactor Watcher { - input x:int; - output d:int; // Produced if the deadline is violated. + input x: int + output d: int // Produced if the deadline is violated. watchdog poodle(10 msec) {= instant_t p = lf_time_physical_elapsed() - lf_time_logical_elapsed(); @@ -12,21 +12,22 @@ reactor Watcher { reaction(x) -> poodle, d {= lf_watchdog_start(poodle, 0); printf("Normal reaction.\n"); - =} + =} } main reactor { - logical action a; - w = new Watcher(); + logical action a + w = new Watcher() + reaction(startup) -> w.x, a {= lf_set(w.x, 0); lf_schedule(a, 0); =} + reaction(a) -> w.x {= lf_set(w.x, 0); lf_nanosleep(MSEC(40)); =} - reaction(w.d) {= - printf("Deadline reactor produced an output.\n"); - =} + + reaction(w.d) {= printf("Deadline reactor produced an output.\n"); =} } From 9d83f55b725cedec83e92231eb753e5cec5b5bf7 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 8 Mar 2023 20:46:56 -0800 Subject: [PATCH 026/108] Fix submodule issues and remove files that should not be checked in --- .gitmodules | 3 ++ org.lflang.tests/.classpath | 13 ----- org.lflang.tests/.project | 51 ------------------- org.lflang/src/lib/cpp/reactor-cpp | 1 + org.lflang/src/lib/rs/reactor-rs | 2 +- .../src/lib/rs/runtime-version.properties | 2 +- 6 files changed, 6 insertions(+), 66 deletions(-) delete mode 100644 org.lflang.tests/.classpath delete mode 100644 org.lflang.tests/.project create mode 160000 org.lflang/src/lib/cpp/reactor-cpp diff --git a/.gitmodules b/.gitmodules index fdbf50ba8d..2231bc265a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "org.lflang/src/lib/rs/reactor-rs"] path = org.lflang/src/lib/rs/reactor-rs url = https://github.com/lf-lang/reactor-rs +[submodule "org.lflang/src/lib/cpp/reactor-cpp"] + path = org.lflang/src/lib/cpp/reactor-cpp + url = https://github.com/lf-lang/reactor-cpp.git diff --git a/org.lflang.tests/.classpath b/org.lflang.tests/.classpath deleted file mode 100644 index e025b430eb..0000000000 --- a/org.lflang.tests/.classpath +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/org.lflang.tests/.project b/org.lflang.tests/.project deleted file mode 100644 index 7acfd6f031..0000000000 --- a/org.lflang.tests/.project +++ /dev/null @@ -1,51 +0,0 @@ - - - org.lflang.tests - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.xtext.ui.shared.xtextBuilder - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.xtext.ui.shared.xtextNature - org.eclipse.jdt.core.javanature - org.eclipse.pde.PluginNature - org.eclipse.buildship.core.gradleprojectnature - - - - 1677834823336 - - 30 - - org.eclipse.core.resources.regexFilterMatcher - node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ - - - - diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp new file mode 160000 index 0000000000..e5d7cfd1f8 --- /dev/null +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -0,0 +1 @@ +Subproject commit e5d7cfd1f89846bc928da86c38a33bb17e1c049b diff --git a/org.lflang/src/lib/rs/reactor-rs b/org.lflang/src/lib/rs/reactor-rs index 7685764194..28f9460724 160000 --- a/org.lflang/src/lib/rs/reactor-rs +++ b/org.lflang/src/lib/rs/reactor-rs @@ -1 +1 @@ -Subproject commit 76857641941aa49c766ba23da63db4f7650a4c77 +Subproject commit 28f9460724b266c263f4fdf8146b2029f4ff9d30 diff --git a/org.lflang/src/lib/rs/runtime-version.properties b/org.lflang/src/lib/rs/runtime-version.properties index 4449aaf2fd..35b335b731 100644 --- a/org.lflang/src/lib/rs/runtime-version.properties +++ b/org.lflang/src/lib/rs/runtime-version.properties @@ -1 +1 @@ -rs = 76857641941aa49c766ba23da63db4f7650a4c77 +rs = 28f9460724b266c263f4fdf8146b2029f4ff9d30 From ec8afa7cf2f1ecab29b678f15e4263b1003f04c5 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 8 Mar 2023 20:48:07 -0800 Subject: [PATCH 027/108] Remove spurious file --- base | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 base diff --git a/base b/base deleted file mode 100644 index e69de29bb2..0000000000 From da5b43a1182f678e7ed6f52f277592d6b63a6e27 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 8 Mar 2023 20:48:54 -0800 Subject: [PATCH 028/108] Change order of submodules --- .gitmodules | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 2231bc265a..efb73c9f3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,9 @@ [submodule "org.lflang/src/lib/py/reactor-c-py"] path = org.lflang/src/lib/py/reactor-c-py url = https://github.com/lf-lang/reactor-c-py.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 [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = org.lflang/src/lib/cpp/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 From 9460c1a990724fb2d0a35886da65a7643b66cee3 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 8 Mar 2023 20:51:14 -0800 Subject: [PATCH 029/108] Let version of reactor-cpp be the same as in master --- org.lflang/src/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp index e5d7cfd1f8..a37f0ade9e 160000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit e5d7cfd1f89846bc928da86c38a33bb17e1c049b +Subproject commit a37f0ade9ee0f97606528fd7ba67712495948373 From 043cfbcbe06d54b9de0fbd189339ed630913089d Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 8 Mar 2023 20:56:48 -0800 Subject: [PATCH 030/108] Make submodule entries uniform --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index efb73c9f3d..274bffab2c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,4 @@ 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 From 41c543065055e98b36f5b720d8faa4302dc5c678 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 8 Mar 2023 23:03:10 -0800 Subject: [PATCH 031/108] Update org.lflang/src/org/lflang/ast/ToLf.java Co-authored-by: Marten Lohstroh --- org.lflang/src/org/lflang/ast/ToLf.java | 1 - 1 file changed, 1 deletion(-) diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 6f9d369dcc..637a555a24 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -656,7 +656,6 @@ public MalleableString caseDeadline(Deadline object) { return handler(object, "deadline", Deadline::getDelay, Deadline::getCode); } - //FIXME: modif4watchdogs @Override public MalleableString caseWatchdog(Watchdog object) { // 'watchdog' name=ID '(' timeout=Expression ')' From 89abf744dc967a9ad56098e2deecd6269276063e Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 8 Mar 2023 23:03:24 -0800 Subject: [PATCH 032/108] Update org.lflang/src/org/lflang/ast/IsEqual.java Co-authored-by: Marten Lohstroh --- org.lflang/src/org/lflang/ast/IsEqual.java | 1 - 1 file changed, 1 deletion(-) diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 1af1eed81a..0d2eab148e 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -400,7 +400,6 @@ public Boolean caseTypedVariable(TypedVariable object) { @Override public Boolean caseVariable(Variable object) { - //FIXME: modif4watchdogs throw thereIsAMoreSpecificCase(Variable.class, TypedVariable.class, Timer.class, Mode.class, Watchdog.class); } From e4be4d56a8bfe28095d0d2dd49f8ad390c93533a Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 8 Mar 2023 23:03:30 -0800 Subject: [PATCH 033/108] Update org.lflang/src/org/lflang/ast/IsEqual.java Co-authored-by: Marten Lohstroh --- org.lflang/src/org/lflang/ast/IsEqual.java | 1 - 1 file changed, 1 deletion(-) diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 0d2eab148e..16312eb73a 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -490,7 +490,6 @@ public Boolean caseArraySpec(ArraySpec object) { .conclusion; } - //FIXME: modif4watchdogs @Override public Boolean caseWatchdog(Watchdog object) { return new ComparisonMachine<>(object, Watchdog.class) From a81d25b66e9649172e1c2042afea9c0bdc974976 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 8 Mar 2023 23:19:42 -0800 Subject: [PATCH 034/108] removed fixmes, updated grammar --- org.lflang/src/org/lflang/ASTUtils.java | 11 ----------- org.lflang/src/org/lflang/LinguaFranca.xtext | 2 +- org.lflang/src/org/lflang/ast/IsEqual.java | 4 ---- org.lflang/src/org/lflang/ast/ToLf.java | 1 - .../federated/generator/FederateInstance.java | 19 ------------------- .../org/lflang/generator/ReactorInstance.java | 4 ---- .../lflang/generator/WatchdogInstance.java | 2 -- .../org/lflang/generator/c/CGenerator.java | 11 ----------- .../generator/c/CReactionGenerator.java | 4 ---- .../generator/c/CWatchdogGenerator.java | 2 +- .../org/lflang/validation/LFValidator.java | 1 - 11 files changed, 2 insertions(+), 59 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 896765f459..e027ad41dc 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -91,7 +91,6 @@ import org.lflang.lf.VarRef; import org.lflang.lf.Variable; -//FIXME: modif4watchdogs import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; @@ -125,8 +124,6 @@ public class ASTUtils { /** * A mapping from Reactor features to corresponding Mode features for collecting contained elements. */ - //FIXME: modif4watchdogs - // added 'featurePackage.getReactor_Watchdogs' private static final Map reactorModeFeatureMap = Map.of( featurePackage.getReactor_Actions(), featurePackage.getMode_Actions(), featurePackage.getReactor_Connections(), featurePackage.getMode_Connections(), @@ -423,7 +420,6 @@ public static List allReactions(Reactor definition) { * @param definition Reactor class definition * @return List */ - // FIXME: modif4watchdogs public static List allWatchdogs(Reactor definition) { return ASTUtils.collectElements(definition, featurePackage.getReactor_Watchdogs()); } @@ -1075,13 +1071,6 @@ public static InferredType getInferredType(Port p) { return getInferredType(p.getType(), null); } - //FIXME: modif4watchdogs - // public static InferredType getInferredType(Watchdog w) { - // return getInferredType(w.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. diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 55f5fb0a97..98dbb77e1e 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -87,11 +87,11 @@ Reactor: | (outputs+=Output) | (timers+=Timer) | (actions+=Action) + | (watchdogs+=Watchdog) | (instantiations+=Instantiation) | (connections+=Connection) | (reactions+=Reaction) | (modes+=Mode) - | (watchdogs+=Watchdog) )* '}'; diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 1af1eed81a..08c54a999b 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -61,7 +61,6 @@ import org.lflang.lf.Variable; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; -//FIXME: modif4watchdogs import org.lflang.lf.Watchdog; import org.lflang.lf.util.LfSwitch; @@ -394,13 +393,11 @@ public Boolean caseElement(Element object) { @Override public Boolean caseTypedVariable(TypedVariable object) { - //FIXME: modif4watchdogs throw thereIsAMoreSpecificCase(TypedVariable.class, Port.class, Action.class); } @Override public Boolean caseVariable(Variable object) { - //FIXME: modif4watchdogs throw thereIsAMoreSpecificCase(Variable.class, TypedVariable.class, Timer.class, Mode.class, Watchdog.class); } @@ -491,7 +488,6 @@ public Boolean caseArraySpec(ArraySpec object) { .conclusion; } - //FIXME: modif4watchdogs @Override public Boolean caseWatchdog(Watchdog object) { return new ComparisonMachine<>(object, Watchdog.class) diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 6f9d369dcc..637a555a24 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -656,7 +656,6 @@ public MalleableString caseDeadline(Deadline object) { return handler(object, "deadline", Deadline::getDelay, Deadline::getCode); } - //FIXME: modif4watchdogs @Override public MalleableString caseWatchdog(Watchdog object) { // 'watchdog' name=ID '(' timeout=Expression ')' diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index f13313b033..af581cc74d 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -464,25 +464,6 @@ private boolean contains(Timer timer) { } return false; } - - //FIXME:modif4watchdogs - - // public boolean contains(Watchdog watchdog) { - // Reactor reactor = ASTUtils.getEnclosingReactor(watchdog); - // if (!reactor.isFederated() || this.isSingleton()) { - // return true; - // } - - // if (!reactor.getWatchdogs().contains(watchdog)) { - // return false; - // } - - // if (networkReactions.contains(watchdog)) { - // return true; - // } - - // return false; - // } /** * Return true if the specified reactor instance or any parent diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 06acad45dc..19859cb8a0 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -63,7 +63,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Variable; import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; -//FIXME: modif4watchdogs import org.lflang.lf.Watchdog; @@ -148,7 +147,6 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re public final List reactions = new ArrayList<>(); /** List of watchdog instances for this reactor instance. */ - //FIXME: modif4watchdogs public final List watchdogs = new ArrayList<>(); /** The timer instances belonging to this reactor instance. */ @@ -753,7 +751,6 @@ protected void createReactionInstances() { /** * Create all the watchdog instances of this reactor instance. */ - // FIXME: modif4watchdogs protected void createWatchdogInstances() { List watchdogs = ASTUtils.allWatchdogs(reactorDefinition); if (watchdogs != null) { @@ -876,7 +873,6 @@ private ReactorInstance( createReactionInstances(); // Create the reaction instances in this reactor instance. - // FIXME: modif4watchdogs createWatchdogInstances(); // Instantiate modes for this reactor instance diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index 780113a80f..2a2e11a3ad 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -10,8 +10,6 @@ * * @author{Benjamin Asch } */ -//FIXME: modif4watchdogs -//FIXME: functions in constructor not defined public class WatchdogInstance { /** diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index aa64cd5fba..d34e58ebfc 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -80,7 +80,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; -// FIXME: modif4watchdogs import org.lflang.generator.WatchdogInstance; import org.lflang.generator.SubContext; @@ -105,7 +104,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Watchdog; import org.lflang.util.FileUtil; -// FIXME: modif4watchdogs import org.lflang.lf.Watchdog; import org.lflang.util.LFCommand; @@ -365,7 +363,6 @@ public class CGenerator extends GeneratorBase { private int resetReactionCount = 0; private int modalReactorCount = 0; private int modalStateResetCount = 0; - // FIXME: modif4watchdogs private int watchdogCount = 0; // Indicate whether the generator is in Cpp mode or not @@ -709,7 +706,6 @@ private void generateCodeFor( // If there are reset reactions, create a table of triggers. code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); - //FIXME: modif4watchdogs // If there are watchdogs, create a table of triggers. code.pr(CWatchdogGenerator.generateBuiltinTriggersTable(watchdogCount, "watchdog")); @@ -737,7 +733,6 @@ private void generateCodeFor( // Generate function to schedule timers for all reactors. code.pr(CTimerGenerator.generateLfInitializeTimer(timerCount)); - //FIXME:modif4watchdogs // Generate function to initialize mutexes for all reactors with watchdogs. // needs to be implemented still code.pr(CWatchdogGenerator.generateLfInitializeWatchdogMutexes(watchdogCount)); @@ -827,7 +822,6 @@ private boolean hasDeadlines(List reactors) { return false; } - //FIXME: modif4watchdogs private boolean hasWatchdogs(Reactor reactor) { if (ASTUtils.allWatchdogs(reactor) != null) { return true; @@ -1072,7 +1066,6 @@ private void generateReactorClass(ReactorDecl reactor) { generateSelfStruct(reactor, constructorCode); generateMethods(reactor); - // FIXME: modif4watchdogs generateWatchdogs(reactor); generateReactions(reactor); generateConstructor(reactor, constructorCode); @@ -1225,7 +1218,6 @@ private void generateSelfStruct(ReactorDecl decl, CodeBuilder constructorCode) { ); // Generate the fields needed for each watchdog. - // FIXME: modif4watchdogs CWatchdogGenerator.generateWatchdogStruct( body, decl, @@ -1435,7 +1427,6 @@ protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactio )); } - // FIXME: modif4watchdogs /** 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), @@ -1513,7 +1504,6 @@ private void recordBuiltinTriggers(ReactorInstance instance) { } } - // FIXME: modif4watchdogs private void recordWatchdogs(ReactorInstance instance) { var foundOne = false; var temp = new CodeBuilder(); @@ -1774,7 +1764,6 @@ public void generateReactorInstance(ReactorInstance instance) { initializeOutputMultiports(instance); initializeInputMultiports(instance); recordBuiltinTriggers(instance); - // FIXME: modif4watchdogs recordWatchdogs(instance); // Next, initialize the "self" struct with state variables. diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 6eaa7506b1..6959d52937 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -36,7 +36,6 @@ import org.lflang.lf.Variable; import org.lflang.lf.Watchdog; import org.lflang.util.StringUtil; -// FIXME: modif4watchdogs import org.lflang.lf.Watchdog; public class CReactionGenerator { @@ -199,7 +198,6 @@ public static String generateInitializationForReaction(String body, (Input) variable ); } else if (variable instanceof Watchdog) { - //FIXME: modif4watchdogs reactionInitialization.pr(generateWatchdogVariablesInReaction( effect, decl @@ -678,7 +676,6 @@ public static String generateOutputVariablesInReaction( * @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. */ - //FIXME: modif4watchdogs // Fine to have watchdog be in reactor self struct? public static String generateWatchdogVariablesInReaction( VarRef effect, @@ -1185,7 +1182,6 @@ public static String generateStpFunctionHeader(ReactorDecl decl, return generateFunctionHeader(functionName); } - //FIXME: modif4watchdogs (changed from private to public to access in CWatchdogGenerator) 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 index 5b0343ecc7..4078a98688 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -21,7 +21,7 @@ * * @author{Benjamin Asch } */ -//FIXME: modif4watchdogs + public class CWatchdogGenerator { /** diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 847bd7320d..fa6d8b06fc 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -113,7 +113,6 @@ import org.lflang.lf.Visibility; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; -//FIXME: modif4watchdogs import org.lflang.lf.Watchdog; import org.lflang.util.FileUtil; From 88dbef7eed634fd02e8a8f0ecd451e91a689a36d Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 8 Mar 2023 23:57:29 -0800 Subject: [PATCH 035/108] fixed ccpp test issue? --- org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 4078a98688..8b536a9ef8 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -153,7 +153,7 @@ public static void generateWatchdogStruct( "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+".watchdog_function = &("+watchdogFunctionName+");" )); // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? From 5670e27cc3051381b0becd2f1ec2c3c39b2a81a9 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 13 Mar 2023 11:46:09 -0700 Subject: [PATCH 036/108] saving before pull again --- .github/actions/prepare-build-env/action.yml | 6 +- .github/scripts/test-lfc.sh | 2 +- .github/workflows/c-zephyr-tests.yml | 2 +- org.lflang/src/org/lflang/cli/CliBase.java | 23 +---- org.lflang/src/org/lflang/cli/Lfc.java | 98 ++++++++----------- org.lflang/src/org/lflang/cli/Lff.java | 12 --- .../org/lflang/generator/GeneratorBase.java | 1 - .../org/lflang/generator/GeneratorUtils.java | 68 +------------ .../lflang/generator/c/CCmakeGenerator.java | 5 - .../generator/cpp/CppRos2NodeGenerator.kt | 3 +- .../cpp/CppStandaloneMainGenerator.kt | 4 +- .../org/lflang/validation/LFValidator.java | 15 ++- test/Cpp/src/concurrent/AsyncCallback.lf | 1 - test/Cpp/src/concurrent/AsyncCallback2.lf | 1 - 14 files changed, 66 insertions(+), 175 deletions(-) diff --git a/.github/actions/prepare-build-env/action.yml b/.github/actions/prepare-build-env/action.yml index e0b8f8bfb0..16cfc3c410 100644 --- a/.github/actions/prepare-build-env/action.yml +++ b/.github/actions/prepare-build-env/action.yml @@ -15,12 +15,16 @@ runs: cat gradle.properties echo $JAVA_HOME shell: bash + - name: Create hash of Gradle configuration + run: | + echo "gradle-hash"="$(find . -type f \( -name "gradle.properties" -o -name "gradle-wrapper.properties" \) -exec cat {} + | sha256sum | cut -d ' ' -f 1)" >> $GITHUB_ENV + shell: bash - name: Cache uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper - key: gradle-${{ runner.os }}-${{ hashFiles('**/gradle-wrapper.properties') }} + key: gradle-${{ runner.os }}-${{ env.gradle-hash }} # restore-keys: | # ${{ runner.os }}-gradle- diff --git a/.github/scripts/test-lfc.sh b/.github/scripts/test-lfc.sh index 20c087db81..a59d4acd3b 100755 --- a/.github/scripts/test-lfc.sh +++ b/.github/scripts/test-lfc.sh @@ -50,7 +50,7 @@ bin/lfc --output-path . test/C/src/Minimal.lf # --runtime-version Specify the version of the runtime # library used for compiling LF # programs. -bin/lfc --runtime-version f157be30bbeab0e2a991f29f9c7f388ca39681a7 test/Cpp/src/Minimal.lf +bin/lfc --runtime-version e80cd36ce5bd625a7b167e7dfd65d25f78b0dd01 test/Cpp/src/Minimal.lf # -w,--workers Specify the default number of worker threads. bin/lfc -w 2 test/C/src/Minimal.lf diff --git a/.github/workflows/c-zephyr-tests.yml b/.github/workflows/c-zephyr-tests.yml index cb3b3a4657..21fe4db2c4 100644 --- a/.github/workflows/c-zephyr-tests.yml +++ b/.github/workflows/c-zephyr-tests.yml @@ -21,7 +21,7 @@ jobs: run: runs-on: ubuntu-latest container: - image: zephyrprojectrtos/zephyr-build:latest + image: zephyrprojectrtos/zephyr-build:v0.24.13 options: -u root --entrypoint /bin/sh steps: - name: Install Java 17, Maven and set JAVA_HOME diff --git a/org.lflang/src/org/lflang/cli/CliBase.java b/org.lflang/src/org/lflang/cli/CliBase.java index 1a8a26b8c6..77581a6410 100644 --- a/org.lflang/src/org/lflang/cli/CliBase.java +++ b/org.lflang/src/org/lflang/cli/CliBase.java @@ -1,22 +1,15 @@ package org.lflang.cli; import java.io.IOException; -import java.io.PrintStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; -import java.util.Properties; import java.util.stream.Collectors; import picocli.CommandLine; -import picocli.CommandLine.Command; -import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; -import picocli.CommandLine.ParseResult; -import picocli.CommandLine.Spec; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; @@ -29,8 +22,6 @@ import org.lflang.ErrorReporter; import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; -import org.lflang.LocalStrings; -import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.util.FileUtil; import com.google.inject.Inject; import com.google.inject.Injector; @@ -45,11 +36,6 @@ * @author Atharva Patil */ public abstract class CliBase implements Runnable { - /** - * Models a command specification, including the options, positional - * parameters and subcommands supported by the command. - */ - @Spec CommandSpec spec; /** * Options and parameters present in both Lfc and Lff. @@ -65,8 +51,8 @@ public abstract class CliBase implements Runnable { defaultValue = "", fallbackValue = "", description = "Specify the root output directory.") - private Path outputPath; + /** * Used to collect all errors that happen during validation/generation. */ @@ -103,13 +89,6 @@ public abstract class CliBase implements Runnable { @Inject private IResourceValidator validator; - /** Name of the program, eg "lfc". */ - private final String toolName; - - protected CliBase(String toolName) { - this.toolName = toolName; - } - protected static void cliMain( String toolName, Class toolClass, Io io, String[] args) { diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index ffafb9d824..489e9cd2f4 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -1,20 +1,12 @@ package org.lflang.cli; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; import java.util.Properties; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import picocli.CommandLine; import picocli.CommandLine.Command; -import picocli.CommandLine.Model.OptionSpec; import picocli.CommandLine.Option; -import picocli.CommandLine.Parameters; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.GeneratorDelegate; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; @@ -54,15 +46,8 @@ public class Lfc extends CliBase { @Inject private JavaIoFileSystemAccess fileAccess; - public Lfc() { - super("lfc"); - } - - /** + /* * Supported CLI options. - * - * @author Marten Lohstroh - * @author Atharva Patil */ @Option( @@ -101,7 +86,7 @@ public Lfc() { @Option( names = {"-l", "--lint"}, arity = "0", - description = "Enable or disable linting of generated code.") + description = "Enable linting of generated code.") private boolean lint; @Option( @@ -143,7 +128,7 @@ public Lfc() { @Option( names = {"-w", "--workers"}, description = "Specify the default number of worker threads.") - private int workers; + private Integer workers; /** * Main function of the stand-alone compiler. @@ -245,47 +230,48 @@ private Path getActualOutputPath(Path root, Path path) { * @return Properties for the code generator. */ protected Properties filterPassOnProps() { - // Parameters corresponding to the options that need to be passed on to - // the generator as properties. - final Set passOnParams = Stream.of( - BuildParm.BUILD_TYPE, - BuildParm.CLEAN, - BuildParm.TARGET_COMPILER, - BuildParm.EXTERNAL_RUNTIME_PATH, - BuildParm.LOGGING, - BuildParm.LINT, - BuildParm.NO_COMPILE, - BuildParm.QUIET, - BuildParm.RTI, - BuildParm.RUNTIME_VERSION, - BuildParm.SCHEDULER, - BuildParm.THREADING, - BuildParm.WORKERS) - .map(param -> param.getKey()) - .collect(Collectors.toUnmodifiableSet()); - Properties props = new Properties(); - for (OptionSpec option : spec.options()) { - String optionName = option.longestName(); - // Check whether this option needs to be passed on to the code - // generator as a property. - if (passOnParams.contains(optionName)) { - String value = ""; - // Boolean or Integer option. - if (option.getValue() instanceof Boolean || - option.getValue() instanceof Integer) { - value = String.valueOf(option.getValue()); - // String option. - } else if (option.getValue() instanceof String) { - value = option.getValue(); - // Path option. - } else if (option.getValue() instanceof Path) { - value = option.getValue().toString(); - } - props.setProperty(optionName, value); - } + if (buildType != null) { + props.setProperty(BuildParm.BUILD_TYPE.getKey(), buildType); + } + if (clean) { + props.setProperty(BuildParm.CLEAN.getKey(), "true"); + } + if (externalRuntimePath != null) { + props.setProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey(), externalRuntimePath.toString()); + } + if (lint) { + props.setProperty(BuildParm.LINT.getKey(), "true"); + } + if (logging != null) { + props.setProperty(BuildParm.LOGGING.getKey(), logging); + } + if (noCompile) { + props.setProperty(BuildParm.NO_COMPILE.getKey(), "true"); + } + if (targetCompiler != null) { + props.setProperty(BuildParm.TARGET_COMPILER.getKey(), targetCompiler); + } + if (quiet) { + props.setProperty(BuildParm.QUIET.getKey(), "true"); + } + if (rti != null) { + props.setProperty(BuildParm.RTI.getKey(), rti); + } + if (runtimeVersion != null) { + props.setProperty(BuildParm.RUNTIME_VERSION.getKey(), runtimeVersion); + } + if (scheduler != null) { + props.setProperty(BuildParm.SCHEDULER.getKey(), scheduler); + } + if (threading != null) { + props.setProperty(BuildParm.THREADING.getKey(), threading); + } + if (workers != null) { + props.setProperty(BuildParm.WORKERS.getKey(), workers.toString()); } + return props; } } diff --git a/org.lflang/src/org/lflang/cli/Lff.java b/org.lflang/src/org/lflang/cli/Lff.java index 1c93c39d8a..0eeeea6785 100644 --- a/org.lflang/src/org/lflang/cli/Lff.java +++ b/org.lflang/src/org/lflang/cli/Lff.java @@ -3,25 +3,17 @@ import java.io.IOException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; -import java.util.Arrays; import java.util.List; -import java.util.Properties; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; -import picocli.CommandLine.Parameters; import org.eclipse.emf.ecore.resource.Resource; import org.lflang.ast.FormattingUtils; -import org.lflang.LocalStrings; import org.lflang.util.FileUtil; /** @@ -67,10 +59,6 @@ public class Lff extends CliBase { description = "Print more details on files affected.") private boolean verbose = false; - public Lff() { - super("lff"); - } - /** * Main function of the formatter. * Caution: this will invoke System.exit. diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index 95427b1175..0a9c2064d7 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -267,7 +267,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // to validate, which happens in setResources(). setReactorsAndInstantiationGraph(context.getMode()); - GeneratorUtils.validate(context, context.getFileConfig(), instantiationGraph, errorReporter); 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()) diff --git a/org.lflang/src/org/lflang/generator/GeneratorUtils.java b/org.lflang/src/org/lflang/generator/GeneratorUtils.java index f51439c390..ec6e167ef2 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.java @@ -156,9 +156,9 @@ public static void accommodatePhysicalActionsIfPresent( } for (Resource resource : resources) { for (Action action : findAll(resource, Action.class)) { - if (action.getOrigin() == ActionOrigin.PHYSICAL && + if (action.getOrigin() == ActionOrigin.PHYSICAL && // Check if the user has explicitly set keepalive to false - !targetConfig.setByUser.contains(TargetProperty.KEEPALIVE) && + !targetConfig.setByUser.contains(TargetProperty.KEEPALIVE) && !targetConfig.keepalive ) { // If not, set it to true @@ -214,70 +214,6 @@ public T next() { }; } - /** - * Validate the files containing reactors in the given - * {@code instantiationGraph}. If a file is imported by - * another file in the instantiation graph, propagate the - * resulting errors to the importing file. - * @param context The context providing the cancel - * indicator used by the validator. - * @param fileConfig The file system configuration. - * @param instantiationGraph A DAG containing all - * reactors of interest. - * @param errorReporter An error acceptor. - */ - public static void validate( - IGeneratorContext context, - FileConfig fileConfig, - InstantiationGraph instantiationGraph, - ErrorReporter errorReporter - ) { - // NOTE: This method was previously misnamed validateImports. - // It validates all files, including the main file that does the importing. - // Also, it is now the only invocation of validation during code generation, - // and yet it used to only report errors in the files doing the importing. - IResourceValidator validator = ((XtextResource) fileConfig.resource).getResourceServiceProvider() - .getResourceValidator(); - HashSet bad = new HashSet<>(); - HashSet visited = new HashSet<>(); - // The graph must be traversed in topological order so that errors will propagate through arbitrarily many - // levels. - for (Reactor reactor : instantiationGraph.nodesInTopologicalOrder()) { - Resource resource = reactor.eResource(); - if (visited.contains(resource)) continue; - visited.add(resource); - List issues = validator.validate(resource, CheckMode.ALL, context.getCancelIndicator()); - if ( - bad.contains(resource) || issues.size() > 0 - ) { - // Report the error on this resource. - Path path = null; - try { - path = FileUtil.toPath(resource); - } catch (IOException e) { - path = Paths.get("Unknown file"); // Not sure if this is what we want. - } - for (Issue issue : issues) { - errorReporter.reportError(path, issue.getLineNumber(), issue.getMessage()); - } - - // Report errors on resources that import this one. - for (Reactor downstreamReactor : instantiationGraph.getDownstreamAdjacentNodes(reactor)) { - for (Import importStatement : ((Model) downstreamReactor.eContainer()).getImports()) { - // FIXME: This will report the error on ALL import statements in - // file doing the importing, not just the one importing this resource. - // I have no idea how to determine which import statement is the right one. - errorReporter.reportError(importStatement, String.format( - "Unresolved compilation issues in '%s': " - + issues.toString(), importStatement.getImportURI() - )); - bad.add(downstreamReactor.eResource()); - } - } - } - } - } - /** * Return the resources that provide the given * reactors. diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java index 3e220e5ad6..f5e7187233 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java @@ -194,11 +194,6 @@ CodeBuilder generateCMakeCode( if (targetConfig.platformOptions.platform != Platform.AUTO) { cMakeCode.pr("set(CMAKE_SYSTEM_NAME "+targetConfig.platformOptions.platform.getcMakeName()+")"); } - - cMakeCode.pr("# Target definitions\n"); - targetConfig.compileDefinitions.forEach((key, value) -> cMakeCode.pr( - "add_compile_definitions("+key+"="+value+")\n" - )); cMakeCode.newLine(); if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { diff --git a/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt index 064f6758f8..22e3c3045a 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt @@ -59,14 +59,13 @@ class CppRos2NodeGenerator( | : Node("$nodeName", node_options) { | unsigned workers = ${if (targetConfig.workers != 0) targetConfig.workers else "std::thread::hardware_concurrency()"}; | bool fast{${targetConfig.fastMode}}; - | bool keepalive{${targetConfig.keepalive}}; | reactor::Duration lf_timeout{${targetConfig.timeout?.toCppCode() ?: "reactor::Duration::max()"}}; | | // provide a globally accessible reference to this node | // FIXME: this is pretty hacky... | lf_node = this; | - | lf_env = std::make_unique(workers, keepalive, fast, lf_timeout); + | lf_env = std::make_unique(workers, fast, lf_timeout); | | // instantiate the main reactor | lf_main_reactor = std::make_unique<${main.name}> ("${main.name}", lf_env.get(), ${main.name}::Parameters{}); diff --git a/org.lflang/src/org/lflang/generator/cpp/CppStandaloneMainGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppStandaloneMainGenerator.kt index 171b59c485..07cd3f8ba2 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppStandaloneMainGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppStandaloneMainGenerator.kt @@ -60,7 +60,6 @@ class CppStandaloneMainGenerator( | | unsigned workers = ${if (targetConfig.workers != 0) targetConfig.workers else "std::thread::hardware_concurrency()"}; | bool fast{${targetConfig.fastMode}}; - | bool keepalive{${targetConfig.keepalive}}; | reactor::Duration timeout = ${targetConfig.timeout?.toCppCode() ?: "reactor::Duration::max()"}; | | // the timeout variable needs to be tested beyond fitting the Duration-type @@ -69,7 +68,6 @@ class CppStandaloneMainGenerator( | .add_options() | ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value(workers)->default_value(std::to_string(workers)), "'unsigned'") | ("o,timeout", "Time after which the execution is aborted.", cxxopts::value(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") - | ("k,keepalive", "Continue execution even when there are no events to process.", cxxopts::value(keepalive)->default_value("${targetConfig.keepalive}")) | ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value(fast)->default_value("${targetConfig.fastMode}")) | ("help", "Print help"); | @@ -91,7 +89,7 @@ class CppStandaloneMainGenerator( | return parse_error ? -1 : 0; | } | - | reactor::Environment e{workers, keepalive, fast, timeout}; + | reactor::Environment e{workers, fast, timeout}; | | // instantiate the main reactor | ${generateMainReactorInstantiation()} diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index fa6d8b06fc..3f1fbef2fc 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -624,7 +624,7 @@ public void checkParameter(Parameter param) { 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", "k", "keepalive", "f", "fast", "help"); + 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,", @@ -1031,11 +1031,12 @@ public void checkTargetProperties(KeyValuePairs 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() == property.description) + .filter(pair -> pair.getName().equals(property.description)) .toList(); assert (properties.size() <= 1); return properties.size() > 0 ? properties.get(0) : null; @@ -1126,10 +1127,18 @@ private void validateSchedulerTargetProperties(KeyValuePairs targetProperties) { } } + 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); + } + } + private void validateRos2TargetProperties(KeyValuePairs targetProperties) { KeyValuePair ros2 = getKeyValuePair(targetProperties, TargetProperty.ROS2); KeyValuePair ros2Dependencies = getKeyValuePair(targetProperties, TargetProperty.ROS2_DEPENDENCIES); - if (!ASTUtils.toBoolean(ros2.getValue()) && ros2Dependencies != null) { + if (ros2Dependencies != null && (ros2 == null || !ASTUtils.toBoolean(ros2.getValue()))) { warning( "Ignoring ros2-dependencies as ros2 compilation is disabled", ros2Dependencies, diff --git a/test/Cpp/src/concurrent/AsyncCallback.lf b/test/Cpp/src/concurrent/AsyncCallback.lf index 29fedc41e8..984132f133 100644 --- a/test/Cpp/src/concurrent/AsyncCallback.lf +++ b/test/Cpp/src/concurrent/AsyncCallback.lf @@ -1,7 +1,6 @@ // Test asynchronous callbacks that trigger a physical action. target Cpp { timeout: 2 sec, - keepalive: true, cmake-include: "AsyncCallback.cmake" } diff --git a/test/Cpp/src/concurrent/AsyncCallback2.lf b/test/Cpp/src/concurrent/AsyncCallback2.lf index ea493a32cd..322413617b 100644 --- a/test/Cpp/src/concurrent/AsyncCallback2.lf +++ b/test/Cpp/src/concurrent/AsyncCallback2.lf @@ -1,7 +1,6 @@ // Test asynchronous callbacks that trigger a non-physical action. target Cpp { timeout: 2 sec, - keepalive: true, cmake-include: "AsyncCallback.cmake" } From 0b27dd08cdd6aeaa4d0269fd46a5ca1a50392025 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 13 Mar 2023 12:02:19 -0700 Subject: [PATCH 037/108] save before second pull --- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/lib/cpp/reactor-cpp | 2 +- test/Cpp/src/properties/Keepalive.lf | 43 ---------------------------- 3 files changed, 2 insertions(+), 45 deletions(-) delete mode 100644 test/Cpp/src/properties/Keepalive.lf diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 8a8827fa77..82afe9b180 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 8a8827fa77dc935676fbc148ecd83fffd3feb151 +Subproject commit 82afe9b180b3537c03284ba7d9ae7073982c5993 diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp index a37f0ade9e..e80cd36ce5 160000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit a37f0ade9ee0f97606528fd7ba67712495948373 +Subproject commit e80cd36ce5bd625a7b167e7dfd65d25f78b0dd01 diff --git a/test/Cpp/src/properties/Keepalive.lf b/test/Cpp/src/properties/Keepalive.lf deleted file mode 100644 index 1fe6c5e1bc..0000000000 --- a/test/Cpp/src/properties/Keepalive.lf +++ /dev/null @@ -1,43 +0,0 @@ -target Cpp { - keepalive: true, - cmake-include: "../concurrent/AsyncCallback.cmake" -} - -main reactor { - public preamble {= - #include - =} - - state thread: std::thread - - physical action a - state success: bool{false} - - reaction(startup) -> a {= - // start new thread - this->thread = std::thread([&] () { - // Simulate time passing before a callback occurs - std::this_thread::sleep_for(1s); - a.schedule(); - }); - =} - - reaction(a) {= - success = true; - environment()->sync_shutdown(); - =} - - reaction(shutdown) {= - if (success) { - std::cout << "SUCCESS!\n"; - } else { - std::cout << "ERROR: reaction was not invoked!\n"; - exit(1); - } - - // make sure to join the thread before shutting down - if(thread.joinable()) { - thread.join(); - } - =} -} From 945bffe3988259961659f3dffd2fe2621b74894c Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 15 Mar 2023 13:43:52 -0700 Subject: [PATCH 038/108] added error reporting for unthreaded w watchdog --- .../org/lflang/generator/c/CGenerator.java | 26 ++++++++++++++++--- .../generator/c/CReactionGenerator.java | 7 ++++- .../generator/c/CWatchdogGenerator.java | 7 ++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index d34e58ebfc..2ef5074455 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -445,6 +445,20 @@ protected boolean isOSCompatible() { return true; } + /** Returns false if watchdogs exist and are + * unsupported in this context. + * Otherwise, return true. + */ + protected boolean isWatchdogCompatible() { + if (hasWatchdogs() && !targetConfig.threading) { + errorReporter.reportError( + "Watchdogs are not supported for unthreaded programs." + ); + 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 @@ -459,6 +473,7 @@ 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 + if (!isWatchdogCompatible()) return; // Perform set up that does not generate code setUpGeneralParameters(); @@ -822,9 +837,11 @@ private boolean hasDeadlines(List reactors) { return false; } - private boolean hasWatchdogs(Reactor reactor) { - if (ASTUtils.allWatchdogs(reactor) != null) { - return true; + private boolean hasWatchdogs() { + for (Reactor reactor : reactors) { + if (ASTUtils.allWatchdogs(reactor) != null) { + return true; + } } return false; } @@ -1432,7 +1449,6 @@ protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactio * a struct that contains parameters, state variables, inputs (triggering or not), * actions (triggering or produced), and outputs. * @param decl The reactor. - * @param federate The federate, or null if this is not * federated or not the main reactor and reactions should be * unconditionally generated. */ @@ -1508,6 +1524,7 @@ private void recordWatchdogs(ReactorInstance instance) { var foundOne = false; var temp = new CodeBuilder(); var reactorRef = CUtil.reactorRef(instance); + // temp.pr("#ifdef LF_THREADED"); for (WatchdogInstance watchdog : instance.watchdogs) { temp.pr(" _lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); @@ -1515,6 +1532,7 @@ private void recordWatchdogs(ReactorInstance instance) { watchdogCount += 1; foundOne = true; } + // temp.pr("#endif"); if (foundOne) { initializeTriggerObjects.pr(temp.toString()); } diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 6959d52937..152413f10c 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -683,7 +683,12 @@ public static String generateWatchdogVariablesInReaction( ) { Watchdog watchdog = (Watchdog) effect.getVariable(); String watchdogName = watchdog.getName(); - return "watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");"; + + return String.join("\n", List.of( + "#ifdef LF_THREADED", + " watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");", + "#endif" + )); } /** diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 8b536a9ef8..72892e8e6b 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -122,10 +122,6 @@ public static void generateWatchdogStruct( ) { var reactor = ASTUtils.toDefinition(decl); - // WATCHDOG QUESTION 1: I followed similar structure to - // `CReactionGenerator.generateReactionAndTriggerStructs` - // but am not sure why we need to check if watchdog exists in the - // current federate. for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { String watchdogName = watchdog.getName(); // Create pointer to the watchdog_t struct @@ -148,6 +144,7 @@ public static void generateWatchdogStruct( // Set values of watchdog_t struct in the reactor's constructor // WATCHDOG QUESTION 5: should I be defining these in the constructor of the reactor? //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;", @@ -155,7 +152,7 @@ public static void generateWatchdogStruct( // "self->_lf_watchdog_"+watchdogName+".min_expiration = "+min_expiration+";", "self->_lf_watchdog_"+watchdogName+".watchdog_function = &("+watchdogFunctionName+");" )); - + // constructorCode.pr("#endif"); // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? } From 8228827769fe9d84916e73c3271720b276c0bf08 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Sun, 19 Mar 2023 19:33:14 -0700 Subject: [PATCH 039/108] saving before checkout out klighd-workaround --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 2 +- org.lflang/src/org/lflang/generator/c/CReactionGenerator.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 2ef5074455..e7ce2439f8 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -473,7 +473,7 @@ 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 - if (!isWatchdogCompatible()) return; + // if (!isWatchdogCompatible()) return; // Perform set up that does not generate code setUpGeneralParameters(); diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 152413f10c..5af962c9d9 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -36,7 +36,6 @@ import org.lflang.lf.Variable; import org.lflang.lf.Watchdog; import org.lflang.util.StringUtil; -import org.lflang.lf.Watchdog; public class CReactionGenerator { protected static String DISABLE_REACTION_INITIALIZATION_MARKER From 2af0fa75dcbd61842f58e4bf330c47a67d624545 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Sun, 19 Mar 2023 19:33:38 -0700 Subject: [PATCH 040/108] saving before klighd workaround --- test/C/src/Watchdog.lf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index 0dd2436478..e3d7f6cb41 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -26,7 +26,7 @@ main reactor { reaction(a) -> w.x {= lf_set(w.x, 0); - lf_nanosleep(MSEC(40)); + lf_nanosleep(MSEC(90)); =} reaction(w.d) {= printf("Deadline reactor produced an output.\n"); =} From be2a4c9fcd6adf00ff28735b1d08d0411c95ab33 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 20 Mar 2023 13:12:54 -0700 Subject: [PATCH 041/108] some changes to code review --- org.lflang/src/org/lflang/generator/c/CReactionGenerator.java | 4 +--- org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 5af962c9d9..e7e43a903c 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -684,9 +684,7 @@ public static String generateWatchdogVariablesInReaction( String watchdogName = watchdog.getName(); return String.join("\n", List.of( - "#ifdef LF_THREADED", - " watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");", - "#endif" + "watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");" )); } diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 72892e8e6b..344209c3b8 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -202,6 +202,7 @@ public static String generateLfInitializeWatchdogMutexes(int watchdogCount) { " 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;", " }", " }" )); From d87274fa43e9efd40636714e4599e5946df066b5 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 10:53:09 +0100 Subject: [PATCH 042/108] Align lingua-franca/watchdogs with reactor-c/watchdogs --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index ff3c84c36b..8ca094f356 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ff3c84c36b2d7fbcbe09fafa577ec94f75194e83 +Subproject commit 8ca094f35616f0024534ff9a2d46e6c03440a8ef From aaa73689f641a3fd1ddf9b5a4ab45dc543aa27cc Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 11:05:24 +0100 Subject: [PATCH 043/108] Merged master in watchdogs --- .github/actions/setup-ros2/action.yml | 2 +- .github/workflows/ts-tests.yml | 2 +- gradle/source-layout.gradle | 20 +- lib/scripts/launch-fedsd.sh | 103 ++++++ org.lflang.tests/build.gradle | 6 +- .../tests/compiler/TargetConfigTests.java | 102 ++++++ org.lflang/build.gradle | 22 +- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/lib/ts/package.json | 2 +- org.lflang/src/org/lflang/TargetConfig.java | 90 ++++- org.lflang/src/org/lflang/TargetProperty.java | 5 +- .../src/org/lflang/ast/FormattingUtils.java | 4 +- .../federated/extensions/CExtension.java | 33 +- .../federated/extensions/CExtensionUtils.java | 14 +- .../extensions/FedTargetExtension.java | 14 +- .../extensions/FedTargetExtensionFactory.java | 4 +- .../federated/extensions/TSExtension.java | 47 ++- .../federated/generator/FedASTUtils.java | 18 +- .../federated/generator/FedEmitter.java | 11 +- .../federated/generator/FedFileConfig.java | 45 ++- .../federated/generator/FedGenerator.java | 267 +++++++-------- .../federated/generator/FedImportEmitter.java | 2 +- .../federated/generator/FedMainEmitter.java | 2 +- .../generator/FedPreambleEmitter.java | 7 +- .../generator/FedReactorEmitter.java | 2 +- .../federated/generator/FedTargetConfig.java | 78 +++++ .../federated/generator/FedTargetEmitter.java | 85 +---- .../federated/generator/FederateInstance.java | 17 +- .../federated/launcher/BuildConfig.java | 60 ++++ .../{FedCLauncher.java => CBuildConfig.java} | 57 +--- .../launcher/FedLauncherFactory.java | 64 ---- ...auncher.java => FedLauncherGenerator.java} | 130 ++++---- .../federated/launcher/FedPyLauncher.java | 83 ----- .../federated/launcher/PyBuildConfig.java | 22 ++ .../lflang/federated/launcher/RtiConfig.java | 92 +++++ ...{FedTSLauncher.java => TsBuildConfig.java} | 37 +-- .../org/lflang/generator/GeneratorUtils.java | 95 +----- .../src/org/lflang/generator/MainContext.java | 4 +- .../org/lflang/generator/ts/TSGenerator.kt | 6 +- org.lflang/src/org/lflang/util/Averager.java | 27 ++ .../SimpleFederatedAuthenticated.lf | 0 util/tracing/README.md | 6 + util/tracing/makefile | 2 + util/tracing/trace_to_chrome.c | 52 ++- util/tracing/trace_to_csv.c | 115 +++++-- util/tracing/trace_to_influxdb.c | 16 +- util/tracing/trace_util.c | 127 +++---- util/tracing/trace_util.h | 31 +- util/tracing/visualization/.gitignore | 1 + util/tracing/visualization/README.md | 25 ++ util/tracing/visualization/fedsd.py | 314 ++++++++++++++++++ util/tracing/visualization/fedsd_helper.py | 241 ++++++++++++++ 52 files changed, 1758 insertions(+), 855 deletions(-) create mode 100755 lib/scripts/launch-fedsd.sh create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java create mode 100644 org.lflang/src/org/lflang/federated/generator/FedTargetConfig.java create mode 100644 org.lflang/src/org/lflang/federated/launcher/BuildConfig.java rename org.lflang/src/org/lflang/federated/launcher/{FedCLauncher.java => CBuildConfig.java} (56%) delete mode 100644 org.lflang/src/org/lflang/federated/launcher/FedLauncherFactory.java rename org.lflang/src/org/lflang/federated/launcher/{FedLauncher.java => FedLauncherGenerator.java} (86%) delete mode 100644 org.lflang/src/org/lflang/federated/launcher/FedPyLauncher.java create mode 100644 org.lflang/src/org/lflang/federated/launcher/PyBuildConfig.java create mode 100644 org.lflang/src/org/lflang/federated/launcher/RtiConfig.java rename org.lflang/src/org/lflang/federated/launcher/{FedTSLauncher.java => TsBuildConfig.java} (68%) create mode 100644 org.lflang/src/org/lflang/util/Averager.java rename test/C/src/federated/{ => failing}/SimpleFederatedAuthenticated.lf (100%) create mode 100644 util/tracing/visualization/.gitignore create mode 100644 util/tracing/visualization/README.md create mode 100644 util/tracing/visualization/fedsd.py create mode 100644 util/tracing/visualization/fedsd_helper.py diff --git a/.github/actions/setup-ros2/action.yml b/.github/actions/setup-ros2/action.yml index ac2a0b0d15..54b77b841a 100644 --- a/.github/actions/setup-ros2/action.yml +++ b/.github/actions/setup-ros2/action.yml @@ -9,6 +9,6 @@ runs: # see https://github.com/ros-tooling/setup-ros/issues/80 and https://github.com/ros2/rmw_cyclonedds/pull/134 run: sed -e 's/azure.archive.ubuntu.com/us.archive.ubuntu.com/g' -e t -e d /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/nonazure.list - name: Setup ROS2 - uses: lhstrh/setup-ros@master + uses: ros-tooling/setup-ros@0.6.1 with: required-ros-distributions: rolling diff --git a/.github/workflows/ts-tests.yml b/.github/workflows/ts-tests.yml index 24e40b7eef..83c04d8cf7 100644 --- a/.github/workflows/ts-tests.yml +++ b/.github/workflows/ts-tests.yml @@ -28,7 +28,7 @@ jobs: if: ${{ runner.os == 'macOS' }} - name: Perform TypeScript tests run: | - ./gradlew test --tests org.lflang.tests.runtime.TypeScriptTest.* + ./gradlew test --tests org.lflang.tests.runtime.TypeScriptTest.* -Druntime="git://github.com/lf-lang/reactor-ts.git#master" - name: Report to CodeCov uses: codecov/codecov-action@v3.1.1 with: diff --git a/gradle/source-layout.gradle b/gradle/source-layout.gradle index 2aab84e0eb..9bbe4b3944 100644 --- a/gradle/source-layout.gradle +++ b/gradle/source-layout.gradle @@ -6,14 +6,8 @@ if (name.endsWith(".tests")) { resources.srcDirs = [] } test { - java { - srcDirs = ['src', 'src-gen'] - exclude "org/lflang/diagram/**" - } - kotlin { - srcDirs = ['src', 'src-gen'] - exclude "org/lflang/diagram/**" - } + java.srcDirs = ['src', 'src-gen'] + kotlin.srcDirs = ['src', 'src-gen'] resources.srcDirs = ['src', 'src-gen'] xtendOutputDir = 'xtend-gen' } @@ -21,14 +15,8 @@ if (name.endsWith(".tests")) { } else { sourceSets { main { - java { - srcDirs = ['src', 'src-gen'] - exclude "org/lflang/diagram/**" - } - kotlin { - srcDirs = ['src', 'src-gen'] - exclude "org/lflang/diagram/**" - } + java.srcDirs = ['src', 'src-gen'] + kotlin.srcDirs = ['src', 'src-gen'] xtendOutputDir = 'xtend-gen' resources { srcDirs = ['src', 'src-gen'] diff --git a/lib/scripts/launch-fedsd.sh b/lib/scripts/launch-fedsd.sh new file mode 100755 index 0000000000..e09c60fc1e --- /dev/null +++ b/lib/scripts/launch-fedsd.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +#============================================================================ +# Description: Visualize federated trace data for RTI-federate interactions. +# Authors: Chadlia Jerad +# Edward A. Lee +# Usage: Usage: fedsd -r [rti.csv] -f [fed.csv ...] +#============================================================================ + +#============================================================================ +# Preamble +#============================================================================ + +# Copied from build.sh FIXME: How to avoid copying + +# Find the directory in which this script resides in a way that is compatible +# with MacOS, which has a `readlink` implementation that does not support the +# necessary `-f` flag to canonicalize by following every symlink in every +# component of the given name recursively. +# This solution, adapted from an example written by Geoff Nixon, is POSIX- +# compliant and robust to symbolic links. If a chain of more than 1000 links +# is encountered, we return. +find_dir() ( + start_dir=$PWD + cd "$(dirname "$1")" + link=$(readlink "$(basename "$1")") + count=0 + while [ "${link}" ]; do + if [[ "${count}" -lt 1000 ]]; then + cd "$(dirname "${link}")" + link=$(readlink "$(basename "$1")") + ((count++)) + else + return + fi + done + real_path="$PWD/$(basename "$1")" + cd "${start_dir}" + echo `dirname "${real_path}"` +) + +# Report fatal error and exit. +function fatal_error() { + 1>&2 echo -e "\e[1mfedsd: \e[31mfatal error: \e[0m$1" + exit 1 +} + +abs_path="$(find_dir "$0")" + +if [[ "${abs_path}" ]]; then + base=`dirname $(dirname ${abs_path})` +else + fatal_error "Unable to determine absolute path to $0." +fi + +# Get the lft files +lft_files_list=$@ + +if [ -z "$lft_files_list" ] +then + echo "Usage: fedsd [lft files]" + exit 1 +fi + +# Initialize variables +csv_files_list='' +extension='.csv' +rti_csv_file='' + +# Iterate over the lft file list to: +# - First, transform into csv +# - Second, construct the csv fiel name +# - Then construct the csv file list +# The csv file list does include the rti, it is put in a separate variable +for each_lft_file in $lft_files_list + do + # Tranform to csv + trace_to_csv $each_lft_file + # Get the file name + csv=${each_lft_file%.*} + if [ $csv == 'rti' ] + then + # Set the rti csv file + rti_csv_file='rti.csv' + else + # Construct the csv file name and add it to the list + csv_files_list="$csv$extension $csv_files_list" + fi + done + +# echo $lft_files_list +# echo $rti_csv_file +# echo $csv_files_list + +# FIXME: Check that python3 is in the path. +if [ $rti_csv_file == '' ] +then + # FIXME: Support the case where no rti file is given + python3 "${base}/util/tracing/visualization/fedsd.py" "-f" $csv_files_list +else + echo Building the communication diagram for the following trace files: $lft_files_list in trace_svg.html + python3 "${base}/util/tracing/visualization/fedsd.py" "-r" "$rti_csv_file" "-f" $csv_files_list +fi diff --git a/org.lflang.tests/build.gradle b/org.lflang.tests/build.gradle index bdb279d61d..020a3a8395 100644 --- a/org.lflang.tests/build.gradle +++ b/org.lflang.tests/build.gradle @@ -1,9 +1,9 @@ repositories { mavenCentral() // TODO Replace this unofficial maven repository as soon as Klighd is released to maven central in the future. - //maven { - // url "https://rtsys.informatik.uni-kiel.de/~kieler/files/repo/" - //} + maven { + url "https://rtsys.informatik.uni-kiel.de/~kieler/files/repo/" + } } dependencies { implementation project(':org.lflang') diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java new file mode 100644 index 0000000000..593e302d25 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java @@ -0,0 +1,102 @@ +package org.lflang.tests.compiler; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.nio.file.Files; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.xtext.generator.JavaIoFileSystemAccess; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.lflang.federated.generator.FedFileConfig; +import org.lflang.generator.GeneratorUtils; +import org.lflang.generator.LFGenerator; +import org.lflang.generator.LFGeneratorContext.Mode; +import org.lflang.generator.MainContext; +import org.lflang.lf.Model; +import org.lflang.tests.LFInjectorProvider; + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) + +/** + * Tests for checking that target properties adequately translate into the target configuration. + */ +class TargetConfigTests { + + @Inject + ParseHelper parser; + + @Inject + LFGenerator generator; + + @Inject + JavaIoFileSystemAccess fileAccess; + + @Inject + Provider resourceSetProvider; + + private void assertHasTargetProperty(Model model, String name) { + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.getTarget().getConfig().getPairs().stream().anyMatch( + p -> p.getName().equals(name) + ) + ); + } + + /** + * Check that tracing target property affects the target configuration. + * @throws Exception + */ + @Test + public void testParsing() throws Exception { + assertHasTargetProperty(parser.parse(""" + target C { + tracing: true + } + """), "tracing"); + } + + /** + * Check that when a federation has the "tracing" target property set, the generated federates + * will also have it set. + * @throws Exception + */ + @Test + public void testFederation() throws Exception { + fileAccess.setOutputPath("src-gen"); + + Model federation = parser.parse(""" + target C { + tracing: true + } + reactor Foo { + + } + federated reactor { + a = new Foo() + b = new Foo() + } + """, URI.createFileURI("tmp/src/Federation.lf"), resourceSetProvider.get()); + assertHasTargetProperty(federation, "tracing"); + + var resource = federation.eResource(); + var context = new MainContext(Mode.STANDALONE, resource, fileAccess, () -> false); + + if (GeneratorUtils.isHostWindows()) return; + + generator.doGenerate(resource, fileAccess, context); + + String lfSrc = Files.readAllLines( + ((FedFileConfig)context.getFileConfig()).getSrcPath().resolve("a.lf") + ).stream().reduce("\n", String::concat); + Model federate = parser.parse(lfSrc); + assertHasTargetProperty(federate, "tracing"); + } +} diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle index 5de780bb07..9c6b9bf5d9 100644 --- a/org.lflang/build.gradle +++ b/org.lflang/build.gradle @@ -3,9 +3,9 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar repositories { mavenCentral() // TODO Replace this unofficial maven repository as soon as Klighd is released to maven central in the future. - //maven { - // url "https://rtsys.informatik.uni-kiel.de/~kieler/files/repo/" - //} + maven { + url "https://rtsys.informatik.uni-kiel.de/~kieler/files/repo/" + } } dependencies { implementation "org.eclipse.xtext:org.eclipse.xtext:${xtextVersion}" @@ -20,14 +20,14 @@ dependencies { implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.4' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.12.4' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.4' - //implementation ("de.cau.cs.kieler.klighd:de.cau.cs.kieler.klighd.lsp:${klighdVersion}") { - // exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt.*' - // exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' - //} - //implementation ("de.cau.cs.kieler.klighd:de.cau.cs.kieler.klighd.standalone:${klighdVersion}") { - // exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt.*' - // exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' - //} + implementation ("de.cau.cs.kieler.klighd:de.cau.cs.kieler.klighd.lsp:${klighdVersion}") { + exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt.*' + exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' + } + implementation ("de.cau.cs.kieler.klighd:de.cau.cs.kieler.klighd.standalone:${klighdVersion}") { + exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt.*' + exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' + } } configurations { diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 8ca094f356..10c275b99a 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 8ca094f35616f0024534ff9a2d46e6c03440a8ef +Subproject commit 10c275b99a01802f00168b92d4e9068fdf9b34b6 diff --git a/org.lflang/src/lib/ts/package.json b/org.lflang/src/lib/ts/package.json index 3c368c9481..a1d65a6403 100644 --- a/org.lflang/src/lib/ts/package.json +++ b/org.lflang/src/lib/ts/package.json @@ -2,7 +2,7 @@ "name": "LinguaFrancaDefault", "type": "commonjs", "dependencies": { - "@lf-lang/reactor-ts": "0.3.2", + "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#master", "@babel/cli": "^7.8.4", "@babel/core": "^7.8.7", "@babel/node": "^7.8.7", diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index 446460d5c1..23e99541da 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.Set; import org.lflang.TargetProperty.BuildType; @@ -38,7 +39,9 @@ import org.lflang.TargetProperty.LogLevel; import org.lflang.TargetProperty.Platform; import org.lflang.TargetProperty.SchedulerOption; +import org.lflang.TargetProperty.UnionType; import org.lflang.generator.rust.RustTargetConfig; +import org.lflang.lf.KeyValuePair; import org.lflang.lf.TargetDecl; /** @@ -50,12 +53,92 @@ */ public class TargetConfig { + /** + * The target of this configuration (e.g., C, TypeScript, Python). + */ public final Target target; - public TargetConfig(TargetDecl target) { + /** + * Create a new target configuration based on the given target declaration AST node only. + * @param target AST node of a target declaration. + */ + public TargetConfig(TargetDecl target) { // FIXME: eliminate this constructor if we can this.target = Target.fromDecl(target); } + /** + * Create a new target configuration based on the given commandline arguments and target + * declaration AST node. + * @param cliArgs Arguments passed on the commandline. + * @param target AST node of a target declaration. + * @param errorReporter An error reporter to report problems. + */ + public TargetConfig( + Properties cliArgs, + TargetDecl target, + ErrorReporter errorReporter + ) { + this(target); + if (target.getConfig() != null) { + List pairs = target.getConfig().getPairs(); + TargetProperty.set(this, pairs != null ? pairs : List.of(), errorReporter); + } + if (cliArgs.containsKey("no-compile")) { + this.noCompile = true; + } + if (cliArgs.containsKey("docker")) { + var arg = cliArgs.getProperty("docker"); + if (Boolean.parseBoolean(arg)) { + this.dockerOptions = new DockerOptions(); + } else { + this.dockerOptions = null; + } + // FIXME: this is pretty ad-hoc and does not account for more complex overrides yet. + } + if (cliArgs.containsKey("build-type")) { + this.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION.forName(cliArgs.getProperty("build-type")); + } + if (cliArgs.containsKey("logging")) { + this.logLevel = LogLevel.valueOf(cliArgs.getProperty("logging").toUpperCase()); + } + if (cliArgs.containsKey("workers")) { + this.workers = Integer.parseInt(cliArgs.getProperty("workers")); + } + if (cliArgs.containsKey("threading")) { + this.threading = Boolean.parseBoolean(cliArgs.getProperty("threading")); + } + if (cliArgs.containsKey("target-compiler")) { + this.compiler = cliArgs.getProperty("target-compiler"); + } + if (cliArgs.containsKey("tracing")) { + this.tracing = new TracingOptions(); + } + if (cliArgs.containsKey("scheduler")) { + this.schedulerType = SchedulerOption.valueOf( + cliArgs.getProperty("scheduler") + ); + this.setByUser.add(TargetProperty.SCHEDULER); + } + if (cliArgs.containsKey("target-flags")) { + this.compilerFlags.clear(); + if (!cliArgs.getProperty("target-flags").isEmpty()) { + this.compilerFlags.addAll(List.of( + cliArgs.getProperty("target-flags").split(" ") + )); + } + } + if (cliArgs.containsKey("runtime-version")) { + this.runtimeVersion = cliArgs.getProperty("runtime-version"); + } + if (cliArgs.containsKey("external-runtime-path")) { + this.externalRuntimePath = cliArgs.getProperty("external-runtime-path"); + } + if (cliArgs.containsKey(TargetProperty.KEEPALIVE.description)) { + this.keepalive = Boolean.parseBoolean( + cliArgs.getProperty(TargetProperty.KEEPALIVE.description)); + } + } + /** * Keep track of every target property that is explicitly set by the user. */ @@ -197,9 +280,6 @@ public TargetConfig(TargetDecl target) { * * This is now a wrapped class to account for overloaded definitions * of defining platform (either a string or dictionary of values) - * - * @author Samuel Berkun - * @author Anirudh Rengarajan */ public PlatformOptions platformOptions = new PlatformOptions(); @@ -293,7 +373,7 @@ public static class ClockSyncOptions { public int attenuation = 10; /** - * Whether or not to collect statistics while performing clock synchronization. + * Whether to collect statistics while performing clock synchronization. * This setting is only considered when clock synchronization has been activated. * The default is true. */ diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index f0de831bf4..3f38360d67 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -580,8 +580,7 @@ public enum TargetProperty { }), /** - * Directive to generate a Dockerfile. This is either a boolean, - * true or false, or a dictionary of options. + * Directive to enable tracing. */ TRACING("tracing", UnionType.TRACING_UNION, Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Python), @@ -974,7 +973,7 @@ public static TargetDecl extractTargetDecl(Target target, TargetConfig config) { * @param config The configuration object to update. * @param properties AST node that holds all the target properties. */ - public static void update(TargetConfig config, List properties,ErrorReporter err) { + public static void update(TargetConfig config, List properties, ErrorReporter err) { properties.forEach(property -> { TargetProperty p = forName(property.getName()); if (p != null) { diff --git a/org.lflang/src/org/lflang/ast/FormattingUtils.java b/org.lflang/src/org/lflang/ast/FormattingUtils.java index bd9fbaf3d8..0e148f218b 100644 --- a/org.lflang/src/org/lflang/ast/FormattingUtils.java +++ b/org.lflang/src/org/lflang/ast/FormattingUtils.java @@ -55,8 +55,8 @@ public static String render(EObject object, int lineLength) { } /** Return a function that renders AST nodes for the given target. */ - public static Function renderer(TargetDecl targetDecl) { - return object -> render(object, DEFAULT_LINE_LENGTH, Target.fromDecl(targetDecl), true); + public static Function renderer(Target target) { + return object -> render(object, DEFAULT_LINE_LENGTH, target, true); } /** diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index d9ed222920..fda727d735 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -45,6 +45,7 @@ import org.lflang.federated.generator.FedConnectionInstance; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.federated.serialization.FedROS2CPPSerialization; import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; @@ -83,18 +84,10 @@ public void initializeTargetConfig( int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter, - LinkedHashMap federationRTIProperties + RtiConfig rtiConfig ) throws IOException { - if(GeneratorUtils.isHostWindows()) { - errorReporter.reportError( - "Federated LF programs with a C target are currently not supported on Windows. " + - "Exiting code generation." - ); - // Return to avoid compiler errors - return; - } - CExtensionUtils.handleCompileDefinitions(federate, numOfFederates, federationRTIProperties); + CExtensionUtils.handleCompileDefinitions(federate, numOfFederates, rtiConfig); generateCMakeInclude(federate, fileConfig); @@ -488,11 +481,11 @@ public String getNetworkBufferType() { public String generatePreamble( FederateInstance federate, FedFileConfig fileConfig, - LinkedHashMap federationRTIProperties, + RtiConfig rtiConfig, ErrorReporter errorReporter ) throws IOException { // Put the C preamble in a `include/_federate.name + _preamble.h` file - String cPreamble = makePreamble(federate, fileConfig, federationRTIProperties, errorReporter); + String cPreamble = makePreamble(federate, fileConfig, rtiConfig, errorReporter); String relPath = "include" + File.separator + "_" + federate.name + "_preamble.h"; Path fedPreamblePath = fileConfig.getSrcPath().resolve(relPath); Files.createDirectories(fedPreamblePath.getParent()); @@ -509,7 +502,7 @@ public String generatePreamble( protected String makePreamble( FederateInstance federate, FedFileConfig fileConfig, - LinkedHashMap federationRTIProperties, + RtiConfig rtiConfig, ErrorReporter errorReporter) { var code = new CodeBuilder(); @@ -532,7 +525,7 @@ protected String makePreamble( code.pr(generateSerializationPreamble(federate, fileConfig)); - code.pr(generateExecutablePreamble(federate, federationRTIProperties, errorReporter)); + code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); code.pr(generateInitializeTriggers(federate, errorReporter)); @@ -582,12 +575,12 @@ private String generateInitializeTriggers(FederateInstance federate, ErrorReport * Generate code for an executed preamble. * */ - private String generateExecutablePreamble(FederateInstance federate, LinkedHashMap federationRTIProperties, ErrorReporter errorReporter) { + private String generateExecutablePreamble(FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { CodeBuilder code = new CodeBuilder(); code.pr(generateCodeForPhysicalActions(federate, errorReporter)); - code.pr(generateCodeToInitializeFederate(federate, federationRTIProperties)); + code.pr(generateCodeToInitializeFederate(federate, rtiConfig)); code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); @@ -600,10 +593,10 @@ void _lf_executable_preamble() { /** * Generate code to initialize the {@code federate}. - * @param federationRTIProperties Properties related to the RTI. + * @param rtiConfig * @return The generated code */ - private String generateCodeToInitializeFederate(FederateInstance federate, LinkedHashMap federationRTIProperties) { + private String generateCodeToInitializeFederate(FederateInstance federate, RtiConfig rtiConfig) { CodeBuilder code = new CodeBuilder(); code.pr("// ***** Start initializing the federated execution. */"); code.pr(String.join("\n", @@ -672,12 +665,12 @@ private String generateCodeToInitializeFederate(FederateInstance federate, Linke code.pr(String.join("\n", "// Connect to the RTI. This sets _fed.socket_TCP_RTI and _lf_rti_socket_UDP.", - "connect_to_rti("+addDoubleQuotes(federationRTIProperties.get("host").toString())+", "+ federationRTIProperties.get("port")+");" + "connect_to_rti("+addDoubleQuotes(rtiConfig.getHost())+", "+ rtiConfig.getPort()+");" )); // Disable clock synchronization for the federate if it resides on the same host as the RTI, // unless that is overridden with the clock-sync-options target property. - if (CExtensionUtils.clockSyncIsOn(federate, federationRTIProperties)) { + if (CExtensionUtils.clockSyncIsOn(federate, rtiConfig)) { code.pr("synchronize_initial_physical_clock_with_rti(_fed.socket_TCP_RTI);"); } diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index a24bcbae58..aabc77ecd5 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; @@ -17,6 +16,7 @@ import org.lflang.TimeValue; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.federated.serialization.FedROS2CPPSerialization; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.CodeBuilder; @@ -239,7 +239,7 @@ static boolean isSharedPtrType(InferredType type, CTypes types) { public static void handleCompileDefinitions( FederateInstance federate, int numOfFederates, - LinkedHashMap federationRTIProperties + RtiConfig rtiConfig ) { federate.targetConfig.setByUser.add(TargetProperty.COMPILE_DEFINITIONS); federate.targetConfig.compileDefinitions.put("FEDERATED", ""); @@ -250,7 +250,7 @@ public static void handleCompileDefinitions( handleAdvanceMessageInterval(federate); - initializeClockSynchronization(federate, federationRTIProperties); + initializeClockSynchronization(federate, rtiConfig); } /** @@ -276,9 +276,9 @@ private static void handleAdvanceMessageInterval(FederateInstance federate) { } } - static boolean clockSyncIsOn(FederateInstance federate, LinkedHashMap federationRTIProperties) { + static boolean clockSyncIsOn(FederateInstance federate, RtiConfig rtiConfig) { return federate.targetConfig.clockSync != ClockSyncMode.OFF - && (!federationRTIProperties.get("host").toString().equals(federate.host) + && (!rtiConfig.getHost().equals(federate.host) || federate.targetConfig.clockSyncOptions.localFederatesOn); } @@ -290,10 +290,10 @@ static boolean clockSyncIsOn(FederateInstance federate, LinkedHashMap federationRTIProperties + RtiConfig rtiConfig ) { // Check if clock synchronization should be enabled for this federate in the first place - if (clockSyncIsOn(federate, federationRTIProperties)) { + if (clockSyncIsOn(federate, rtiConfig)) { System.out.println("Initial clock synchronization is enabled for federate " + federate.id ); diff --git a/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java b/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java index 7fb42bf007..7aa13d6cfc 100644 --- a/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java @@ -1,21 +1,15 @@ package org.lflang.federated.extensions; import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; import org.lflang.ErrorReporter; -import org.lflang.FileConfig; import org.lflang.InferredType; -import org.lflang.Target; -import org.lflang.TargetConfig; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.federated.generator.FedConnectionInstance; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; -import org.lflang.federated.serialization.SupportedSerializers; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.LFGeneratorContext; import org.lflang.lf.Action; import org.lflang.lf.Reaction; @@ -33,7 +27,7 @@ public interface FedTargetExtension { * @param errorReporter Used to report errors. */ void initializeTargetConfig(LFGeneratorContext context, int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, - ErrorReporter errorReporter, LinkedHashMap federationRTIProperties) throws IOException; + ErrorReporter errorReporter, RtiConfig rtiConfig) throws IOException; /** * Generate code for the body of a reaction that handles the @@ -120,13 +114,13 @@ default void annotateReaction(Reaction reaction) {} * Add necessary preamble to the source to set up federated execution. * * @param federate - * @param federationRTIProperties + * @param rtiConfig * @param errorReporter * @return */ String generatePreamble(FederateInstance federate, FedFileConfig fileConfig, - LinkedHashMap federationRTIProperties, + RtiConfig rtiConfig, ErrorReporter errorReporter) throws IOException; } diff --git a/org.lflang/src/org/lflang/federated/extensions/FedTargetExtensionFactory.java b/org.lflang/src/org/lflang/federated/extensions/FedTargetExtensionFactory.java index f3ab17514d..5caafc1c0c 100644 --- a/org.lflang/src/org/lflang/federated/extensions/FedTargetExtensionFactory.java +++ b/org.lflang/src/org/lflang/federated/extensions/FedTargetExtensionFactory.java @@ -12,8 +12,8 @@ public class FedTargetExtensionFactory { /** * Given a target, return the appropriate extension. */ - public static FedTargetExtension getExtension(TargetDecl target) { - switch (Target.fromDecl(target)) { + public static FedTargetExtension getExtension(Target target) { + switch (target) { case CCPP: case C: return new CExtension(); case Python: return new PythonExtension(); diff --git a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java index 21b469228e..ca8ce4dd9c 100644 --- a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java @@ -3,6 +3,8 @@ import static org.lflang.util.StringUtil.addDoubleQuotes; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.LinkedHashMap; import java.util.stream.Collectors; @@ -16,6 +18,7 @@ import org.lflang.federated.generator.FedConnectionInstance; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.GeneratorBase; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.ReactorInstance; @@ -28,7 +31,7 @@ public class TSExtension implements FedTargetExtension { @Override - public void initializeTargetConfig(LFGeneratorContext context, int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter, LinkedHashMap federationRTIProperties) throws IOException { + public void initializeTargetConfig(LFGeneratorContext context, int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter, RtiConfig rtiConfig) throws IOException { } @@ -88,9 +91,10 @@ public String getNetworkBufferType() { */ @Override public String generatePreamble(FederateInstance federate, FedFileConfig fileConfig, - LinkedHashMap federationRTIProperties, + RtiConfig rtiConfig, ErrorReporter errorReporter) { var minOutputDelay = getMinOutputDelay(federate, fileConfig, errorReporter); + var upstreamConnectionDelays = getUpstreamConnectionDelays(federate); return """ preamble {= @@ -105,7 +109,8 @@ public String generatePreamble(FederateInstance federate, FedFileConfig fileConf networkMessageActions: [%s], rtiHost: "%s", rtiPort: %d, - sendsTo: [%s] + sendsTo: [%s], + upstreamConnectionDelays: [%s] } =}""".formatted( federate.dependsOn.keySet().stream() @@ -118,11 +123,12 @@ public String generatePreamble(FederateInstance federate, FedFileConfig fileConf .stream() .map(Variable::getName) .collect(Collectors.joining(",", "\"", "\"")), - federationRTIProperties.get("host"), - federationRTIProperties.get("port"), + rtiConfig.getHost(), + rtiConfig.getPort(), federate.sendsTo.keySet().stream() .map(e->String.valueOf(e.id)) - .collect(Collectors.joining(",")) + .collect(Collectors.joining(",")), + upstreamConnectionDelays.stream().collect(Collectors.joining(",")) ); } @@ -167,4 +173,33 @@ private TimeValue getMinOutputDelay(FederateInstance federate, FedFileConfig fil } return null; } + + private List getUpstreamConnectionDelays(FederateInstance federate) { + List candidates = new ArrayList<>(); + if (!federate.dependsOn.keySet().isEmpty()) { + for (FederateInstance upstreamFederate: federate.dependsOn.keySet()) { + String element = "["; + var delays = federate.dependsOn.get(upstreamFederate); + int cnt = 0; + if (delays != null) { + for (Expression delay : delays) { + if (delay == null) { + element += "TimeValue.NEVER()"; + } else { + element += "TimeValue.nsec(" + getNetworkDelayLiteral(delay) + ")"; + } + cnt++; + if (cnt != delays.size()) { + element += ", "; + } + } + } else { + element += "TimeValue.NEVER()"; + } + element += "]"; + candidates.add(element); + } + } + return candidates; + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index 21fb3a0729..7a7eb1f183 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -208,7 +208,7 @@ private static Action createNetworkAction(FedConnectionInstance connection) { Type action_type = factory.createType(); action_type.setId( FedTargetExtensionFactory.getExtension( - connection.srcFederate.target + connection.srcFederate.targetConfig.target ).getNetworkBufferType() ); action.setType(action_type); @@ -264,7 +264,7 @@ private static void addNetworkReceiverReaction( setReactionBankIndex(networkReceiverReaction, connection.getDstBank()); // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.target) + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .annotateReaction(networkReceiverReaction); // The connection is 'physical' if it uses the ~> notation. @@ -298,7 +298,7 @@ private static void addNetworkReceiverReaction( networkReceiverReaction.setCode(factory.createCode()); networkReceiverReaction.getCode().setBody( FedTargetExtensionFactory.getExtension( - connection.dstFederate.target).generateNetworkReceiverBody( + connection.dstFederate.targetConfig.target).generateNetworkReceiverBody( networkAction, sourceRef, destRef, @@ -357,7 +357,7 @@ private static void addNetworkInputControlReaction( setReactionBankIndex(reaction, connection.getDstBank()); // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.target) + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .annotateReaction(reaction); // Create a new action that will be used to trigger the @@ -397,7 +397,7 @@ private static void addNetworkInputControlReaction( reaction.getCode() .setBody( FedTargetExtensionFactory - .getExtension(connection.dstFederate.target) + .getExtension(connection.dstFederate.targetConfig.target) .generateNetworkInputControlReactionBody( receivingPortID, maxSTP, @@ -721,7 +721,7 @@ private static void addNetworkSenderReaction( Reaction networkSenderReaction = factory.createReaction(); // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.target) + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .annotateReaction(networkSenderReaction); // If the sender or receiver is in a bank of reactors, then we want @@ -750,7 +750,7 @@ private static void addNetworkSenderReaction( networkSenderReaction.getTriggers().add(sourceRef); networkSenderReaction.setCode(factory.createCode()); networkSenderReaction.getCode().setBody( - FedTargetExtensionFactory.getExtension(connection.srcFederate.target) + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .generateNetworkSenderBody( sourceRef, destRef, @@ -797,7 +797,7 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec setReactionBankIndex(reaction, connection.getSrcBank()); // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.target) + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .annotateReaction(reaction); // We use an action at the top-level to manually @@ -845,7 +845,7 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec reaction.setCode(factory.createCode()); reaction.getCode().setBody( - FedTargetExtensionFactory.getExtension(connection.srcFederate.target) + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .generateNetworkOutputControlReactionBody(newPortRef, connection)); ASTUtils.addReactionAttribute(reaction, "_unordered"); diff --git a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java index e86af3ab77..9ae5c9d2cc 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java @@ -8,6 +8,7 @@ import java.util.Map; import org.lflang.ErrorReporter; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.CodeMap; import org.lflang.generator.LFGeneratorContext; import org.lflang.lf.Reactor; @@ -20,18 +21,18 @@ public class FedEmitter { private final FedFileConfig fileConfig; private final Reactor originalMainReactor; private final ErrorReporter errorReporter; - private final LinkedHashMap federationRTIProperties; + private final RtiConfig rtiConfig; public FedEmitter( FedFileConfig fileConfig, Reactor originalMainReactor, ErrorReporter errorReporter, - LinkedHashMap federationRTIProperties + RtiConfig rtiConfig ) { this.fileConfig = fileConfig; this.originalMainReactor = originalMainReactor; this.errorReporter = errorReporter; - this.federationRTIProperties = federationRTIProperties; + this.rtiConfig = rtiConfig; } /** @@ -55,9 +56,9 @@ Map generateFederate( String federateCode = String.join( "\n", - new FedTargetEmitter().generateTarget(context, numOfFederates, federate, fileConfig, errorReporter, federationRTIProperties), + new FedTargetEmitter().generateTarget(context, numOfFederates, federate, fileConfig, errorReporter, rtiConfig), new FedImportEmitter().generateImports(federate, fileConfig), - new FedPreambleEmitter().generatePreamble(federate, fileConfig, federationRTIProperties, errorReporter), + new FedPreambleEmitter().generatePreamble(federate, fileConfig, rtiConfig, errorReporter), new FedReactorEmitter().generateReactorDefinitions(federate), new FedMainEmitter().generateMainReactor( federate, diff --git a/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java b/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java index 8e9df19d6e..42f5c60dc7 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java +++ b/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java @@ -27,6 +27,8 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import org.eclipse.emf.ecore.resource.Resource; @@ -43,10 +45,12 @@ */ public class FedFileConfig extends FileConfig { - public FedFileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) throws IOException { - // FIMXE: It is unclear to me that we need this class. + public FedFileConfig( + Resource resource, + Path srcGenBasePath, + boolean useHierarchicalBin + ) throws IOException { super(resource, srcGenBasePath, useHierarchicalBin); - } public FedFileConfig(FileConfig fileConfig) throws IOException { @@ -89,9 +93,44 @@ public Path getFedGenPath() { return srcPkgPath.resolve("fed-gen").resolve(this.name); } + /** + * Return the path to the directory in which the executables of compiled federates are stored. + */ + public Path getFedBinPath() { return getFedGenPath().resolve("bin"); } + @Override public void doClean() throws IOException { super.doClean(); FileUtil.deleteDirectory(this.getFedGenPath()); } + + /** + * Relativize target properties that involve paths like files and cmake-include to be + * relative to the generated .lf file for the federate. + */ + public void relativizePaths(FedTargetConfig targetConfig) { + relativizePathList(targetConfig.protoFiles); + relativizePathList(targetConfig.fileNames); + relativizePathList(targetConfig.cmakeIncludes); + } + + /** + * Relativize each path in the given list. + * @param paths The paths to relativize. + */ + private void relativizePathList(List paths) { + List tempList = new ArrayList<>(); + paths.forEach(f -> tempList.add(relativizePath(f))); + paths.clear(); + paths.addAll(tempList); + } + + /** + * Relativize a single path. + * @param path The path to relativize. + */ + private String relativizePath(String path) { + Path resolvedPath = this.srcPath.resolve(path).toAbsolutePath(); + return this.getSrcPath().relativize(resolvedPath).toString(); + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java index 9a98e90291..18d5770165 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java +++ b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -27,10 +26,7 @@ import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.util.RuntimeIOException; -import org.eclipse.xtext.xbase.lib.CollectionLiterals; import org.eclipse.xtext.xbase.lib.Exceptions; -import org.eclipse.xtext.xbase.lib.Pair; -import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; import org.lflang.ASTUtils; import org.lflang.ErrorReporter; @@ -39,12 +35,11 @@ import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TargetProperty.CoordinationType; -import org.lflang.federated.launcher.FedLauncher; -import org.lflang.federated.launcher.FedLauncherFactory; +import org.lflang.federated.launcher.FedLauncherGenerator; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.CodeMap; import org.lflang.generator.DockerData; import org.lflang.generator.FedDockerComposeGenerator; -import org.lflang.generator.GeneratorResult; import org.lflang.generator.GeneratorResult.Status; import org.lflang.generator.GeneratorUtils; import org.lflang.generator.IntegratedBuilder; @@ -61,55 +56,40 @@ import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Reactor; -import org.lflang.lf.TargetDecl; +import org.lflang.util.Averager; import com.google.inject.Injector; public class FedGenerator { - /** Average asynchronously reported numbers and do something with them. */ - private static class Averager { - private final int n; - private final int[] reports; - /** Create an averager of reports from {@code n} processes. */ - public Averager(int n) { - this.n = n; - reports = new int[n]; - } - - /** - * Receive {@code x} from process {@code id} and invoke {@code callback} - * on the mean of the numbers most recently reported by the processes. - */ - public synchronized void report(int id, int x, Procedure1 callback) { - assert 0 <= id && id < n; - reports[id] = x; - callback.apply(Arrays.stream(reports).sum() / n); - } - } - - private final FedFileConfig fileConfig; - private final ErrorReporter errorReporter; /** - * The current target configuration. + * */ - private final TargetConfig targetConfig; + private final ErrorReporter errorReporter; + /** * A list of federate instances. */ private final List federates = new ArrayList<>(); + /** - * A map from federate IDs to federate instances. + * File configuration to be used during the LF code generation stage (not the target code + * generation stage of individual federates). */ - private final Map federateByID = new LinkedHashMap<>(); + private final FedFileConfig fileConfig; + /** - * The federation RTI properties, which defaults to 'localhost: 15045'. + * Configuration of the RTI. */ - final LinkedHashMap federationRTIProperties = CollectionLiterals.newLinkedHashMap( - Pair.of("host", "localhost"), - Pair.of("port", 0) // Indicator to use the default port, typically 15045. - ); + final RtiConfig rtiConfig = new RtiConfig(); + + /** + * Target configuration of the federation; drawn from the file + * in which the federated reactor is defined. + */ + private final TargetConfig targetConfig; + /** * A map from instantiations to the federate instances for that * instantiation. @@ -125,12 +105,26 @@ public synchronized void report(int id, int x, Procedure1 callback) { */ private Instantiation mainDef; + /** + * Create a new generator and initialize a file configuration, target configuration, and error + * reporter. + * @param context + */ public FedGenerator(LFGeneratorContext context) { this.fileConfig = (FedFileConfig) context.getFileConfig(); this.targetConfig = context.getTargetConfig(); this.errorReporter = context.getErrorReporter(); } + /** + * Produce LF code for each federate in a separate file, then invoke a target-specific code + * generator for each of those files. + * + * @param resource The resource that has the federated main reactor in it + * @param context The context in which to carry out the code generation. + * @return False if no errors have occurred, true otherwise. + * @throws IOException + */ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws IOException { if (!federatedExecutionIsSupported(resource)) return true; cleanIfNeeded(context); @@ -144,27 +138,26 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws processCLIArguments(context); // Find the federated reactor - Reactor fedReactor = FedASTUtils.findFederatedReactor(resource); + Reactor federation = FedASTUtils.findFederatedReactor(resource); // Extract some useful information about the federation - analyzeFederates(fedReactor); + analyzeFederates(federation, context); // Find all the connections between federates. // For each connection between federates, replace it in the // AST with an action (which inherits the delay) and four reactions. // The action will be physical for physical connections and logical // for logical connections. - replaceFederateConnectionsWithProxies(fedReactor); - - createLauncher(fileConfig, errorReporter, federationRTIProperties); + replaceFederateConnectionsWithProxies(federation); FedEmitter fedEmitter = new FedEmitter( fileConfig, ASTUtils.toDefinition(mainDef.getReactorClass()), errorReporter, - federationRTIProperties + rtiConfig ); - // Generate code for each federate + + // Generate LF code for each federate. Map lf2lfCodeMapMap = new HashMap<>(); for (FederateInstance federate : federates) { lf2lfCodeMapMap.putAll(fedEmitter.generateFederate( @@ -172,45 +165,64 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws )); } + // Do not invoke target code generators if --no-compile flag is used. if (context.getTargetConfig().noCompile) { context.finish(Status.GENERATED, lf2lfCodeMapMap); return false; } Map codeMapMap = compileFederates(context, lf2lfCodeMapMap, subContexts -> { - if (context.getTargetConfig().dockerOptions == null) return; - final List services = new ArrayList<>(); - // 1. create a Dockerfile for each federate - for (SubContext subContext : subContexts) {// Inherit Docker options from main context - subContext.getTargetConfig().dockerOptions = context.getTargetConfig().dockerOptions; - var dockerGenerator = dockerGeneratorFactory(subContext); - var dockerData = dockerGenerator.generateDockerData(); - try { - dockerData.writeDockerFile(); - } catch (IOException e) { - throw new RuntimeIOException(e); - } - services.add(dockerData); - } - // 2. create a docker-compose.yml for the federation - try { - // FIXME: https://issue.lf-lang.org/1559 - // It appears that the rtiHost information should come from federationRTIproperties, - // which is a kludge and not in scope here. - new FedDockerComposeGenerator(context, "localhost").writeDockerComposeFile(services); - } catch (IOException e) { - throw new RuntimeIOException(e); - } + createDockerFiles(context, subContexts); + generateLaunchScript(); }); context.finish(Status.COMPILED, codeMapMap); return false; } + private void generateLaunchScript() { + new FedLauncherGenerator( + this.targetConfig, + this.fileConfig, + this.errorReporter + ).doGenerate(federates, rtiConfig); + } + + /** + * Generate a Dockerfile for each federate and a docker-compose.yml for the federation. + * @param context The main context in which the federation has been compiled. + * @param subContexts The subcontexts in which the federates have been compiled. + */ + private void createDockerFiles(LFGeneratorContext context, List subContexts) { + if (context.getTargetConfig().dockerOptions == null) return; + final List services = new ArrayList<>(); + // 1. create a Dockerfile for each federate + for (SubContext subContext : subContexts) {// Inherit Docker options from main context + subContext.getTargetConfig().dockerOptions = context.getTargetConfig().dockerOptions; + var dockerGenerator = dockerGeneratorFactory(subContext); + var dockerData = dockerGenerator.generateDockerData(); + try { + dockerData.writeDockerFile(); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + services.add(dockerData); + } + // 2. create a docker-compose.yml for the federation + try { + new FedDockerComposeGenerator( + context, + rtiConfig.getHost() + ).writeDockerComposeFile(services); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + } + /** * Check if a clean was requested from the standalone compiler and perform * the clean step. - * @param context + * @param context Context in which the generator operates */ private void cleanIfNeeded(LFGeneratorContext context) { if (context.getArgs().containsKey(BuildParm.CLEAN.getKey())) { @@ -222,54 +234,25 @@ private void cleanIfNeeded(LFGeneratorContext context) { } } - /** - * Create a launcher for the federation. - * @param fileConfig - * @param errorReporter - * @param federationRTIProperties - */ - public void createLauncher( - FedFileConfig fileConfig, - ErrorReporter errorReporter, - LinkedHashMap federationRTIProperties - ) { - FedLauncher launcher; - if (federates.size() == 0) { - // no federates, use target properties of main file - TargetDecl targetDecl = GeneratorUtils.findTarget(fileConfig.resource); - launcher = FedLauncherFactory.getLauncher(Target.fromDecl(targetDecl), - targetConfig, - fileConfig, - errorReporter); - } else { - launcher = FedLauncherFactory.getLauncher( - federates.get(0), // FIXME: This would not work for mixed-target programs. - fileConfig, - errorReporter - ); - } - try { - launcher.createLauncher( - federates, - federationRTIProperties - ); - } catch (IOException e) { - errorReporter.reportError(e.getMessage()); - } - - // System.out.println(PythonInfoGenerator.generateFedRunInfo(fileConfig)); - } - /** Return whether federated execution is supported for {@code resource}. */ private boolean federatedExecutionIsSupported(Resource resource) { - var target = Target.fromDecl(GeneratorUtils.findTarget(resource)); - var ret = List.of(Target.C, Target.Python, Target.TS, Target.CPP, Target.CCPP).contains(target); - if (!ret) { + var target = Target.fromDecl(GeneratorUtils.findTargetDecl(resource)); + var targetOK = List.of( + Target.C, Target.Python, Target.TS, Target.CPP, Target.CCPP + ).contains(target); + if (!targetOK) { errorReporter.reportError( "Federated execution is not supported with target " + target + "." ); } - return ret; + if(target.equals(Target.C) && GeneratorUtils.isHostWindows()) { + errorReporter.reportError( + "Federated LF programs with a C target are currently not supported on Windows." + ); + targetOK = false; + } + + return targetOK; } private Map compileFederates( @@ -295,7 +278,7 @@ private Map compileFederates( var compileThreadPool = Executors.newFixedThreadPool(numOfCompileThreads); System.out.println("******** Using "+numOfCompileThreads+" threads to compile the program."); Map codeMapMap = new ConcurrentHashMap<>(); - List subContexts = Collections.synchronizedList(new ArrayList()); + List subContexts = Collections.synchronizedList(new ArrayList<>()); Averager averager = new Averager(federates.size()); final var threadSafeErrorReporter = new SynchronizedErrorReporter(errorReporter); for (int i = 0; i < federates.size(); i++) { @@ -314,8 +297,8 @@ private Map compileFederates( } props.put("docker", "false"); - TargetConfig subConfig = GeneratorUtils.getTargetConfig( - props, GeneratorUtils.findTarget(subFileConfig.resource), subContextErrorReporter + TargetConfig subConfig = new TargetConfig( + props, GeneratorUtils.findTargetDecl(subFileConfig.resource), subContextErrorReporter ); SubContext subContext = new SubContext(context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, 100) { @Override @@ -393,34 +376,34 @@ private void setFederationRTIProperties(LFGeneratorContext context) { String port = matcher.group(3); if (host != null) { - federationRTIProperties.put("host", host); + rtiConfig.setHost(host); } if (port != null) { - federationRTIProperties.put("port", port); + rtiConfig.setPort(Integer.parseInt(port)); } if (user != null) { - federationRTIProperties.put("user", user); + rtiConfig.setUser(user); } } /** * Analyze the federation and record various properties of it. * - * @param fedReactor The federated reactor that contains all federates' instances. + * @param federation The federated reactor that contains all federates' instances. */ - private void analyzeFederates(Reactor fedReactor) { + private void analyzeFederates(Reactor federation, LFGeneratorContext context) { // Create an instantiation for the fed reactor because there isn't one. // Creating a definition for the main reactor because there isn't one. mainDef = LfFactory.eINSTANCE.createInstantiation(); - mainDef.setName(fedReactor.getName()); - mainDef.setReactorClass(fedReactor); + mainDef.setName(federation.getName()); + mainDef.setReactorClass(federation); // Make sure that if no federation RTI properties were given in the // cmdline, then those specified in the lf file are not lost - if (federationRTIProperties.get("host").equals("localhost") && - fedReactor.getHost() != null && - !fedReactor.getHost().getAddr().equals("localhost")) { - federationRTIProperties.put("host", fedReactor.getHost().getAddr()); + if (rtiConfig.getHost().equals("localhost") && + federation.getHost() != null && + !federation.getHost().getAddr().equals("localhost")) { + rtiConfig.setHost(federation.getHost().getAddr()); } // Since federates are always within the main (federated) reactor, @@ -429,15 +412,15 @@ private void analyzeFederates(Reactor fedReactor) { List mainReactorContext = new ArrayList<>(); mainReactorContext.add(mainDef); - // Create a FederateInstance for each top-level reactor. - for (Instantiation instantiation : ASTUtils.allInstantiations(fedReactor)) { + // Create a FederateInstance for each instance in the top-level reactor. + for (Instantiation instantiation : ASTUtils.allInstantiations(federation)) { int bankWidth = ASTUtils.width(instantiation.getWidthSpec(), mainReactorContext); if (bankWidth < 0) { errorReporter.reportError(instantiation, "Cannot determine bank width! Assuming width of 1."); // Continue with a bank width of 1. bankWidth = 1; } - List federateInstances = getFederateInstances(instantiation, bankWidth); + List federateInstances = getFederateInstances(instantiation, bankWidth, context); if (federatesByInstantiation == null) { federatesByInstantiation = new LinkedHashMap<>(); } @@ -454,17 +437,23 @@ private void analyzeFederates(Reactor fedReactor) { * @param bankWidth The width specified for the instantiation. * @return A list of federate instance (of type @see FederateInstance). */ - private List getFederateInstances(Instantiation instantiation, int bankWidth) { + private List getFederateInstances(Instantiation instantiation, int bankWidth, LFGeneratorContext context) { // Create one federate instance for each instance in a bank of reactors. List federateInstances = new ArrayList<>(bankWidth); + for (int i = 0; i < bankWidth; i++) { // Assign an integer ID to the federate. int federateID = federates.size(); - FederateInstance federateInstance = new FederateInstance(instantiation, federateID, i, errorReporter); - federateInstance.bankIndex = i; + var resource = instantiation.getReactorClass().eResource(); + var federateTargetConfig = new FedTargetConfig(context, resource); + FederateInstance federateInstance = new FederateInstance( + instantiation, + federateID, + i, + federateTargetConfig, + errorReporter); federates.add(federateInstance); federateInstances.add(federateInstance); - federateByID.put(federateID, federateInstance); if (instantiation.getHost() != null) { federateInstance.host = instantiation.getHost().getAddr(); @@ -489,16 +478,16 @@ private List getFederateInstances(Instantiation instantiation, * Replace connections between federates in the AST with proxies that * handle sending and receiving data. * - * @param fedReactor + * @param federation Reactor class of the federation. */ - private void replaceFederateConnectionsWithProxies(Reactor fedReactor) { + private void replaceFederateConnectionsWithProxies(Reactor federation) { // Each connection in the AST may represent more than one connection between - // federate instances because of banks and multiports. We need to generate communication + // federation instances because of banks and multiports. We need to generate communication // for each of these. To do this, we create a ReactorInstance so that we don't have // to duplicate the rather complicated logic in that class. We specify a depth of 1, // so it only creates the reactors immediately within the top level, not reactors // that those contain. - ReactorInstance mainInstance = new ReactorInstance(fedReactor, errorReporter); + ReactorInstance mainInstance = new ReactorInstance(federation, errorReporter); for (ReactorInstance child : mainInstance.children) { for (PortInstance output : child.outputs) { @@ -507,7 +496,7 @@ private void replaceFederateConnectionsWithProxies(Reactor fedReactor) { } // Remove the connections at the top level - fedReactor.getConnections().clear(); + federation.getConnections().clear(); // There will be AST transformations that invalidate some info // cached in ReactorInstance. FIXME: most likely not needed anymore diff --git a/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java index 86923c8914..95351237a7 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java @@ -54,7 +54,7 @@ String generateImports(FederateInstance federate, FedFileConfig fileConfig) { ); return new_import; }) - .map(FormattingUtils.renderer(federate.target)) + .map(FormattingUtils.renderer(federate.targetConfig.target)) .collect(Collectors.joining("\n"))); return importStatements.getCode(); diff --git a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java index ee3b64ab4a..54e215264f 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java @@ -41,7 +41,7 @@ String generateMainReactor(FederateInstance federate, Reactor originalMainReacto "Modes at the top level are not supported under federated execution." ); } - var renderer = FormattingUtils.renderer(federate.target); + var renderer = FormattingUtils.renderer(federate.targetConfig.target); return String .join( diff --git a/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java index 827a31fe33..20771e7848 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java @@ -8,6 +8,7 @@ import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.federated.extensions.FedTargetExtensionFactory; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Model; import org.lflang.lf.Preamble; @@ -20,7 +21,7 @@ public FedPreambleEmitter() {} * Add necessary code to the source and necessary build support to * enable the requested serializations in 'enabledSerializations' */ - String generatePreamble(FederateInstance federate, FedFileConfig fileConfig, LinkedHashMap federationRTIProperties, ErrorReporter errorReporter) + String generatePreamble(FederateInstance federate, FedFileConfig fileConfig, RtiConfig rtiConfig, ErrorReporter errorReporter) throws IOException { CodeBuilder preambleCode = new CodeBuilder(); @@ -38,8 +39,8 @@ String generatePreamble(FederateInstance federate, FedFileConfig fileConfig, Lin )); } - preambleCode.pr(FedTargetExtensionFactory.getExtension(federate.target).generatePreamble( - federate, fileConfig, federationRTIProperties, errorReporter)); + preambleCode.pr(FedTargetExtensionFactory.getExtension(federate.targetConfig.target).generatePreamble( + federate, fileConfig, rtiConfig, errorReporter)); return preambleCode.getCode(); } diff --git a/org.lflang/src/org/lflang/federated/generator/FedReactorEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedReactorEmitter.java index 066b344417..976049601c 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedReactorEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedReactorEmitter.java @@ -18,7 +18,7 @@ String generateReactorDefinitions(FederateInstance federate) { .getReactors() .stream() .filter(federate::contains) - .map(FormattingUtils.renderer(federate.target)) + .map(FormattingUtils.renderer(federate.targetConfig.target)) .collect(Collectors.joining("\n")); } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedTargetConfig.java b/org.lflang/src/org/lflang/federated/generator/FedTargetConfig.java new file mode 100644 index 0000000000..9af20347c2 --- /dev/null +++ b/org.lflang/src/org/lflang/federated/generator/FedTargetConfig.java @@ -0,0 +1,78 @@ +package org.lflang.federated.generator; + +import static org.lflang.ASTUtils.convertToEmptyListIfNull; + +import org.lflang.ErrorReporter; +import org.lflang.TargetConfig; +import org.lflang.TargetProperty; +import org.lflang.generator.GeneratorUtils; +import org.lflang.generator.LFGeneratorContext; +import org.eclipse.emf.ecore.resource.Resource; + +/** + * Subclass of TargetConfig with a specialized constructor for creating configurations for federates. + * @author Marten Lohstroh + */ +public class FedTargetConfig extends TargetConfig { + + /** + * Create a configuration for a federate given a main context and the resource in which the class + * of the federate is specified. + * @param context The generator context. + * @param federateResource The resource in which to find the reactor class of the federate. + */ + public FedTargetConfig(LFGeneratorContext context, Resource federateResource) { + // Create target config based on the main .lf file + super( + context.getArgs(), + GeneratorUtils.findTargetDecl(context.getFileConfig().resource), + context.getErrorReporter() + ); + + mergeImportedConfig( + federateResource, + context.getFileConfig().resource, + context.getErrorReporter() + ); + + clearPropertiesToIgnore(); + + ((FedFileConfig)context.getFileConfig()).relativizePaths(this); + + } + + /** + * If the federate that target configuration applies to is imported, merge target properties + * declared in the file that it was imported from. + * @param federateResource The resource where the class of the federate is specified. + * @param mainResource The resource in which the federation (i.e., main reactor) is specified. + * @param errorReporter An error reporter to use when problems are encountered. + */ + private void mergeImportedConfig( + Resource federateResource, + Resource mainResource, + ErrorReporter errorReporter) { + // If the federate is imported, then update the configuration based on target properties + // in the imported file. + if (!federateResource.equals(mainResource)) { + var importedTargetDecl = GeneratorUtils.findTargetDecl(federateResource); + var targetProperties = importedTargetDecl.getConfig(); + if (targetProperties != null) { + // Merge properties + TargetProperty.update( + this, + convertToEmptyListIfNull(targetProperties.getPairs()), + errorReporter + ); + } + } + } + + /** + * Method for the removal of things that should not appear in the target config of a federate. + */ + private void clearPropertiesToIgnore() { + this.setByUser.remove(TargetProperty.CLOCK_SYNC); + this.setByUser.remove(TargetProperty.CLOCK_SYNC_OPTIONS); + } +} diff --git a/org.lflang/src/org/lflang/federated/generator/FedTargetEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedTargetEmitter.java index 7143eb18c5..1616286048 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedTargetEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedTargetEmitter.java @@ -1,19 +1,12 @@ package org.lflang.federated.generator; -import static org.lflang.ASTUtils.convertToEmptyListIfNull; - import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; import org.lflang.ErrorReporter; -import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.ast.FormattingUtils; import org.lflang.federated.extensions.FedTargetExtensionFactory; -import org.lflang.generator.GeneratorUtils; +import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.LFGeneratorContext; public class FedTargetEmitter { @@ -24,86 +17,28 @@ String generateTarget( FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter, - LinkedHashMap federationRTIProperties + RtiConfig rtiConfig ) throws IOException { - federate.targetConfig = - GeneratorUtils.getTargetConfig( - context.getArgs(), - federate.target, - errorReporter - ); - // FIXME: Should we merge some properties with the main .lf file if the federate is imported? - // https://issue.lf-lang.org/1560 - var fedReactorClass = federate.instantiation.getReactorClass(); - if (!fedReactorClass.eResource().equals(fileConfig.resource)) { - // Merge some target properties of the main .lf file. - var target = GeneratorUtils.findTarget(fileConfig.resource); - if (target.getConfig() != null) { - // Merge properties - TargetProperty.update( - federate.targetConfig, - convertToEmptyListIfNull(target.getConfig().getPairs()), - errorReporter - ); - } - } - - relativizeTargetPaths(federate, fileConfig); - - clearFederatedTargetPropertiesI(federate); - FedTargetExtensionFactory.getExtension(federate.target) + // FIXME: First of all, this is not an initialization; there is all sorts of stuff happening + // in the C implementation of this method. Second, true initialization stuff should happen + // when the target config is constructed, not when we're doing code generation. + // See https://issues.lf-lang.org/1667 + FedTargetExtensionFactory.getExtension(federate.targetConfig.target) .initializeTargetConfig( context, numOfFederates, federate, fileConfig, errorReporter, - federationRTIProperties + rtiConfig ); - return FormattingUtils.renderer(federate.target).apply( + return FormattingUtils.renderer(federate.targetConfig.target).apply( TargetProperty.extractTargetDecl( - Target.fromDecl(federate.target), + federate.targetConfig.target, federate.targetConfig ) ); } - - /** - * Clear target properties that should not end up in the generated .lf file - * for {@code federate}. - */ - private void clearFederatedTargetPropertiesI(FederateInstance federate) { - federate.targetConfig.setByUser.remove(TargetProperty.CLOCK_SYNC); - federate.targetConfig.setByUser.remove(TargetProperty.CLOCK_SYNC_OPTIONS); - } - - - /** - * Relativize target properties that involve paths like files and cmake-include to be - * relative to the generated .lf file for the federate. - */ - private void relativizeTargetPaths(FederateInstance federate, FedFileConfig fileConfig) { - // FIXME: Should we relativize here or calculate absolute paths? - relativizePathList(federate.targetConfig.protoFiles, fileConfig); - - relativizePathList(federate.targetConfig.fileNames, fileConfig); - - relativizePathList(federate.targetConfig.cmakeIncludes, fileConfig); - } - - private void relativizePathList(List paths, FedFileConfig fileConfig) { - List tempList = new ArrayList<>(); - paths.forEach( f -> { - tempList.add(relativizePath(f, fileConfig)); - }); - paths.clear(); - paths.addAll(tempList); - } - - private String relativizePath(String path, FedFileConfig fileConfig) { - Path resolvedPath = fileConfig.srcPath.resolve(path).toAbsolutePath(); - return fileConfig.getSrcPath().relativize(resolvedPath).toString(); - } } diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index af581cc74d..f3768cec7c 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -25,6 +25,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.federated.generator; +import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -39,6 +40,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.ASTUtils; import org.lflang.ErrorReporter; +import org.lflang.FileConfig; +import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TimeValue; import org.lflang.federated.serialization.SupportedSerializers; @@ -47,6 +50,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 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; @@ -62,7 +66,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.StateVar; -import org.lflang.lf.TargetDecl; import org.lflang.lf.Timer; import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; @@ -98,15 +101,14 @@ 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.target = GeneratorUtils.findTarget( - ASTUtils.toDefinition(instantiation.getReactorClass()).eResource() - ); - this.targetConfig = new TargetConfig(target); // FIXME: this is actually set in FedTargetEmitter. Why? + this.targetConfig = targetConfig; + if (instantiation != null) { this.name = instantiation.getName(); // If the instantiation is in a bank, then we have to append @@ -249,11 +251,6 @@ public Instantiation getInstantiation() { */ public List networkReactions = new ArrayList<>(); - /** - * Target of the federate. - */ - public TargetDecl target; - /** * Parsed target config of the federate. */ diff --git a/org.lflang/src/org/lflang/federated/launcher/BuildConfig.java b/org.lflang/src/org/lflang/federated/launcher/BuildConfig.java new file mode 100644 index 0000000000..c1a01bfa47 --- /dev/null +++ b/org.lflang/src/org/lflang/federated/launcher/BuildConfig.java @@ -0,0 +1,60 @@ +package org.lflang.federated.launcher; + +import org.lflang.ErrorReporter; +import org.lflang.federated.generator.FedFileConfig; +import org.lflang.federated.generator.FederateInstance; + +/** + * A collection of methods used for building target code for federates. + */ +public abstract class BuildConfig { + + /** + * The federate that this configuration applies to. + */ + protected final FederateInstance federate; + + /** + * An error reporter to report problems. + */ + protected final ErrorReporter errorReporter; + + /** + * The file configuration of the federation that the federate belongs to. + */ + protected final FedFileConfig fileConfig; + + /** + * Create a new build configuration. + * + * @param federate The federate that this configuration applies to. + * @param fileConfig The file configuration of the federation that the federate belongs to. + * @param errorReporter An error reporter to report problems. + */ + public BuildConfig(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + this.errorReporter = errorReporter; + this.federate = federate; + this.fileConfig = fileConfig; + } + + /** + * Return the compile command for the federate that this build configuration belongs to. + */ + public String compileCommand() { + throw new UnsupportedOperationException(); + } + + /** + * Return the command that will execute the federate that this build configuration belongs to + * locally, assuming that the current directory is the top-level project folder. + */ + public abstract String localExecuteCommand(); + + /** + * Return the command that will execute the federate that this build configuration belongs to + * remotely, assuming that the current directory is the top-level project folder. + */ + public String remoteExecuteCommand() { + throw new UnsupportedOperationException(); + } +} diff --git a/org.lflang/src/org/lflang/federated/launcher/FedCLauncher.java b/org.lflang/src/org/lflang/federated/launcher/CBuildConfig.java similarity index 56% rename from org.lflang/src/org/lflang/federated/launcher/FedCLauncher.java rename to org.lflang/src/org/lflang/federated/launcher/CBuildConfig.java index 0b45444be9..bd88a719b4 100644 --- a/org.lflang/src/org/lflang/federated/launcher/FedCLauncher.java +++ b/org.lflang/src/org/lflang/federated/launcher/CBuildConfig.java @@ -26,10 +26,8 @@ package org.lflang.federated.launcher; import java.io.File; -import java.io.IOException; import org.lflang.ErrorReporter; -import org.lflang.TargetConfig; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; import org.lflang.generator.c.CCompiler; @@ -39,43 +37,25 @@ * that are written in C. * * @author Soroush Bateni + * @author Marten Lohstroh */ -public class FedCLauncher extends FedLauncher { +public class CBuildConfig extends BuildConfig { - /** - * Create an instance of FedCLauncher. - * - * @param targetConfig The current target configuration. - * @param fileConfig The current file configuration. - * @param errorReporter A error reporter for reporting any errors or warnings during the code generation - */ - public FedCLauncher( - TargetConfig targetConfig, - FedFileConfig fileConfig, - ErrorReporter errorReporter - ) { - super(targetConfig, fileConfig, errorReporter); + public CBuildConfig(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + super(federate, fileConfig, errorReporter); } - /** - * Return the compile command for a federate. - * - * @param federate The federate to compile. - * @throws IOException - */ @Override - protected - String compileCommandForFederate(FederateInstance federate) { - TargetConfig localTargetConfig = targetConfig; + public String compileCommand() { String commandToReturn = ""; // FIXME: Hack to add platform support only for linux systems. // We need to fix the CMake build command for remote federates. String linuxPlatformSupport = "core" + File.separator + "platform" + File.separator + "lf_linux_support.c"; - if (!localTargetConfig.compileAdditionalSources.contains(linuxPlatformSupport)) { - localTargetConfig.compileAdditionalSources.add(linuxPlatformSupport); + if (!federate.targetConfig.compileAdditionalSources.contains(linuxPlatformSupport)) { + federate.targetConfig.compileAdditionalSources.add(linuxPlatformSupport); } - CCompiler cCompiler= new CCompiler(localTargetConfig, fileConfig, errorReporter, false); + CCompiler cCompiler= new CCompiler(federate.targetConfig, fileConfig, errorReporter, false); commandToReturn = String.join(" ", cCompiler.compileCCommand( fileConfig.name+"_"+federate.name, @@ -84,28 +64,13 @@ String compileCommandForFederate(FederateInstance federate) { return commandToReturn; } - /** - * Return the command that will execute a remote federate, assuming that the current - * directory is the top-level project folder. This is used to create a launcher script - * for federates. - * - * @param federate The federate to execute. - */ @Override - protected - String executeCommandForRemoteFederate(FederateInstance federate) { + public String remoteExecuteCommand() { return "bin/"+fileConfig.name+"_"+federate.name+" -i '$FEDERATION_ID'"; } - /** - * Return the command that will execute a local federate. - * This is used to create a launcher script for federates. - * - * @param federate The federate to execute. - */ @Override - protected - String executeCommandForLocalFederate(FedFileConfig fileConfig, FederateInstance federate) { - return fileConfig.getGenPath().resolve("bin/"+federate.name)+" -i $FEDERATION_ID"; + public String localExecuteCommand() { + return fileConfig.getFedBinPath().resolve(federate.name)+" -i $FEDERATION_ID"; } } diff --git a/org.lflang/src/org/lflang/federated/launcher/FedLauncherFactory.java b/org.lflang/src/org/lflang/federated/launcher/FedLauncherFactory.java deleted file mode 100644 index c92cea1807..0000000000 --- a/org.lflang/src/org/lflang/federated/launcher/FedLauncherFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.lflang.federated.launcher; - -import org.lflang.ErrorReporter; -import org.lflang.Target; -import org.lflang.TargetConfig; -import org.lflang.federated.generator.FedFileConfig; -import org.lflang.federated.generator.FederateInstance; - -/** - * Helper class to get the appropriate launcher generator. - * - * FIXME: This architecture needs to be redesigned for multi-target federations. - */ -public class FedLauncherFactory { - - public static FedLauncher getLauncher ( - FederateInstance federate, - FedFileConfig fileConfig, - ErrorReporter errorReporter - ) { - return getLauncher(Target.fromDecl(federate.target), federate.targetConfig, fileConfig, errorReporter); - } - - /** - * Return a launcher generator. - * @param target The target to generate for. - * @param targetConfig The target config of the federate. - * @param fileConfig The file config for the federate. - * @param errorReporter The error reporter to use. - * @return null if not supported, an instance of {@code #FedLauncher} otherwise. - */ - public static FedLauncher getLauncher( - Target target, - TargetConfig targetConfig, - FedFileConfig fileConfig, - ErrorReporter errorReporter - ) { - switch (target) { - case C: - case CCPP: - return new FedCLauncher( - targetConfig, - fileConfig, - errorReporter - ); - case CPP: - case Rust: - return null; - case TS: - return new FedTSLauncher( - targetConfig, - fileConfig, - errorReporter - ); - case Python: - return new FedPyLauncher( - targetConfig, - fileConfig, - errorReporter - ); - } - return null; - } -} diff --git a/org.lflang/src/org/lflang/federated/launcher/FedLauncher.java b/org.lflang/src/org/lflang/federated/launcher/FedLauncherGenerator.java similarity index 86% rename from org.lflang/src/org/lflang/federated/launcher/FedLauncher.java rename to org.lflang/src/org/lflang/federated/launcher/FedLauncherGenerator.java index 48a7149729..3ad08e2450 100644 --- a/org.lflang/src/org/lflang/federated/launcher/FedLauncher.java +++ b/org.lflang/src/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -26,15 +26,16 @@ package org.lflang.federated.launcher; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import org.lflang.ErrorReporter; +import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TargetProperty.ClockSyncMode; import org.lflang.federated.generator.FedFileConfig; @@ -46,8 +47,7 @@ * @author Edward A. Lee * @author Soroush Bateni */ -public class FedLauncher { - +public class FedLauncherGenerator { protected TargetConfig targetConfig; protected FedFileConfig fileConfig; protected ErrorReporter errorReporter; @@ -57,44 +57,12 @@ public class FedLauncher { * @param fileConfig The current file configuration. * @param errorReporter A error reporter for reporting any errors or warnings during the code generation */ - public FedLauncher(TargetConfig targetConfig, FedFileConfig fileConfig, ErrorReporter errorReporter) { + public FedLauncherGenerator(TargetConfig targetConfig, FedFileConfig fileConfig, ErrorReporter errorReporter) { this.targetConfig = targetConfig; this.fileConfig = fileConfig; this.errorReporter = errorReporter; } - /** - * Return the compile command for a federate. - * - * @param federate The federate to compile. - */ - protected String compileCommandForFederate(FederateInstance federate) { - throw new UnsupportedOperationException("Don't know how to compile the federates."); - } - - /** - * Return the command that will execute a remote federate, assuming that the current - * directory is the top-level project folder. This is used to create a launcher script - * for federates. - * - * @param federate The federate to execute. - */ - protected String executeCommandForRemoteFederate(FederateInstance federate) { - throw new UnsupportedOperationException("Don't know how to execute the federates."); - } - - /** - * Return the command that will execute a local federate, assuming that the current - * directory is the top-level project folder. This is used to create a launcher script - * for federates. - * - * @param federate The federate to execute. - */ - protected String executeCommandForLocalFederate(FedFileConfig fileConfig, - FederateInstance federate) { - throw new UnsupportedOperationException("Don't know how to execute the federates."); - } - /** * Create the launcher shell scripts. This will create one or two files * in the output path (bin directory). The first has name equal to @@ -131,13 +99,13 @@ protected String executeCommandForLocalFederate(FedFileConfig fileConfig, * openssl version * * @param federates A list of federate instances in the federation - * @param federationRTIProperties Contains relevant properties of the RTI. + * @param rtiConfig * Can have values for 'host', 'dir', and 'user' */ - public void createLauncher( + public void doGenerate( List federates, - LinkedHashMap federationRTIProperties - ) throws IOException { + RtiConfig rtiConfig + ) { // NOTE: It might be good to use screen when invoking the RTI // or federates remotely, so you can detach and the process keeps running. // However, I was unable to get it working properly. @@ -154,12 +122,10 @@ public void createLauncher( StringBuilder distCode = new StringBuilder(); shCode.append(getSetupCode() + "\n"); String distHeader = getDistHeader(); - Object host = federationRTIProperties.get("host"); - Object target = host; + String host = rtiConfig.getHost(); + String target = host; - Path path = Path.of(federationRTIProperties.get("dir") == null ? "LinguaFrancaRemote" : federationRTIProperties.get("dir").toString()); - - Object user = federationRTIProperties.get("user"); + String user = rtiConfig.getUser(); if (user != null) { target = user + "@" + host; } @@ -199,17 +165,18 @@ public void createLauncher( // Index used for storing pids of federates int federateIndex = 0; for (FederateInstance federate : federates) { + var buildConfig = getBuildConfig(federate, fileConfig, errorReporter); if (federate.isRemote) { Path fedRelSrcGenPath = fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath()).resolve(federate.name); if(distCode.length() == 0) distCode.append(distHeader + "\n"); String logFileName = String.format("log/%s_%s.log", fileConfig.name, federate.name); - String compileCommand = compileCommandForFederate(federate); + String compileCommand = buildConfig.compileCommand(); // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the remote host? - distCode.append(getDistCode(path, federate, fedRelSrcGenPath, logFileName, fileConfig.getSrcGenPath(), compileCommand) + "\n"); - String executeCommand = executeCommandForRemoteFederate(federate); - shCode.append(getFedRemoteLaunchCode(federate, path, logFileName, executeCommand, federateIndex++) + "\n"); + distCode.append(getDistCode(rtiConfig.getDirectory(), federate, fedRelSrcGenPath, logFileName, fileConfig.getSrcGenPath(), compileCommand) + "\n"); + String executeCommand = buildConfig.remoteExecuteCommand(); + shCode.append(getFedRemoteLaunchCode(federate, rtiConfig.getDirectory(), logFileName, executeCommand, federateIndex++) + "\n"); } else { - String executeCommand = executeCommandForLocalFederate(fileConfig, federate); + String executeCommand = buildConfig.localExecuteCommand(); shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++) + "\n"); } } @@ -232,7 +199,11 @@ public void createLauncher( // Create bin directory for the script. if (!Files.exists(fileConfig.binPath)) { - Files.createDirectories(fileConfig.binPath); + try { + Files.createDirectories(fileConfig.binPath); + } catch (IOException e) { + errorReporter.reportError("Unable to create directory: " + fileConfig.binPath); + } } System.out.println("##### Generating launcher for federation " @@ -246,9 +217,19 @@ public void createLauncher( file.delete(); } - FileOutputStream fOut = new FileOutputStream(file); - fOut.write(shCode.toString().getBytes()); - fOut.close(); + FileOutputStream fOut = null; + try { + fOut = new FileOutputStream(file); + } catch (FileNotFoundException e) { + errorReporter.reportError("Unable to find file: " + file); + } + try { + fOut.write(shCode.toString().getBytes()); + fOut.close(); + } catch (IOException e) { + errorReporter.reportError("Unable to write to file: " + file); + } + if (!file.setExecutable(true, false)) { errorReporter.reportWarning("Unable to make launcher script executable."); } @@ -260,11 +241,17 @@ public void createLauncher( file.delete(); } if (distCode.length() > 0) { - fOut = new FileOutputStream(file); - fOut.write(distCode.toString().getBytes()); - fOut.close(); - if (!file.setExecutable(true, false)) { - errorReporter.reportWarning("Unable to make distributor script executable."); + try { + fOut = new FileOutputStream(file); + fOut.write(distCode.toString().getBytes()); + fOut.close(); + if (!file.setExecutable(true, false)) { + errorReporter.reportWarning("Unable to make file executable: " + file); + } + } catch (FileNotFoundException e) { + errorReporter.reportError("Unable to find file: " + file); + } catch (IOException e) { + errorReporter.reportError("Unable to write to file " + file); } } } @@ -329,6 +316,9 @@ private String getRtiCommand(List federates, boolean isRemote) if (targetConfig.auth) { commands.add(" -a \\"); } + if (targetConfig.tracing != null) { + commands.add(" -t \\"); + } commands.addAll(List.of( " -n "+federates.size()+" \\", " -c "+targetConfig.clockSync.toString()+" \\" @@ -451,7 +441,7 @@ private String getUserHost(Object user, Object host) { private String getFedRemoteLaunchCode( FederateInstance federate, - Object path, + Path path, String logFileName, String executeCommand, int federateIndex @@ -482,4 +472,24 @@ private String getFedLocalLaunchCode(FederateInstance federate, String executeCo executeCommand, federateIndex); } + + /** + * Create a build configuration of the appropriate target. + * + * @param federate The federate to which the build configuration applies. + * @param fileConfig The file configuration of the federation to which the federate belongs. + * @param errorReporter An error reporter to report problems. + * @return + */ + private BuildConfig getBuildConfig( + FederateInstance federate, + FedFileConfig fileConfig, + ErrorReporter errorReporter) { + return switch(federate.targetConfig.target) { + case C, CCPP -> new CBuildConfig(federate, fileConfig, errorReporter); + case Python -> new PyBuildConfig(federate, fileConfig, errorReporter); + case TS -> new TsBuildConfig(federate, fileConfig, errorReporter); + case CPP, Rust -> throw new UnsupportedOperationException(); + }; + } } diff --git a/org.lflang/src/org/lflang/federated/launcher/FedPyLauncher.java b/org.lflang/src/org/lflang/federated/launcher/FedPyLauncher.java deleted file mode 100644 index 414caf2ee4..0000000000 --- a/org.lflang/src/org/lflang/federated/launcher/FedPyLauncher.java +++ /dev/null @@ -1,83 +0,0 @@ -/************* - * Copyright (c) 2021, The University of California at Berkeley. - * Copyright (c) 2021, The University of Texas at Dallas. - * - * 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.launcher; - -import org.lflang.ErrorReporter; -import org.lflang.TargetConfig; -import org.lflang.federated.generator.FedFileConfig; -import org.lflang.federated.generator.FederateInstance; - -/** - * Utility class that can be used to create a launcher for federated LF programs - * that are written in Python. - * - * @author Soroush Bateni - * - */ -public class FedPyLauncher extends FedLauncher { - /** - * Create an instance of FedPyLauncher. - * - * @param targetConfig The current target configuration. - * @param fileConfig The current file configuration. - * @param errorReporter A error reporter for reporting any errors or warnings during the code generation - */ - public FedPyLauncher( - TargetConfig targetConfig, - FedFileConfig fileConfig, - ErrorReporter errorReporter - ) { - super(targetConfig, fileConfig, errorReporter); - } - - /** - * Return the command that will execute a remote federate, assuming that the current - * directory is the top-level project folder. This is used to create a launcher script - * for federates. - * - * @param federate The federate to execute. - */ - @Override - protected - String executeCommandForRemoteFederate(FederateInstance federate) { - return "python3 src-gen/"+fileConfig.name+"/"+federate.name+"/"+fileConfig.name+"_"+federate.name+" -i '$FEDERATION_ID'"; - } - - /** - * Return the command that will execute a local federate, assuming that the current - * directory is the top-level project folder. This is used to create a launcher script - * for federates. - * - * @param federate The federate to execute. - */ - @Override - protected - String executeCommandForLocalFederate(FedFileConfig fileConfig, FederateInstance federate) { - return "python3 " + fileConfig.getSrcGenPath() + "/" + federate.name + "/" + federate.name+".py -i $FEDERATION_ID"; - } -} diff --git a/org.lflang/src/org/lflang/federated/launcher/PyBuildConfig.java b/org.lflang/src/org/lflang/federated/launcher/PyBuildConfig.java new file mode 100644 index 0000000000..85d6dc4a0f --- /dev/null +++ b/org.lflang/src/org/lflang/federated/launcher/PyBuildConfig.java @@ -0,0 +1,22 @@ +package org.lflang.federated.launcher; + +import org.lflang.ErrorReporter; +import org.lflang.federated.generator.FedFileConfig; +import org.lflang.federated.generator.FederateInstance; + +public class PyBuildConfig extends BuildConfig { + + public PyBuildConfig(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + super(federate, fileConfig, errorReporter); + } + + @Override + public String localExecuteCommand() { + return "python3 " + fileConfig.getSrcGenPath() + "/" + federate.name + "/" + federate.name+".py -i $FEDERATION_ID"; + } + + @Override + public String remoteExecuteCommand() { + return "python3 src-gen/"+fileConfig.name+"/"+federate.name+"/"+fileConfig.name+"_"+federate.name+" -i '$FEDERATION_ID'"; + } +} diff --git a/org.lflang/src/org/lflang/federated/launcher/RtiConfig.java b/org.lflang/src/org/lflang/federated/launcher/RtiConfig.java new file mode 100644 index 0000000000..81c608d5b3 --- /dev/null +++ b/org.lflang/src/org/lflang/federated/launcher/RtiConfig.java @@ -0,0 +1,92 @@ +package org.lflang.federated.launcher; + +import java.nio.file.Path; + +/** + * Class for storing configuration settings pertaining to the RTI. + * @author Marten Lohstroh + */ +public class RtiConfig { + + private Path directory; + + /** + * The host on which the RTI process is to be spawned. + */ + private String host; + + /** + * The port on which to connect to the RTI process. + */ + private int port; + + /** + * The username used to gain access to the host where the RTI is to be spawned. + */ + private String user; + + /** + * Construct a new RTI configuration with all options set to their defaults. + */ + public RtiConfig() { + this.directory = Path.of("LinguaFrancaRemote"); + this.host = "localhost"; + this.port = 0; + } + + /** + * Return the directory to create on the remote host. + */ + public Path getDirectory() { + return directory; + } + + /** + * Return the host on which the RTI process is to be spawned. + */ + public String getHost() { + return host; + } + + /** + * Return the port on which to connect to the RTI process. + */ + public int getPort() { + return port; + } + + /** + * Return the username used to gain access to the host where the RTI is to be spawned. + */ + public String getUser() { + return user; + } + + /** + * Set the directory to create on the remote host. + */ + public void setDirectory(Path directory) { + this.directory = directory; + } + + /** + * Set the host on which the RTI process is to be spawned. + */ + public void setHost(String host) { + this.host = host; + } + + /** + * Set the port on which to connect to the RTI process. + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Set the username used to gain access to the host where the RTI is to be spawned. + */ + public void setUser(String user) { + this.user = user; + } +} diff --git a/org.lflang/src/org/lflang/federated/launcher/FedTSLauncher.java b/org.lflang/src/org/lflang/federated/launcher/TsBuildConfig.java similarity index 68% rename from org.lflang/src/org/lflang/federated/launcher/FedTSLauncher.java rename to org.lflang/src/org/lflang/federated/launcher/TsBuildConfig.java index 67dbd134fa..87b7ffaeb1 100644 --- a/org.lflang/src/org/lflang/federated/launcher/FedTSLauncher.java +++ b/org.lflang/src/org/lflang/federated/launcher/TsBuildConfig.java @@ -36,35 +36,24 @@ * * @author Soroush Bateni * @author Hokeun Kim + * @author Marten Lohstroh */ -public class FedTSLauncher extends FedLauncher { +public class TsBuildConfig extends BuildConfig { - /** - * Create an instance of FedCLauncher. - * - * @param targetConfig The current target configuration. - * @param fileConfig The current file configuration. - * @param errorReporter A error reporter for reporting any errors or warnings during the code generation - */ - public FedTSLauncher( - TargetConfig targetConfig, - FedFileConfig fileConfig, - ErrorReporter errorReporter - ) { - super(targetConfig, fileConfig, errorReporter); + + public TsBuildConfig(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + super(federate, fileConfig, errorReporter); + } + + @Override + public String compileCommand() { + return null; } - - /** - * Return the command that will execute a local federate, assuming that the current - * directory is the top-level project folder. This is used to create a launcher script - * for federates. - * - * @param federate The federate to execute. - */ + @Override - protected - String executeCommandForLocalFederate(FedFileConfig fileConfig, FederateInstance federate) { + public String localExecuteCommand() { String jsFilename = federate.name + ".js"; return "node "+fileConfig.getSrcGenPath().resolve(federate.name).resolve("dist").resolve(jsFilename)+" -i $FEDERATION_ID"; } + } diff --git a/org.lflang/src/org/lflang/generator/GeneratorUtils.java b/org.lflang/src/org/lflang/generator/GeneratorUtils.java index 32ad03e08b..709e1943bd 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.java @@ -1,52 +1,30 @@ package org.lflang.generator; -import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.Properties; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.generator.IGeneratorContext; -import org.eclipse.xtext.resource.XtextResource; -import org.eclipse.xtext.validation.CheckMode; -import org.eclipse.xtext.validation.IResourceValidator; -import org.eclipse.xtext.validation.Issue; import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.Target; import org.lflang.TargetConfig; -import org.lflang.TargetConfig.DockerOptions; -import org.lflang.TargetProperty.BuildType; -import org.lflang.TargetProperty.LogLevel; -import org.lflang.TargetProperty.UnionType; import org.lflang.generator.LFGeneratorContext.Mode; import org.lflang.TargetProperty; -import org.lflang.TargetProperty.SchedulerOption; -import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; -import org.lflang.lf.Import; import org.lflang.lf.Instantiation; import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; -import org.lflang.lf.Model; -import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.TargetDecl; -import org.lflang.util.FileUtil; -import org.lflang.util.IteratorUtil; /** * A helper class with functions that may be useful for code @@ -65,81 +43,10 @@ private GeneratorUtils() { /** * Return the target declaration found in the given resource. */ - public static TargetDecl findTarget(Resource resource) { + public static TargetDecl findTargetDecl(Resource resource) { return findAll(resource, TargetDecl.class).iterator().next(); } - /** - * Set the appropriate target properties based on the target properties of - * the main .lf file and the given command-line arguments, if applicable. - * @param args The commandline arguments to process. - * @param target The target properties AST node. - * @param errorReporter The error reporter to which errors should be sent. - */ - public static TargetConfig getTargetConfig( - Properties args, - TargetDecl target, - ErrorReporter errorReporter - ) { - final TargetConfig targetConfig = new TargetConfig(target); // FIXME: why not just do all of this in the constructor? - if (target.getConfig() != null) { - List pairs = target.getConfig().getPairs(); - TargetProperty.set(targetConfig, pairs != null ? pairs : List.of(), errorReporter); - } - if (args.containsKey("no-compile")) { - targetConfig.noCompile = true; - } - if (args.containsKey("docker")) { - var arg = args.getProperty("docker"); - if (Boolean.parseBoolean(arg)) { - targetConfig.dockerOptions = new DockerOptions(); - } else { - targetConfig.dockerOptions = null; - } - // FIXME: this is pretty ad-hoc and does not account for more complex overrides yet. - } - if (args.containsKey("build-type")) { - targetConfig.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION.forName(args.getProperty("build-type")); - } - if (args.containsKey("logging")) { - targetConfig.logLevel = LogLevel.valueOf(args.getProperty("logging").toUpperCase()); - } - if (args.containsKey("workers")) { - targetConfig.workers = Integer.parseInt(args.getProperty("workers")); - } - if (args.containsKey("threading")) { - targetConfig.threading = Boolean.parseBoolean(args.getProperty("threading")); - } - if (args.containsKey("target-compiler")) { - targetConfig.compiler = args.getProperty("target-compiler"); - } - if (args.containsKey("scheduler")) { - targetConfig.schedulerType = SchedulerOption.valueOf( - args.getProperty("scheduler") - ); - targetConfig.setByUser.add(TargetProperty.SCHEDULER); - } - if (args.containsKey("target-flags")) { - targetConfig.compilerFlags.clear(); - if (!args.getProperty("target-flags").isEmpty()) { - targetConfig.compilerFlags.addAll(List.of( - args.getProperty("target-flags").split(" ") - )); - } - } - if (args.containsKey("runtime-version")) { - targetConfig.runtimeVersion = args.getProperty("runtime-version"); - } - if (args.containsKey("external-runtime-path")) { - targetConfig.externalRuntimePath = args.getProperty("external-runtime-path"); - } - if (args.containsKey(TargetProperty.KEEPALIVE.description)) { - targetConfig.keepalive = Boolean.parseBoolean( - args.getProperty(TargetProperty.KEEPALIVE.description)); - } - return targetConfig; - } - /** * Look for physical actions in 'resource'. * If appropriate, set keepalive to true in diff --git a/org.lflang/src/org/lflang/generator/MainContext.java b/org.lflang/src/org/lflang/generator/MainContext.java index e7d442e1d3..5ee53e5f83 100644 --- a/org.lflang/src/org/lflang/generator/MainContext.java +++ b/org.lflang/src/org/lflang/generator/MainContext.java @@ -163,8 +163,8 @@ public void reportProgress(String message, int percentage) { * reflected in the target configuration. */ public void loadTargetConfig() { - this.targetConfig = GeneratorUtils.getTargetConfig( - args, GeneratorUtils.findTarget(fileConfig.resource), errorReporter + this.targetConfig = new TargetConfig( + args, GeneratorUtils.findTargetDecl(fileConfig.resource), errorReporter ); } } diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 21ea083e49..3fd3675d52 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -301,7 +301,7 @@ class TSGenerator( if (ret != 0) { val errors: String = pnpmInstall.errors.toString() errorReporter.reportError( - GeneratorUtils.findTarget(resource), + GeneratorUtils.findTargetDecl(resource), "ERROR: pnpm install command failed" + if (errors.isBlank()) "." else ":\n$errors") } installProtoBufsIfNeeded(true, path, context.cancelIndicator) @@ -318,10 +318,10 @@ class TSGenerator( if (npmInstall.run(context.cancelIndicator) != 0) { errorReporter.reportError( - GeneratorUtils.findTarget(resource), + GeneratorUtils.findTargetDecl(resource), "ERROR: npm install command failed: " + npmInstall.errors.toString()) errorReporter.reportError( - GeneratorUtils.findTarget(resource), "ERROR: npm install command failed." + + GeneratorUtils.findTargetDecl(resource), "ERROR: npm install command failed." + "\nFor installation instructions, see: https://www.npmjs.com/get-npm") return } diff --git a/org.lflang/src/org/lflang/util/Averager.java b/org.lflang/src/org/lflang/util/Averager.java new file mode 100644 index 0000000000..81349c9db1 --- /dev/null +++ b/org.lflang/src/org/lflang/util/Averager.java @@ -0,0 +1,27 @@ +package org.lflang.util; + +import java.util.Arrays; + +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; + +/** Average asynchronously reported numbers and do something with them. */ +public class Averager { + private final int n; + private final int[] reports; + + /** Create an averager of reports from {@code n} processes. */ + public Averager(int n) { + this.n = n; + reports = new int[n]; + } + + /** + * Receive {@code x} from process {@code id} and invoke {@code callback} + * on the mean of the numbers most recently reported by the processes. + */ + public synchronized void report(int id, int x, Procedure1 callback) { + assert 0 <= id && id < n; + reports[id] = x; + callback.apply(Arrays.stream(reports).sum() / n); + } +} diff --git a/test/C/src/federated/SimpleFederatedAuthenticated.lf b/test/C/src/federated/failing/SimpleFederatedAuthenticated.lf similarity index 100% rename from test/C/src/federated/SimpleFederatedAuthenticated.lf rename to test/C/src/federated/failing/SimpleFederatedAuthenticated.lf diff --git a/util/tracing/README.md b/util/tracing/README.md index d189260ef7..28d8db23d7 100644 --- a/util/tracing/README.md +++ b/util/tracing/README.md @@ -3,12 +3,18 @@ This directory contains the source code for utilities that are standalone executables for post-processing tracing data created by the tracing function in Lingua Franca. +Utilities for visualizing the data are contained in the [visualization](visualization/README.md) +directory. + * trace\_to\_csv: Creates a comma-separated values text file from a binary trace file. The resulting file is suitable for analyzing in spreadsheet programs such as Excel. * trace\_to\_chrome: Creates a JSON file suitable for importing into Chrome's trace visualizer. Point Chrome to chrome://tracing/ and load the resulting file. +* trace\_to\_influxdb: A preliminary implementation that takes a binary trace file + and uploads its data into [InfluxDB](https://en.wikipedia.org/wiki/InfluxDB). + ## Installing ``` diff --git a/util/tracing/makefile b/util/tracing/makefile index eb21e42e62..407b153235 100644 --- a/util/tracing/makefile +++ b/util/tracing/makefile @@ -28,6 +28,8 @@ install: trace_to_csv trace_to_chrome trace_to_influxdb mv trace_to_csv ../../bin mv trace_to_chrome ../../bin mv trace_to_influxdb ../../bin + ln -f -s ../lib/scripts/launch-fedsd.sh ../../bin/fedsd + chmod +x ../../bin/fedsd clean: rm -f *.o diff --git a/util/tracing/trace_to_chrome.c b/util/tracing/trace_to_chrome.c index d39f53171e..92eeee71dd 100644 --- a/util/tracing/trace_to_chrome.c +++ b/util/tracing/trace_to_chrome.c @@ -43,6 +43,12 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** Maximum thread ID seen. */ int max_thread_id = 0; +/** File containing the trace binary data. */ +FILE* trace_file = NULL; + +/** File for writing the output data. */ +FILE* output_file = NULL; + /** * Print a usage message. */ @@ -62,17 +68,23 @@ bool physical_time_only = false; /** * Read a trace in the specified file and write it to the specified json file. + * @param trace_file An open trace file. + * @param output_file An open output .json file. * @return The number of records read or 0 upon seeing an EOF. */ -size_t read_and_write_trace() { +size_t read_and_write_trace(FILE* trace_file, FILE* output_file) { int trace_length = read_trace(trace_file); if (trace_length == 0) return 0; // Write each line. for (int i = 0; i < trace_length; i++) { char* reaction_name = "\"UNKNOWN\""; - if (trace[i].reaction_number >= 0) { + + // Ignore federated trace events. + if (trace[i].event_type > federated) continue; + + if (trace[i].dst_id >= 0) { reaction_name = (char*)malloc(4); - snprintf(reaction_name, 4, "%d", trace[i].reaction_number); + snprintf(reaction_name, 4, "%d", trace[i].dst_id); } // printf("DEBUG: Reactor's self struct pointer: %p\n", trace[i].pointer); int reactor_index; @@ -113,7 +125,7 @@ size_t read_and_write_trace() { } // Default thread id is the worker number. - int thread_id = trace[i].worker; + int thread_id = trace[i].src_id; char* args; asprintf(&args, "{" @@ -182,7 +194,7 @@ size_t read_and_write_trace() { phase = "E"; break; default: - fprintf(stderr, "WARNING: Unrecognized event type %d: %s", + fprintf(stderr, "WARNING: Unrecognized event type %d: %s\n", trace[i].event_type, trace_event_names[trace[i].event_type]); pid = PID_FOR_UNKNOWN_EVENT; phase = "i"; @@ -206,8 +218,8 @@ size_t read_and_write_trace() { ); free(args); - if (trace[i].worker > max_thread_id) { - max_thread_id = trace[i].worker; + if (trace[i].src_id > max_thread_id) { + max_thread_id = trace[i].src_id; } // If the event is reaction_starts and physical_time_only is not set, // then also generate an instantaneous @@ -217,13 +229,13 @@ size_t read_and_write_trace() { pid = reactor_index + 1; reaction_name = (char*)malloc(4); char name[13]; - snprintf(name, 13, "reaction %d", trace[i].reaction_number); + snprintf(name, 13, "reaction %d", trace[i].dst_id); // NOTE: If the reactor has more than 1024 timers and actions, then // there will be a collision of thread IDs here. - thread_id = 1024 + trace[i].reaction_number; - if (trace[i].reaction_number > max_reaction_number) { - max_reaction_number = trace[i].reaction_number; + thread_id = 1024 + trace[i].dst_id; + if (trace[i].dst_id > max_reaction_number) { + max_reaction_number = trace[i].dst_id; } fprintf(output_file, "{" @@ -253,8 +265,9 @@ size_t read_and_write_trace() { /** * Write metadata events, which provide names in the renderer. + * @param output_file An open output .json file. */ -void write_metadata_events() { +void write_metadata_events(FILE* output_file) { // Thread 0 is the main thread. fprintf(output_file, "{" "\"name\": \"thread_name\", " @@ -416,13 +429,22 @@ int main(int argc, char* argv[]) { usage(); exit(0); } - open_files(filename, "json"); + + // Open the trace file. + trace_file = open_file(filename, "r"); + + // Construct the name of the csv output file and open it. + char* root = root_name(filename); + char json_filename[strlen(root) + 6]; + strcpy(json_filename, root); + strcat(json_filename, ".json"); + output_file = open_file(json_filename, "w"); if (read_header(trace_file) >= 0) { // Write the opening bracket into the json file. fprintf(output_file, "{ \"traceEvents\": [\n"); - while (read_and_write_trace() != 0) {}; - write_metadata_events(); + while (read_and_write_trace(trace_file, output_file) != 0) {}; + write_metadata_events(output_file); fprintf(output_file, "]}\n"); } } diff --git a/util/tracing/trace_to_csv.c b/util/tracing/trace_to_csv.c index a2bcfd91a2..707cc5f43b 100644 --- a/util/tracing/trace_to_csv.c +++ b/util/tracing/trace_to_csv.c @@ -37,6 +37,15 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MAX_NUM_REACTIONS 64 // Maximum number of reactions reported in summary stats. #define MAX_NUM_WORKERS 64 +/** File containing the trace binary data. */ +FILE* trace_file = NULL; + +/** File for writing the output data. */ +FILE* output_file = NULL; + +/** File for writing summary statistics. */ +FILE* summary_file = NULL; + /** Size of the stats table is object_table_size plus twice MAX_NUM_WORKERS. */ int table_size; @@ -69,7 +78,7 @@ typedef struct reaction_stats_t { */ typedef struct summary_stats_t { trace_event_t event_type; // Use reaction_ends for reactions. - char* description; // Description in the reaction table (e.g. reactor name). + const char* description; // Description in the reaction table (e.g. reactor name). int occurrences; // Number of occurrences of this description. int num_reactions_seen; reaction_stats_t reactions[MAX_NUM_REACTIONS]; @@ -94,11 +103,6 @@ size_t read_and_write_trace() { if (trace_length == 0) return 0; // Write each line. for (int i = 0; i < trace_length; i++) { - char* reaction_name = "none"; - if (trace[i].reaction_number >= 0) { - reaction_name = (char*)malloc(4); - snprintf(reaction_name, 4, "%d", trace[i].reaction_number); - } // printf("DEBUG: reactor self struct pointer: %p\n", trace[i].pointer); int object_instance = -1; char* reactor_name = get_object_description(trace[i].pointer, &object_instance); @@ -110,11 +114,11 @@ size_t read_and_write_trace() { if (trigger_name == NULL) { trigger_name = "NO TRIGGER"; } - fprintf(output_file, "%s, %s, %s, %d, %lld, %d, %lld, %s, %lld\n", + fprintf(output_file, "%s, %s, %d, %d, %lld, %d, %lld, %s, %lld\n", trace_event_names[trace[i].event_type], reactor_name, - reaction_name, - trace[i].worker, + trace[i].src_id, + trace[i].dst_id, trace[i].logical_time - start_time, trace[i].microstep, trace[i].physical_time - start_time, @@ -125,33 +129,41 @@ size_t read_and_write_trace() { if (trace[i].physical_time > latest_time) { latest_time = trace[i].physical_time; } - if (summary_stats[object_instance] == NULL) { - summary_stats[object_instance] = (summary_stats_t*)calloc(1, sizeof(summary_stats_t)); + if (object_instance >= 0 && summary_stats[NUM_EVENT_TYPES + object_instance] == NULL) { + summary_stats[NUM_EVENT_TYPES + object_instance] = (summary_stats_t*)calloc(1, sizeof(summary_stats_t)); } - if (trigger_instance >= 0 && summary_stats[trigger_instance] == NULL) { - summary_stats[trigger_instance] = (summary_stats_t*)calloc(1, sizeof(summary_stats_t)); + if (trigger_instance >= 0 && summary_stats[NUM_EVENT_TYPES + trigger_instance] == NULL) { + summary_stats[NUM_EVENT_TYPES + trigger_instance] = (summary_stats_t*)calloc(1, sizeof(summary_stats_t)); } - summary_stats_t* stats; + summary_stats_t* stats = NULL; interval_t exec_time; reaction_stats_t* rstats; int index; + // Count of event type. + if (summary_stats[trace[i].event_type] == NULL) { + summary_stats[trace[i].event_type] = (summary_stats_t*)calloc(1, sizeof(summary_stats_t)); + } + summary_stats[trace[i].event_type]->event_type = trace[i].event_type; + summary_stats[trace[i].event_type]->description = trace_event_names[trace[i].event_type]; + summary_stats[trace[i].event_type]->occurrences++; + switch(trace[i].event_type) { case reaction_starts: case reaction_ends: // This code relies on the mutual exclusion of reactions in a reactor // and the ordering of reaction_starts and reaction_ends events. - if (trace[i].reaction_number >= MAX_NUM_REACTIONS) { + if (trace[i].dst_id >= MAX_NUM_REACTIONS) { fprintf(stderr, "WARNING: Too many reactions. Not all will be shown in summary file.\n"); continue; } - stats = summary_stats[object_instance]; + stats = summary_stats[NUM_EVENT_TYPES + object_instance]; stats->description = reactor_name; - if (trace[i].reaction_number >= stats->num_reactions_seen) { - stats->num_reactions_seen = trace[i].reaction_number + 1; + if (trace[i].dst_id >= stats->num_reactions_seen) { + stats->num_reactions_seen = trace[i].dst_id + 1; } - rstats = &stats->reactions[trace[i].reaction_number]; + rstats = &stats->reactions[trace[i].dst_id]; if (trace[i].event_type == reaction_starts) { rstats->latest_start_time = trace[i].physical_time; } else { @@ -172,19 +184,19 @@ size_t read_and_write_trace() { // No trigger. Do not report. continue; } - stats = summary_stats[trigger_instance]; + stats = summary_stats[NUM_EVENT_TYPES + trigger_instance]; stats->description = trigger_name; break; case user_event: // Although these are not exec times and not reactions, // commandeer the first entry in the reactions array to track values. - stats = summary_stats[object_instance]; + stats = summary_stats[NUM_EVENT_TYPES + object_instance]; stats->description = reactor_name; break; case user_value: // Although these are not exec times and not reactions, // commandeer the first entry in the reactions array to track values. - stats = summary_stats[object_instance]; + stats = summary_stats[NUM_EVENT_TYPES + object_instance]; stats->description = reactor_name; rstats = &stats->reactions[0]; rstats->occurrences++; @@ -205,7 +217,7 @@ size_t read_and_write_trace() { // Use the reactions array to store data. // There will be two entries per worker, one for waits on the // reaction queue and one for waits while advancing time. - index = trace[i].worker * 2; + index = trace[i].src_id * 2; // Even numbered indices are used for waits on reaction queue. // Odd numbered indices for waits for time advancement. if (trace[i].event_type == scheduler_advancing_time_starts @@ -216,10 +228,10 @@ size_t read_and_write_trace() { fprintf(stderr, "WARNING: Too many workers. Not all will be shown in summary file.\n"); continue; } - stats = summary_stats[object_table_size + index]; + stats = summary_stats[NUM_EVENT_TYPES + object_table_size + index]; if (stats == NULL) { stats = (summary_stats_t*)calloc(1, sizeof(summary_stats_t)); - summary_stats[object_table_size + index] = stats; + summary_stats[NUM_EVENT_TYPES + object_table_size + index] = stats; } // num_reactions_seen here will be used to store the number of // entries in the reactions array, which is twice the number of workers. @@ -244,10 +256,15 @@ size_t read_and_write_trace() { } } break; + default: + // No special summary statistics for the rest. + break; } // Common stats across event types. - stats->occurrences++; - stats->event_type = trace[i].event_type; + if (stats != NULL) { + stats->occurrences++; + stats->event_type = trace[i].event_type; + } } return trace_length; } @@ -261,11 +278,22 @@ void write_summary_file() { fprintf(summary_file, "End time:, %lld\n", latest_time); fprintf(summary_file, "Total time:, %lld\n", latest_time - start_time); + fprintf(summary_file, "\nTotal Event Occurrences\n"); + for (int i = 0; i < NUM_EVENT_TYPES; i++) { + summary_stats_t* stats = summary_stats[i]; + if (stats != NULL) { + fprintf(summary_file, "%s, %d\n", + stats->description, + stats->occurrences + ); + } + } + // First pass looks for reaction invocations. // First print a header. fprintf(summary_file, "\nReaction Executions\n"); fprintf(summary_file, "Reactor, Reaction, Occurrences, Total Time, Pct Total Time, Avg Time, Max Time, Min Time\n"); - for (int i = 0; i < table_size; i++) { + for (int i = NUM_EVENT_TYPES; i < table_size; i++) { summary_stats_t* stats = summary_stats[i]; if (stats != NULL && stats->num_reactions_seen > 0) { for (int j = 0; j < stats->num_reactions_seen; j++) { @@ -288,7 +316,7 @@ void write_summary_file() { // Next pass looks for calls to schedule. bool first = true; - for (int i = 0; i < table_size; i++) { + for (int i = NUM_EVENT_TYPES; i < table_size; i++) { summary_stats_t* stats = summary_stats[i]; if (stats != NULL && stats->event_type == schedule_called && stats->occurrences > 0) { if (first) { @@ -302,7 +330,7 @@ void write_summary_file() { // Next pass looks for user-defined events. first = true; - for (int i = 0; i < table_size; i++) { + for (int i = NUM_EVENT_TYPES; i < table_size; i++) { summary_stats_t* stats = summary_stats[i]; if (stats != NULL && (stats->event_type == user_event || stats->event_type == user_value) @@ -329,7 +357,7 @@ void write_summary_file() { // Next pass looks for wait events. first = true; - for (int i = 0; i < table_size; i++) { + for (int i = NUM_EVENT_TYPES; i < table_size; i++) { summary_stats_t* stats = summary_stats[i]; if (stats != NULL && ( stats->event_type == worker_wait_ends @@ -369,15 +397,34 @@ int main(int argc, char* argv[]) { usage(); exit(0); } - open_files(argv[1], "csv"); + // Open the trace file. + trace_file = open_file(argv[1], "r"); + if (trace_file == NULL) exit(1); + + // Construct the name of the csv output file and open it. + char* root = root_name(argv[1]); + char csv_filename[strlen(root) + 5]; + strcpy(csv_filename, root); + strcat(csv_filename, ".csv"); + output_file = open_file(csv_filename, "w"); + if (output_file == NULL) exit(1); + + // Construct the name of the summary output file and open it. + char summary_filename[strlen(root) + 13]; + strcpy(summary_filename, root); + strcat(summary_filename, "_summary.csv"); + summary_file = open_file(summary_filename, "w"); + if (summary_file == NULL) exit(1); + + free(root); if (read_header() >= 0) { // Allocate an array for summary statistics. - table_size = object_table_size + (MAX_NUM_WORKERS * 2); + table_size = NUM_EVENT_TYPES + object_table_size + (MAX_NUM_WORKERS * 2); summary_stats = (summary_stats_t**)calloc(table_size, sizeof(summary_stats_t*)); // Write a header line into the CSV file. - fprintf(output_file, "Event, Reactor, Reaction, Worker, Elapsed Logical Time, Microstep, Elapsed Physical Time, Trigger, Extra Delay\n"); + fprintf(output_file, "Event, Reactor, Source, Destination, Elapsed Logical Time, Microstep, Elapsed Physical Time, Trigger, Extra Delay\n"); while (read_and_write_trace() != 0) {}; write_summary_file(); diff --git a/util/tracing/trace_to_influxdb.c b/util/tracing/trace_to_influxdb.c index d8281abd01..a99ae003ec 100644 --- a/util/tracing/trace_to_influxdb.c +++ b/util/tracing/trace_to_influxdb.c @@ -117,6 +117,9 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MAX_NUM_REACTIONS 64 // Maximum number of reactions reported in summary stats. #define MAX_NUM_WORKERS 64 +/** File containing the trace binary data. */ +FILE* trace_file = NULL; + /** Struct identifying the influx client. */ influx_client_t influx_client; influx_v2_client_t influx_v2_client; @@ -151,10 +154,14 @@ size_t read_and_write_trace() { if (trace_length == 0) return 0; // Write each line. for (int i = 0; i < trace_length; i++) { + + // Ignore federated traces. + if (trace[i].event_type > federated) continue; + char* reaction_name = "none"; - if (trace[i].reaction_number >= 0) { + if (trace[i].dst_id >= 0) { reaction_name = (char*)malloc(4); - snprintf(reaction_name, 4, "%d", trace[i].reaction_number); + snprintf(reaction_name, 4, "%d", trace[i].dst_id); } // printf("DEBUG: reactor self struct pointer: %p\n", trace[i].pointer); int object_instance = -1; @@ -176,7 +183,7 @@ size_t read_and_write_trace() { INFLUX_MEAS(trace_event_names[trace[i].event_type]), INFLUX_TAG("Reactor", reactor_name), INFLUX_TAG("Reaction", reaction_name), - INFLUX_F_INT("Worker", trace[i].worker), + INFLUX_F_INT("Worker", trace[i].src_id), INFLUX_F_INT("Logical Time", trace[i].logical_time), INFLUX_F_INT("Microstep", trace[i].microstep), INFLUX_F_STR("Trigger Name", trigger_name), @@ -259,7 +266,8 @@ int main(int argc, char* argv[]) { exit(1); } - open_files(filename, NULL); + // Open the trace file. + trace_file = open_file(filename, "r"); if (read_header() >= 0) { size_t num_records = 0, result; diff --git a/util/tracing/trace_util.c b/util/tracing/trace_util.c index f688b560d4..0f97b3bdb9 100644 --- a/util/tracing/trace_util.c +++ b/util/tracing/trace_util.c @@ -37,15 +37,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** Buffer for reading object descriptions. Size limit is BUFFER_SIZE bytes. */ char buffer[BUFFER_SIZE]; -/** File containing the trace binary data. */ -FILE* trace_file = NULL; - -/** File for writing the output data. */ -FILE* output_file = NULL; - -/** File for writing summary statistics. */ -FILE* summary_file = NULL; - /** Buffer for reading trace records. */ trace_record_t trace[TRACE_BUFFER_CAPACITY]; @@ -60,6 +51,13 @@ char* top_level = NULL; object_description_t* object_table; int object_table_size = 0; +typedef struct open_file_t open_file_t; +typedef struct open_file_t { + FILE* file; + open_file_t* next; +} open_file_t; +open_file_t* _open_files = NULL; + /** * Function to be invoked upon exiting. */ @@ -68,82 +66,57 @@ void termination() { for (int i = 0; i < object_table_size; i++) { free(object_table[i].description); } - if (trace_file != NULL) { - fclose(trace_file); - } - if (output_file != NULL) { - fclose(output_file); - } - if (summary_file != NULL) { - fclose(summary_file); + while (_open_files != NULL) { + fclose(_open_files->file); + open_file_t* tmp = _open_files->next; + free(_open_files); + _open_files = tmp; } printf("Done!\n"); } -/** - * Open the trace file and the output file using the given filename. - * This leaves the FILE* pointers in the global variables trace_file and output_file. - * If the extension if "csv", then it also opens a summary_file. - * The filename argument can include path information. - * It can include the ".lft" extension or not. - * The output file will have the same path and name except that the - * extension will be given by the second argument. - * The summary_file, if opened, will have the filename with "_summary.csv" appended. - * @param filename The file name. - * @param output_file_extension The extension to put on the output file name (e.g. "csv"). - * @return A pointer to the file. - */ -void open_files(char* filename, char* output_file_extension) { - // Open the input file for reading. - size_t length = strlen(filename); - if (length > 4 && strcmp(&filename[length - 4], ".lft") == 0) { - // The filename includes the .lft extension. - length -= 4; - } - char trace_file_name[length + 4]; - strncpy(trace_file_name, filename, length); - trace_file_name[length] = 0; - strcat(trace_file_name, ".lft"); - trace_file = fopen(trace_file_name, "r"); - if (trace_file == NULL) { - fprintf(stderr, "No trace file named %s.\n", trace_file_name); +const char PATH_SEPARATOR = +#ifdef _WIN32 + '\\'; +#else + '/'; +#endif + +char* root_name(const char* path) { + if (path == NULL) return NULL; + + // Remove any path. + char* last_separator = strrchr(path, PATH_SEPARATOR); + if (last_separator != NULL) path = last_separator + 1; + + // Allocate and copy name without extension. + char* last_period = strrchr(path, '.'); + size_t length = (last_period == NULL) ? + strlen(path) : last_period - path; + char* result = (char*)malloc(length + 1); + if (result == NULL) return NULL; + strncpy(result, path, length); + result[length] = '\0'; + + return result; +} + +FILE* open_file(const char* path, const char* mode) { + FILE* result = fopen(path, mode); + if (result == NULL) { + fprintf(stderr, "No file named %s.\n", path); usage(); exit(2); } - - // Open the output file for writing. - if (output_file_extension) { - char output_file_name[length + strlen(output_file_extension) + 1]; - strncpy(output_file_name, filename, length); - output_file_name[length] = 0; - strcat(output_file_name, "."); - strcat(output_file_name, output_file_extension); - output_file = fopen(output_file_name, "w"); - if (output_file == NULL) { - fprintf(stderr, "Could not create output file named %s.\n", output_file_name); - usage(); - exit(2); - } - - if (strcmp("csv", output_file_extension) == 0) { - // Also open a summary_file. - char *suffix = "_summary.csv"; - char summary_file_name[length + strlen(suffix) + 1]; - strncpy(summary_file_name, filename, length); - summary_file_name[length] = 0; - strcat(summary_file_name, suffix); - summary_file = fopen(summary_file_name, "w"); - if (summary_file == NULL) { - fprintf(stderr, "Could not create summary file named %s.\n", summary_file_name); - usage(); - exit(2); - } - } - } - - if (atexit(termination) != 0) { - fprintf(stderr, "WARNING: Failed to register termination function!"); + open_file_t* record = (open_file_t*)malloc(sizeof(open_file_t)); + if (record == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(3); } + record->file = result; + record->next = _open_files; + _open_files = record; + return result; } /** diff --git a/util/tracing/trace_util.h b/util/tracing/trace_util.h index 56eb9fe3e8..dab1f5e989 100644 --- a/util/tracing/trace_util.h +++ b/util/tracing/trace_util.h @@ -36,10 +36,10 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** Macro to use when access to trace file fails. */ #define _LF_TRACE_FAILURE(trace_file) \ do { \ - fprintf(stderr, "WARNING: Access to trace file failed.\n"); \ + fprintf(stderr, "ERROR: Access to trace file failed.\n"); \ fclose(trace_file); \ trace_file = NULL; \ - return -1; \ + exit(1); \ } while(0) /** Buffer for reading object descriptions. Size limit is BUFFER_SIZE bytes. */ @@ -73,18 +73,23 @@ extern int object_table_size; extern char* top_level; /** - * Open the trace file and the output file using the given filename. - * This leaves the FILE* pointers in the global variables trace_file and output_file. - * If the extension if "csv", then it also opens a summary_file. - * The filename argument can include path information. - * It can include the ".lft" extension or not. - * The output file will have the same path and name except that the - * extension will be given by the second argument. - * The summary_file, if opened, will have the filename with "_summary.csv" appended. - * @param filename The file name. - * @param output_file_extension The extension to put on the output file name (e.g. "csv"). + * @brief Return the root file name from the given path. + * Given a path to a file, this function returns a dynamically + * allocated string (which you must free) that points to the root + * filename without the preceding path and without the file extension. + * @param path The path including the full filename. + * @return The root name of the file or NULL for failure. */ -void open_files(char* filename, char* output_file_extension); +char* root_name(const char* path); + +/** + * @brief Open the specified file for reading or writing. + * This function records the file for closing at termination. + * @param path The path to the file. + * @param mode "r" for reading and "w" for writing. + * @return A pointer to the open file or NULL for failure. + */ +FILE* open_file(const char* path, const char* mode); /** * Get the description of the object pointed to by the specified pointer. diff --git a/util/tracing/visualization/.gitignore b/util/tracing/visualization/.gitignore new file mode 100644 index 0000000000..ba0430d26c --- /dev/null +++ b/util/tracing/visualization/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/util/tracing/visualization/README.md b/util/tracing/visualization/README.md new file mode 100644 index 0000000000..82861e9b2a --- /dev/null +++ b/util/tracing/visualization/README.md @@ -0,0 +1,25 @@ +# Trace sequence diagram visualiser + +This is a 1st iteration of a prototyping tool for constructing a sequence diagram +out of the traces. +It operates over the csv files generated by `trace_to_csv`. + +# Running + +Once the `.lft` files collected and tranformed into `csv` files, run: +``` +$ python3 sd_gen.py -r -f ... +``` + +The output is an html file with the svg in it. + +# Current problems + +- The collected traces are not complete. They need to be checked for correcteness as well. +- All arrows are horizontal and can be duplicated. Need further processing to derive the connections +for that. +- The scale needs exploration + + + + diff --git a/util/tracing/visualization/fedsd.py b/util/tracing/visualization/fedsd.py new file mode 100644 index 0000000000..3eb13d11bc --- /dev/null +++ b/util/tracing/visualization/fedsd.py @@ -0,0 +1,314 @@ +''' +Define arrows: + (x1, y1) ==> (x2, y2), when unique result (this arrow will be tilted) + (x1, y1) --> (x2, y2), when a possible result (could be not tilted)? +If not arrow, then triangle with text + +In the dataframe, each row will be marked with one op these values: + - 'arrow': draw a non-dashed arrow + - 'dot': draw a dot only + - 'marked': marked, not to be drawn + - 'pending': pending + - 'adv': for reporting logical time advancing, draw a simple dash +''' + +# Styles to determine appearance: +css_style = ' \ +' + +#!/usr/bin/env python3 +import argparse # For arguments parsing +import pandas as pd # For csv manipulation +from os.path import exists +from pathlib import Path +import math +import fedsd_helper as fhlp + +# Define the arguments to pass in the command line +parser = argparse.ArgumentParser(description='Set of the csv trace files to render.') +parser.add_argument('-r','--rti', type=str, default="rti.csv", + help='RTI csv trace file.') +parser.add_argument('-f','--federates', nargs='+', action='append', + help='List of the federates csv trace files.') + + +''' Clock synchronization error ''' +''' FIXME: There should be a value for each communicating pair ''' +clock_sync_error = 0 + +''' Bound on the network latency ''' +''' FIXME: There should be a value for each communicating pair ''' +network_latency = 100000000 # That is 100us + + +def load_and_process_csv_file(csv_file) : + ''' + Loads and processes the csv entries, based on the type of the actor (if RTI + or federate). + + Args: + * csv_file: String file name + Returns: + * The processed dataframe. + ''' + # Load tracepoints, rename the columns and clean non useful data + df = pd.read_csv(csv_file) + df.columns = ['event', 'reactor', 'self_id', 'partner_id', 'logical_time', 'microstep', 'physical_time', 't', 'ed'] + df = df.drop(columns=['reactor', 't', 'ed']) + + # Remove all the lines that do not contain communication information + # which boils up to having 'RTI' in the 'event' column + df = df[df['event'].str.contains('Sending|Receiving|Scheduler advancing time ends') == True] + df = df.astype({'self_id': 'int', 'partner_id': 'int'}) + + # Add an inout column to set the arrow direction + df['inout'] = df['event'].apply(lambda e: 'in' if 'Receiving' in e else 'out') + + # Prune event names + df['event'] = df['event'].apply(lambda e: fhlp.prune_event_name[e]) + return df + + +if __name__ == '__main__': + args = parser.parse_args() + + # Check if the RTI trace file exists + if (not exists(args.rti)): + print('Error: No RTI csv trace file! Specify with -r argument.') + exit(1) + + # The RTI and each of the federates have a fixed x coordinate. They will be + # saved in a dict + x_coor = {} + actors = [] + actors_names = {} + padding = 50 + spacing = 200 # Spacing between federates + + ############################################################################ + #### RTI trace processing + ############################################################################ + trace_df = load_and_process_csv_file(args.rti) + x_coor[-1] = padding * 2 + actors.append(-1) + actors_names[-1] = "RTI" + # Temporary use + trace_df['x1'] = x_coor[-1] + + ############################################################################ + #### Federates trace processing + ############################################################################ + # Loop over the given list of federates trace files + if (args.federates) : + for fed_trace in args.federates[0]: + if (not exists(fed_trace)): + print('Warning: Trace file ' + fed_trace + ' does not exist! Will resume though') + continue + fed_df = load_and_process_csv_file(fed_trace) + if (not fed_df.empty): + # Get the federate id number + fed_id = fed_df.iloc[-1]['self_id'] + # Add to the list of sequence diagram actors and add the name + actors.append(fed_id) + actors_names[fed_id] = Path(fed_trace).stem + # Derive the x coordinate of the actor + x_coor[fed_id] = (padding * 2) + (spacing * (len(actors)-1)) + fed_df['x1'] = x_coor[fed_id] + # Append into trace_df + trace_df = trace_df.append(fed_df, sort=False, ignore_index=True) + fed_df = fed_df[0:0] + + # Sort all traces by physical time and then reset the index + trace_df = trace_df.sort_values(by=['physical_time']) + trace_df = trace_df.reset_index(drop=True) + + # FIXME: For now, we need to remove the rows with negative physical time values... + # Until the reason behinf such values is investigated. The negative physical + # time is when federates are still in the process of joining + # trace_df = trace_df[trace_df['physical_time'] >= 0] + + # Add the Y column and initialize it with the padding value + trace_df['y1'] = math.ceil(padding * 3 / 2) # Or set a small shift + + ############################################################################ + #### Compute the 'y1' coordinates + ############################################################################ + ppt = 0 # Previous physical time + cpt = 0 # Current physical time + py = 0 # Previous y + min = 15 # Minimum spacing between events when time has not advanced. + scale = 1 # Will probably be set manually + first_pass = True + for index, row in trace_df.iterrows(): + if (not first_pass) : + cpt = row['physical_time'] + # print('cpt = '+str(cpt)+' and ppt = '+str(ppt)) + # From the email: + # Y = T_previous + min + log10(1 + (T - T_previous)*scale) + # But rather think it should be: + if (cpt != ppt) : + py = math.ceil(py + min + (1 + math.log10(cpt - ppt) * scale)) + trace_df.at[index, 'y1'] = py + + ppt = row['physical_time'] + py = trace_df.at[index, 'y1'] + first_pass = False + + ############################################################################ + #### Derive arrows that match sided communications + ############################################################################ + # Intialize all rows as pending to be matched + trace_df['arrow'] = 'pending' + trace_df['x2'] = -1 + trace_df['y2'] = -1 + + # Iterate and check possible sides + for index in trace_df.index: + # If the tracepoint is pending, proceed to look for a match + if (trace_df.at[index,'arrow'] == 'pending') : + # Look for a match only if it is not about advancing time + if (trace_df.at[index,'event'] == 'AdvLT') : + trace_df.at[index,'arrow'] = 'adv' + continue + self_id = trace_df.at[index,'self_id'] + partner_id = trace_df.at[index,'partner_id'] + event = trace_df.at[index,'event'] + logical_time = trace_df.at[index, 'logical_time'] + microstep = trace_df.at[index, 'microstep'] + inout = trace_df.at[index, 'inout'] + + # Match tracepoints + matching_df = trace_df[\ + (trace_df['inout'] != inout) & \ + (trace_df['self_id'] == partner_id) & \ + (trace_df['partner_id'] == self_id) & \ + (trace_df['arrow'] == 'pending') & \ + (trace_df['event'] == event) & \ + (trace_df['logical_time'] == logical_time) & \ + (trace_df['microstep'] == microstep) \ + ] + + if (matching_df.empty) : + # If no matching receiver, than set the arrow to 'dot', + # meaning that only a dot will be rendered + trace_df.loc[index, 'arrow'] = 'dot' + else: + # If there is one or more matching rows, then consider + # the first one, since it is an out -> in arrow, and + # since it is the closet in time + # FIXME: What other possible choices to consider? + if (inout == 'out'): + matching_index = matching_df.index[0] + matching_row = matching_df.loc[matching_index] + trace_df.at[index, 'x2'] = matching_row['x1'] + trace_df.at[index, 'y2'] = matching_row['y1'] + else: + matching_index = matching_df.index[-1] + matching_row = matching_df.loc[matching_index] + trace_df.at[index, 'x2'] = trace_df.at[index, 'x1'] + trace_df.at[index, 'y2'] = trace_df.at[index, 'y1'] + trace_df.at[index, 'x1'] = matching_row['x1'] + trace_df.at[index, 'y1'] = matching_row['y1'] + + # Mark it, so not to consider it anymore + trace_df.at[matching_index, 'arrow'] = 'marked' + + trace_df.at[index, 'arrow'] = 'arrow' + + ############################################################################ + #### Write to svg file + ############################################################################ + svg_width = padding * 2 + (len(actors) - 1) * spacing + padding * 2 + 200 + svg_height = padding + trace_df.iloc[-1]['y1'] + + with open('trace_svg.html', 'w', encoding='utf-8') as f: + # Print header + f.write('\n') + f.write('\n') + f.write('\n\n') + + f.write('\n') + + f.write(css_style) + + # Print the circles and the names + for key in x_coor: + title = actors_names[key] + if (key == -1): + f.write(fhlp.svg_string_comment('RTI Actor and line')) + center = 15 + else: + f.write(fhlp.svg_string_comment('Federate '+str(key)+': ' + title + ' Actor and line')) + center = 5 + f.write(fhlp.svg_string_draw_line(x_coor[key], math.ceil(padding/2), x_coor[key], svg_height, False)) + f.write('\t\n') + f.write('\t'+title+'\n') + + # Now, we need to iterate over the traces to draw the lines + f.write(fhlp.svg_string_comment('Draw interactions')) + for index, row in trace_df.iterrows(): + # For time labels, display them on the left for the RTI, right for everthing else. + anchor = 'start' + if (row['self_id'] < 0): + anchor = 'end' + + # formatted physical time. + # FIXME: Using microseconds is hardwired here. + physical_time = f'{int(row["physical_time"]/1000):,}' + + if (row['event'] in {'FED_ID', 'ACK', 'REJECT', 'ADR_RQ', 'ADR_AD', 'MSG', 'P2P_MSG'}): + label = row['event'] + elif (row['logical_time'] == -1678240241788173894) : + # FIXME: This isn't right. NEVER == -9223372036854775808. + label = row['event'] + '(NEVER)' + else: + label = row['event'] + '(' + f'{int(row["logical_time"]):,}' + ', ' + str(row['microstep']) + ')' + + if (row['arrow'] == 'arrow'): + f.write(fhlp.svg_string_draw_arrow(row['x1'], row['y1'], row['x2'], row['y2'], label, row['event'])) + f.write(fhlp.svg_string_draw_side_label(row['x1'], row['y1'], physical_time, anchor)) + elif (row['arrow'] == 'dot'): + if (row['inout'] == 'in'): + label = "(in) from " + str(row['partner_id']) + ' ' + label + else : + label = "(out) to " + str(row['partner_id']) + ' ' + label + + if (anchor == 'end'): + f.write(fhlp.svg_string_draw_side_label(row['x1'], row['y1'], physical_time, anchor)) + f.write(fhlp.svg_string_draw_dot(row['x1'], row['y1'], label)) + else: + f.write(fhlp.svg_string_draw_dot_with_time(row['x1'], row['y1'], physical_time, label)) + + elif (row['arrow'] == 'marked'): + f.write(fhlp.svg_string_draw_side_label(row['x1'], row['y1'], physical_time, anchor)) + + elif (row['arrow'] == 'adv'): + f.write(fhlp.svg_string_draw_adv(row['x1'], row['y1'], label)) + + f.write('\n\n\n') + + # Print footer + f.write('\n') + f.write('\n') + + # Write to a csv file, just to double check + trace_df.to_csv('all.csv', index=True) \ No newline at end of file diff --git a/util/tracing/visualization/fedsd_helper.py b/util/tracing/visualization/fedsd_helper.py new file mode 100644 index 0000000000..804341f117 --- /dev/null +++ b/util/tracing/visualization/fedsd_helper.py @@ -0,0 +1,241 @@ +import math + +# Disctionary for pruning event names. Usefule for tracepoint matching and +# communication rendering +prune_event_name = { + "Sending ACK": "ACK", + "Sending TIMESTAMP": "TIMESTAMP", + "Sending NET": "NET", + "Sending LTC": "LTC", + "Sending STOP_REQ": "STOP_REQ", + "Sending STOP_REQ_REP": "STOP_REQ_REP", + "Sending STOP_GRN": "STOP_GRN", + "Sending FED_ID": "FED_ID", + "Sending PTAG": "PTAG", + "Sending TAG": "TAG", + "Sending REJECT": "REJECT", + "Sending RESIGN": "RESIGN", + "Sending PORT_ABS": "ABS", + "Sending CLOSE_RQ": "CLOSE_RQ", + "Sending TAGGED_MSG": "T_MSG", + "Sending P2P_TAGGED_MSG": "P2P_T_MSG", + "Sending MSG": "MSG", + "Sending P2P_MSG": "P2P_MSG", + "Sending ADR_AD": "ADR_AD", + "Sending ADR_QR": "ADR_QR", + "Receiving ACK": "ACK", + "Receiving TIMESTAMP": "TIMESTAMP", + "Receiving NET": "NET", + "Receiving LTC": "LTC", + "Receiving STOP_REQ": "STOP_REQ", + "Receiving STOP_REQ_REP": "STOP_REQ_REP", + "Receiving STOP_GRN": "STOP_GRN", + "Receiving FED_ID": "FED_ID", + "Receiving PTAG": "PTAG", + "Receiving TAG": "TAG", + "Receiving REJECT": "REJECT", + "Receiving RESIGN": "RESIGN", + "Receiving PORT_ABS": "ABS", + "Receiving CLOSE_RQ": "CLOSE_RQ", + "Receiving TAGGED_MSG": "T_MSG", + "Receiving P2P_TAGGED_MSG": "P2P_T_MSG", + "Receiving MSG": "MSG", + "Receiving P2P_MSG": "P2P_MSG", + "Receiving ADR_AD": "ADR_AD", + "Receiving ADR_QR": "ADR_QR", + "Receiving UNIDENTIFIED": "UNIDENTIFIED", + "Scheduler advancing time ends": "AdvLT" +} + +prune_event_name.setdefault(" ", "UNIDENTIFIED") + +################################################################################ +### Routines to write to csv file +################################################################################ + +def svg_string_draw_line(x1, y1, x2, y2, type=''): + ''' + Constructs the svg html string to draw a line from (x1, y1) to (x2, y2). + + Args: + * x1: Int X coordinate of the source point + * y1: Int Y coordinate of the source point + * x2: Int X coordinate of the sink point + * y2: Int Y coordinate of the sink point + * type: The type of the message (for styling) + Returns: + * String: the svg string of the line© + ''' + str_line = '\t\n' + return str_line + + +def svg_string_draw_arrow_head(x1, y1, x2, y2, type='') : + ''' + Constructs the svg html string to draw the arrow end + + Args: + * x1: Int X coordinate of the source point + * y1: Int Y coordinate of the source point + * x2: Int X coordinate of the sink point + * y2: Int Y coordinate of the sink point + * type: The type (for styling) + Returns: + * String: the svg string of the triangle + ''' + + rotation = - math.ceil(math.atan((x2-x1)/(y2-y1)) * 180 / 3.14) - 90 + style = '' + if (type): + style = ' class="'+type+'"' + + str_line = '' + if (x1 > x2) : + str_line = '\t\n' + else : + str_line = '\t\n' + + return str_line + + +def svg_string_draw_label(x1, y1, x2, y2, label) : + ''' + Computes the rotation angle of the text and then constructs the svg string. + + Args: + * x1: Int X coordinate of the source point + * y1: Int Y coordinate of the source point + * x2: Int X coordinate of the sink point + * y2: Int Y coordinate of the sink point + * label: The label to draw + Returns: + * String: the svg string of the text + ''' + # FIXME: Need further improvement, based of the position of the arrows + # FIXME: Rotation value is not that accurate. + if (x2 < x1) : + # Left-going arrow. + rotation = - math.ceil(math.atan((x2-x1)/(y2-y1)) * 180 / 3.14) - 90 + str_line = '\t'+label+'\n' + else : + # Right-going arrow. + rotation = - math.ceil(math.atan((x1-x2)/(y1-y2)) * 180 / 3.14) + 90 + str_line = '\t'+label+'\n' + #print('rot = '+str(rotation)+' x1='+str(x1)+' y1='+str(y1)+' x2='+str(x2)+' y2='+str(y2)) + return str_line + + +def svg_string_draw_arrow(x1, y1, x2, y2, label, type=''): + ''' + Constructs the svg html string to draw the arrow from (x1, y1) to (x2, y2). + The arrow end is constructed, together with the label + + Args: + * x1: Int X coordinate of the source point + * y1: Int Y coordinate of the source point + * x2: Int X coordinate of the sink point + * y2: Int Y coordinate of the sink point + * label: String Label to draw on top of the arrow + * type: The type of the message + Returns: + * String: the svg string of the arrow + ''' + str_line1 = svg_string_draw_line(x1, y1, x2, y2, type) + str_line2 = svg_string_draw_arrow_head(x1, y1, x2, y2, type) + str_line3 = svg_string_draw_label(x1, y1, x2, y2, label) + return str_line1 + str_line2 + str_line3 + +def svg_string_draw_side_label(x, y, label, anchor="start") : + ''' + Put a label to the right of the x, y point, + unless x is small, in which case put it to the left. + + Args: + * x: Int X coordinate of the source point + * y: Int Y coordinate of the source point + * label: Label to put by the point. + * anchor: One of "start", "middle", or "end" to specify the text-anchor. + Returns: + * String: the svg string of the text + ''' + offset = 5 + if (anchor == 'end'): + offset = -5 + elif (anchor == 'middle'): + offset = 0 + str_line = '\t'+label+'\n' + + return str_line + +def svg_string_comment(comment): + ''' + Constructs the svg html string to write a comment into an svg file. + + Args: + * comment: String Comment to add + Returns: + * String: the svg string of the comment + ''' + str_line = '\n\t\n' + return str_line + + +def svg_string_draw_dot(x, y, label) : + ''' + Constructs the svg html string to draw at a dot. + + Args: + * x: Int X coordinate of the dot + * y: Int Y coordinate of the dot + * label: String to draw + Returns: + * String: the svg string of the triangle + ''' + str_line = '' + str_line = '\t\n' + str_line = str_line + '\t'+label+'\n' + return str_line + +def svg_string_draw_dot_with_time(x, y, time, label) : + ''' + Constructs the svg html string to draw at a dot with a prefixed physical time. + + Args: + * x: Int X coordinate of the dot + * y: Int Y coordinate of the dot + * time: The time + * label: String to draw + Returns: + * String: the svg string of the triangle + ''' + str_line = '' + str_line = '\t\n' + str_line = str_line + '\t '+time+': '+label+'\n' + return str_line + +def svg_string_draw_adv(x, y, label) : + ''' + Constructs the svg html string to draw at a dash, meaning that logical time is advancing there. + + Args: + * x: Int X coordinate of the dash + * y: Int Y coordinate of the dash + * label: String to draw + Returns: + * String: the svg string of the triangle + ''' + str_line1 = svg_string_draw_line(x-5, y, x+5, y, "ADV") + str_line2 = svg_string_draw_side_label(x, y, label) + return str_line1 + str_line2 \ No newline at end of file From e8a1b0522b68c81d8d275390342799378010bc11 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 11:15:40 +0100 Subject: [PATCH 044/108] Deleted spurious empty files --- thread_active | 0 watchdog_function | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 thread_active delete mode 100644 watchdog_function diff --git a/thread_active b/thread_active deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/watchdog_function b/watchdog_function deleted file mode 100644 index e69de29bb2..0000000000 From 534a003d431eefef1151a31e0ecab9f79a40043e Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 11:37:16 +0100 Subject: [PATCH 045/108] Removed unused imports --- org.lflang/src/org/lflang/ASTUtils.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index fc49242322..19dcc68ca0 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -32,7 +32,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; @@ -60,8 +59,6 @@ import org.lflang.generator.InvalidSourceException; 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; From cb06dffb64a3115da6edadb8dfc515695a8e9b15 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 11:37:40 +0100 Subject: [PATCH 046/108] Group watchdog with action --- org.lflang/src/org/lflang/LinguaFranca.xtext | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 870b1ad049..71213d36c2 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -175,10 +175,10 @@ Mode: (stateVars+=StateVar) | (timers+=Timer) | (actions+=Action) | + (watchdogs+=Watchdog) | (instantiations+=Instantiation) | (connections+=Connection) | - (reactions+=Reaction) | - (watchdogs+=Watchdog) + (reactions+=Reaction) )* '}'; // Action that has either a physical or logical origin. From 302f690158d5aa85862650551bbbc8caf583735a Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 11:37:58 +0100 Subject: [PATCH 047/108] Removed unused imports --- .../org/lflang/federated/generator/FederateInstance.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index f3768cec7c..8ed3b63d70 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -25,7 +25,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.federated.generator; -import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -40,17 +39,13 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.ASTUtils; import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TimeValue; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.ActionInstance; -import org.lflang.generator.GeneratorUtils; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; -import org.lflang.generator.SubContext; import org.lflang.generator.TriggerInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; @@ -70,7 +65,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; -import org.lflang.lf.Watchdog; import com.google.common.base.Objects; From 7b05cd6d7edbf01179e175276187fbef5ecc9eac Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 11:38:29 +0100 Subject: [PATCH 048/108] Removed non-substantive change --- org.lflang/src/org/lflang/generator/ReactionInstance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 3ce7f6ceaa..cf9dce7013 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -221,7 +221,7 @@ public ReactionInstance( * Deadline for this reaction instance, if declared. */ public DeadlineInstance declaredDeadline; - + /** * Sadly, we have no way to mark reaction "unordered" in the AST, * so instead, we use a magic comment at the start of the reaction body. From 673a7fcd58329d2a013ffc748638c6e878ff5eb3 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 11:39:41 +0100 Subject: [PATCH 049/108] Removed unused or redundant imports --- org.lflang/src/org/lflang/generator/ReactorInstance.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index cc049f971c..395bb90d6d 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -35,8 +35,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Map; import java.util.Set; -import org.eclipse.emf.ecore.util.EcoreUtil; - import org.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; @@ -49,7 +47,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Expression; 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; @@ -63,8 +60,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Variable; import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; -import org.lflang.lf.Watchdog; - /** * Representation of a compile-time instance of a reactor. From 91086adfabc0612caa763b7bd4cb034b0456e918 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 15:26:45 +0100 Subject: [PATCH 050/108] Made this a real test --- test/C/src/Watchdog.lf | 65 +++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index e3d7f6cb41..b44a16f22c 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -1,33 +1,64 @@ -target C +/** + * 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: 1100ms +} -reactor Watcher { - input x: int - output d: int // Produced if the deadline is violated. +reactor Watcher(timeout:time(150ms)) { + timer t(100ms, 100ms) // Offset ameliorates startup time. + output d: int // Produced if the watchdog triggers. + state alternating:bool(false) + state count:int(0) - watchdog poodle(10 msec) {= + watchdog poodle(timeout) {= instant_t p = lf_time_physical_elapsed() - lf_time_logical_elapsed(); - printf("Deadline missed! Lag: %lld (too late by %lld nsecs)\n", p, p-500000); + lf_print("Watchdog timed out! Lag: %lld (too late by " PRINTF_TIME " ns)", p, p - self->timeout); + self->count++; =} - reaction(x) -> poodle, d {= + reaction(t) -> poodle, d {= lf_watchdog_start(poodle, 0); - printf("Normal reaction.\n"); + 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_set(d, 1); + =} + + reaction(shutdown) -> poodle {= + // FIXME: There needs to be an lf_watchdog_stop() defined. + _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(startup) -> w.x, a {= - lf_set(w.x, 0); - lf_schedule(a, 0); + reaction(w.d) {= + lf_print("*** Watcher reactor produced an output."); + self->count++; =} - - reaction(a) -> w.x {= - lf_set(w.x, 0); - lf_nanosleep(MSEC(90)); + reaction(shutdown) {= + if (self->count != 4) { + lf_print_error_and_exit("Watchdog produced output %d times. Expected 4.", self->count); + } =} - - reaction(w.d) {= printf("Deadline reactor produced an output.\n"); =} } From c20e711b19a439deee5d88daf46283a555c920ab Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 15:31:04 +0100 Subject: [PATCH 051/108] Made fields private and rearranged --- .../lflang/generator/WatchdogInstance.java | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index 2a2e11a3ad..001246cf2f 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -13,16 +13,15 @@ public class WatchdogInstance { /** - * Create a new watchdog instance associated with the given reaction + * Create a new watchdog instance associated with the given reactor * instance. */ public WatchdogInstance(Watchdog definition, ReactorInstance reactor) { if (definition.getTimeout() != null) { - // WATCHDOG QUESTION - // How does this .getTimeValue work? Where is expression coming from - // versus other time parameters? + // 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; } @@ -31,6 +30,9 @@ public WatchdogInstance(Watchdog definition, ReactorInstance reactor) { this.reactor = reactor; } + ////////////////////////////////////////////////////// + //// Public methods. + public String getName() { return this.name; } @@ -47,27 +49,19 @@ public ReactorInstance getReactor() { return this.reactor; } - ////////////////////////////////////////////////////// - //// Public fields. - + @Override + public String toString() { + return "WatchdogInstance " + name + "(" + timeout.toString() + ")"; + } - public final TimeValue timeout; + ////////////////////////////////////////////////////// + //// Private fields. - /** - * The watchdog name. - */ - public final String name; + private final TimeValue timeout; - public final Watchdog definition; + private final String name; - public final ReactorInstance reactor; + private final Watchdog definition; - ////////////////////////////////////////////////////// - //// Public methods. - - //FIXME: unsure of use or need for watchdogs - @Override - public String toString() { - return "WatchdogInstance " + timeout.toString(); - } + private final ReactorInstance reactor; } From bb2a21d38e9abfc906d25b00ed64223af2eefdbc Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 15:32:13 +0100 Subject: [PATCH 052/108] Fixed hasWatchdogs, which was always returning true --- .../src/org/lflang/generator/c/CGenerator.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index e7ce2439f8..fbaec56d97 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -36,9 +36,7 @@ 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 java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -81,7 +79,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.ReactorInstance; import org.lflang.generator.WatchdogInstance; -import org.lflang.generator.SubContext; import org.lflang.generator.TargetTypes; import org.lflang.generator.TimerInstance; @@ -839,7 +836,8 @@ private boolean hasDeadlines(List reactors) { private boolean hasWatchdogs() { for (Reactor reactor : reactors) { - if (ASTUtils.allWatchdogs(reactor) != null) { + List watchdogs = ASTUtils.allWatchdogs(reactor); + if (watchdogs != null && !watchdogs.isEmpty()) { return true; } } @@ -1526,9 +1524,8 @@ private void recordWatchdogs(ReactorInstance instance) { var reactorRef = CUtil.reactorRef(instance); // temp.pr("#ifdef LF_THREADED"); for (WatchdogInstance watchdog : instance.watchdogs) { - temp.pr(" _lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); - temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); - temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); + temp.pr("_lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); + temp.pr(reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); watchdogCount += 1; foundOne = true; } @@ -1903,8 +1900,6 @@ protected void generateStateVariableInitializations(ReactorInstance instance) { * specified reactor instance. * @param instance The reactor instance. */ - // WATCHDOG QUESTION: Why do we wait to set the deadline instead of defining - // it in the constructor of the reactor? private void generateSetDeadline(ReactorInstance instance) { for (ReactionInstance reaction : instance.reactions) { var selfRef = CUtil.reactorRef(reaction.getParent())+"->_lf__reaction_"+reaction.index; From 6d33efc3a61a51e66707749b7e674039a413c2aa Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 15:32:39 +0100 Subject: [PATCH 053/108] Regularized function pointer --- org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 344209c3b8..4193a7c522 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -150,7 +150,7 @@ public static void generateWatchdogStruct( "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+".watchdog_function = "+watchdogFunctionName+";" )); // constructorCode.pr("#endif"); // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? From 5e31d322765f6bc1706b12b6b016028c4eca6ff1 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 25 Mar 2023 15:34:50 +0100 Subject: [PATCH 054/108] Align reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 10c275b99a..24c0dc700b 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 10c275b99a01802f00168b92d4e9068fdf9b34b6 +Subproject commit 24c0dc700bfde4ddf8a592fef82a837528006340 From 60cf911610a09cbbb07a706212b22808b0c5e07d Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 30 Mar 2023 19:15:40 -0700 Subject: [PATCH 055/108] merged changes, fixed inconsistencies, implemented watchdog stop --- org.lflang/src/lib/c/reactor-c | 2 +- .../platform/arduino/Arduino-CMake-Toolchain | 1 + test/C/src/Watchdog.lf | 6 ++- test/C/src/file0.lf | 40 +++++++++++++++++++ test/C/src/file1.lf | 40 +++++++++++++++++++ test/C/src/file2.lf | 40 +++++++++++++++++++ 6 files changed, 126 insertions(+), 3 deletions(-) create mode 160000 org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain create mode 100644 test/C/src/file0.lf create mode 100644 test/C/src/file1.lf create mode 100644 test/C/src/file2.lf diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 10c275b99a..ff3c84c36b 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 10c275b99a01802f00168b92d4e9068fdf9b34b6 +Subproject commit ff3c84c36b2d7fbcbe09fafa577ec94f75194e83 diff --git a/org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain b/org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain new file mode 160000 index 0000000000..e745a9bed3 --- /dev/null +++ b/org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain @@ -0,0 +1 @@ +Subproject commit e745a9bed3c3fb83442d55bf05630f31574674f2 diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index e3d7f6cb41..4827637f07 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -1,4 +1,6 @@ -target C +target C { + workers: 100 +}; reactor Watcher { input x: int @@ -6,7 +8,7 @@ reactor Watcher { watchdog poodle(10 msec) {= instant_t p = lf_time_physical_elapsed() - lf_time_logical_elapsed(); - printf("Deadline missed! Lag: %lld (too late by %lld nsecs)\n", p, p-500000); + printf("Watchdog missed! Lag: %lld (too late by %lld nsecs)\n", p, p-500000); =} reaction(x) -> poodle, d {= diff --git a/test/C/src/file0.lf b/test/C/src/file0.lf new file mode 100644 index 0000000000..645242845a --- /dev/null +++ b/test/C/src/file0.lf @@ -0,0 +1,40 @@ +target C + +reactor Watcher { + input x: int + // Produced if the + // deadline is + // violated. + output d: int + + reaction(x) -> + d, poodle {= + lf_watchdog_start(poodle, 0); + printf("Normal reaction.\n"); + =} +} + +main reactor { + logical action a + w = new Watcher( + + ) + + reaction( + startup + ) -> + w.x, a {= + lf_set(w.x, 0); + lf_schedule(a, 0); + =} + + reaction(a) -> + w.x {= + lf_set(w.x, 0); + lf_nanosleep(MSEC(40)); + =} + + reaction(w.d) {= + printf("Deadline reactor produced an output.\n"); + =} +} diff --git a/test/C/src/file1.lf b/test/C/src/file1.lf new file mode 100644 index 0000000000..1183364928 --- /dev/null +++ b/test/C/src/file1.lf @@ -0,0 +1,40 @@ +target C + +reactor Watcher { + input x: int + // Produced if the + // deadline is + // violated. + output d: int + + reaction(x) -> + poodle, d {= + lf_watchdog_start(poodle, 0); + printf("Normal reaction.\n"); + =} +} + +main reactor { + logical action a + w = new Watcher( + + ) + + reaction( + startup + ) -> + w.x, a {= + lf_set(w.x, 0); + lf_schedule(a, 0); + =} + + reaction(a) -> + w.x {= + lf_set(w.x, 0); + lf_nanosleep(MSEC(40)); + =} + + reaction(w.d) {= + printf("Deadline reactor produced an output.\n"); + =} +} diff --git a/test/C/src/file2.lf b/test/C/src/file2.lf new file mode 100644 index 0000000000..1183364928 --- /dev/null +++ b/test/C/src/file2.lf @@ -0,0 +1,40 @@ +target C + +reactor Watcher { + input x: int + // Produced if the + // deadline is + // violated. + output d: int + + reaction(x) -> + poodle, d {= + lf_watchdog_start(poodle, 0); + printf("Normal reaction.\n"); + =} +} + +main reactor { + logical action a + w = new Watcher( + + ) + + reaction( + startup + ) -> + w.x, a {= + lf_set(w.x, 0); + lf_schedule(a, 0); + =} + + reaction(a) -> + w.x {= + lf_set(w.x, 0); + lf_nanosleep(MSEC(40)); + =} + + reaction(w.d) {= + printf("Deadline reactor produced an output.\n"); + =} +} From 7fb877be88cf5709758756b4ca1351ec9bace6ef Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 30 Mar 2023 21:04:33 -0700 Subject: [PATCH 056/108] threading error working --- org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain | 1 - org.lflang/src/org/lflang/generator/c/CGenerator.java | 2 +- test/C/src/Watchdog.lf | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 160000 org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain diff --git a/org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain b/org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain deleted file mode 160000 index e745a9bed3..0000000000 --- a/org.lflang/src/lib/platform/arduino/Arduino-CMake-Toolchain +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e745a9bed3c3fb83442d55bf05630f31574674f2 diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index e7ce2439f8..2ef5074455 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -473,7 +473,7 @@ 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 - // if (!isWatchdogCompatible()) return; + if (!isWatchdogCompatible()) return; // Perform set up that does not generate code setUpGeneralParameters(); diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index 4827637f07..488ae42acd 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -1,6 +1,6 @@ target C { workers: 100 -}; +} reactor Watcher { input x: int From 8b3b3e3e8ed7e765f2573012ddba1b4c9b5ac60e Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 30 Mar 2023 22:10:38 -0700 Subject: [PATCH 057/108] saving before pull again --- .../tests/compiler/FormattingUnitTests.java | 33 +- .../tests/compiler/LetInferenceTests.java | 2 +- .../compiler/LinguaFrancaValidationTest.java | 2234 ++++++----------- .../lflang/tests/compiler/RoundTripTests.java | 9 +- org.lflang/src/org/lflang/ASTUtils.java | 8 + org.lflang/src/org/lflang/LinguaFranca.xtext | 19 +- org.lflang/src/org/lflang/Target.java | 18 +- org.lflang/src/org/lflang/ast/IsEqual.java | 12 +- org.lflang/src/org/lflang/ast/ToLf.java | 60 +- org.lflang/src/org/lflang/ast/ToText.java | 6 + org.lflang/src/org/lflang/cli/Lff.java | 32 +- .../synthesis/LinguaFrancaSynthesis.java | 11 +- .../federated/extensions/CExtension.java | 13 +- .../federated/extensions/CExtensionUtils.java | 18 +- .../federated/extensions/TSExtension.java | 5 +- .../federated/generator/FederateInstance.java | 6 +- .../src/org/lflang/generator/CodeMap.java | 15 +- .../org/lflang/generator/GeneratorBase.java | 77 - .../org/lflang/generator/GeneratorUtils.java | 2 +- .../lflang/generator/LfExpressionVisitor.java | 160 ++ .../lflang/generator/ParameterInstance.java | 29 +- .../org/lflang/generator/ReactorInstance.java | 81 +- .../src/org/lflang/generator/TargetTypes.java | 14 +- .../lflang/generator/c/CActionGenerator.java | 7 +- .../org/lflang/generator/c/CGenerator.java | 6 +- .../generator/c/CParameterGenerator.java | 50 +- .../lflang/generator/c/CStateGenerator.java | 15 +- .../lflang/generator/c/CTimerGenerator.java | 6 +- .../src/org/lflang/generator/c/CTypes.java | 72 +- .../generator/cpp/CppParameterGenerator.kt | 20 +- .../generator/cpp/CppReactorGenerator.kt | 9 +- .../lflang/generator/cpp/CppStateGenerator.kt | 2 +- .../src/org/lflang/generator/cpp/CppTypes.kt | 21 +- .../org/lflang/generator/python/PyUtil.java | 22 +- .../generator/python/PythonGenerator.java | 2 +- .../python/PythonParameterGenerator.java | 47 +- .../lflang/generator/python/PythonTypes.java | 58 +- .../generator/ts/TSConstructorGenerator.kt | 2 +- .../generator/ts/TSDelayBodyGenerator.kt | 2 +- .../generator/ts/TSDockerGenerator.java | 29 + .../lflang/generator/ts/TSDockerGenerator.kt | 26 - .../org/lflang/generator/ts/TSExtensions.kt | 8 +- .../org/lflang/generator/ts/TSGenerator.kt | 2 +- .../generator/ts/TSInstanceGenerator.kt | 6 +- .../generator/ts/TSParameterGenerator.kt | 2 +- .../ts/TSParameterPreambleGenerator.kt | 4 +- .../generator/ts/TSReactionGenerator.kt | 4 +- .../lflang/generator/ts/TSStateGenerator.kt | 4 +- .../src/org/lflang/generator/ts/TSTypes.java | 75 + .../src/org/lflang/generator/ts/TSTypes.kt | 57 - .../org/lflang/validation/LFValidator.java | 34 +- test/C/src/ActionDelay.lf | 2 +- test/C/src/ActionIsPresent.lf | 4 +- test/C/src/After.lf | 4 +- test/C/src/AfterCycles.lf | 2 +- test/C/src/AfterOverlapped.lf | 4 +- test/C/src/AfterZero.lf | 4 +- test/C/src/Alignment.lf | 8 +- test/C/src/ArrayAsParameter.lf | 10 +- test/C/src/ArrayAsType.lf | 2 +- test/C/src/ArrayFree.lf | 2 +- test/C/src/ArrayFreeMultiple.lf | 4 +- test/C/src/ArrayPrint.lf | 8 +- test/C/src/ArrayScale.lf | 2 +- test/C/src/CharLiteralInitializer.lf | 2 +- test/C/src/Composition.lf | 6 +- test/C/src/CompositionAfter.lf | 8 +- test/C/src/CompositionInheritance.lf | 6 +- test/C/src/CountSelf.lf | 2 +- test/C/src/Deadline.lf | 8 +- test/C/src/DeadlineHandledAbove.lf | 4 +- test/C/src/DeadlineWithBanks.lf | 4 +- test/C/src/DeadlineZero.lf | 2 +- test/C/src/DelayArray.lf | 4 +- test/C/src/DelayArrayWithAfter.lf | 8 +- test/C/src/DelayInt.lf | 6 +- test/C/src/DelayPointer.lf | 6 +- test/C/src/DelayString.lf | 4 +- test/C/src/DelayStruct.lf | 4 +- test/C/src/DelayStructWithAfter.lf | 2 +- test/C/src/DelayStructWithAfterOverlapped.lf | 4 +- test/C/src/DelayedAction.lf | 2 +- test/C/src/DoubleInvocation.lf | 4 +- test/C/src/DoublePort.lf | 2 +- test/C/src/DoubleReaction.lf | 6 +- test/C/src/DoubleTrigger.lf | 2 +- test/C/src/FloatLiteral.lf | 8 +- test/C/src/Gain.lf | 4 +- test/C/src/GetMicroStep.lf | 2 +- test/C/src/Hello.lf | 10 +- test/C/src/HelloWorld.lf | 2 +- test/C/src/Hierarchy2.lf | 4 +- test/C/src/IdentifierLength.lf | 6 +- test/C/src/ImportComposition.lf | 2 +- test/C/src/InheritanceAction.lf | 2 +- test/C/src/ManualDelayedReaction.lf | 2 +- test/C/src/Methods.lf | 2 +- test/C/src/MethodsRecursive.lf | 2 +- test/C/src/MethodsSameName.lf | 4 +- test/C/src/MovingAverage.lf | 8 +- test/C/src/MultipleContained.lf | 2 +- test/C/src/MultipleOutputs.lf | 2 +- test/C/src/NativeListsAndTimes.lf | 22 +- test/C/src/NestedTriggeredReactions.lf | 4 +- test/C/src/ParameterHierarchy.lf | 16 +- test/C/src/ParameterizedState.lf | 4 +- test/C/src/PeriodicDesugared.lf | 2 +- test/C/src/PingPong.lf | 8 +- test/C/src/ReadOutputOfContainedReactor.lf | 2 +- test/C/src/ScheduleLogicalAction.lf | 2 +- test/C/src/SelfLoop.lf | 2 +- test/C/src/SendingInside.lf | 4 +- test/C/src/SendsPointerTest.lf | 2 +- test/C/src/SetArray.lf | 2 +- test/C/src/SetToken.lf | 2 +- test/C/src/SimpleDeadline.lf | 2 +- test/C/src/SlowingClock.lf | 4 +- test/C/src/SlowingClockPhysical.lf | 4 +- test/C/src/Starvation.lf | 6 +- test/C/src/Stop.lf | 2 +- test/C/src/StopZero.lf | 2 +- test/C/src/Stride.lf | 6 +- test/C/src/StructAsState.lf | 2 +- test/C/src/StructAsType.lf | 2 +- test/C/src/StructAsTypeDirect.lf | 2 +- test/C/src/StructParallel.lf | 6 +- test/C/src/StructPrint.lf | 2 +- test/C/src/StructScale.lf | 6 +- test/C/src/SubclassesAndStartup.lf | 6 +- test/C/src/TimeLimit.lf | 8 +- test/C/src/TimeState.lf | 4 +- test/C/src/Timeout.lf | 2 +- test/C/src/TimeoutZero.lf | 2 +- test/C/src/ToReactionNested.lf | 4 +- test/C/src/TriggerDownstreamOnlyIfPresent2.lf | 2 +- test/C/src/UnconnectedInput.lf | 4 +- test/C/src/arduino/DigitalReadSerial.lf | 2 +- test/C/src/arduino/Fade.lf | 6 +- test/C/src/concurrent/AsyncCallback.lf | 8 +- test/C/src/concurrent/AsyncCallbackDrop.lf | 8 +- test/C/src/concurrent/AsyncCallbackReplace.lf | 8 +- test/C/src/concurrent/CompositionThreaded.lf | 6 +- .../DeadlineHandledAboveThreaded.lf | 4 +- test/C/src/concurrent/DeadlineThreaded.lf | 8 +- test/C/src/concurrent/DelayIntThreaded.lf | 6 +- .../src/concurrent/DoubleReactionThreaded.lf | 6 +- test/C/src/concurrent/GainThreaded.lf | 4 +- test/C/src/concurrent/HelloThreaded.lf | 10 +- test/C/src/concurrent/PingPongThreaded.lf | 8 +- test/C/src/concurrent/ScheduleAt.lf | 17 +- test/C/src/concurrent/ScheduleTwice.lf | 2 +- .../C/src/concurrent/ScheduleTwiceThreaded.lf | 2 +- .../C/src/concurrent/SendingInsideThreaded.lf | 4 +- test/C/src/concurrent/StarvationThreaded.lf | 6 +- test/C/src/concurrent/StopThreaded.lf | 2 +- test/C/src/concurrent/StopZeroThreaded.lf | 2 +- test/C/src/concurrent/Threaded.lf | 8 +- test/C/src/concurrent/ThreadedMultiport.lf | 12 +- test/C/src/concurrent/ThreadedThreaded.lf | 8 +- test/C/src/concurrent/TimeLimitThreaded.lf | 8 +- test/C/src/concurrent/TimeoutThreaded.lf | 2 +- test/C/src/concurrent/TimeoutZeroThreaded.lf | 2 +- test/C/src/concurrent/Tracing.lf | 14 +- .../DistributedCountContainerized.lf | 4 +- test/C/src/federated/BroadcastFeedback.lf | 2 +- .../BroadcastFeedbackWithHierarchy.lf | 4 +- test/C/src/federated/CycleDetection.lf | 2 +- test/C/src/federated/DecentralizedP2PComm.lf | 10 +- .../DecentralizedP2PUnbalancedTimeout.lf | 8 +- ...centralizedP2PUnbalancedTimeoutPhysical.lf | 8 +- test/C/src/federated/DistributedBank.lf | 2 +- .../federated/DistributedBankToMultiport.lf | 2 +- test/C/src/federated/DistributedCount.lf | 4 +- .../DistributedCountDecentralized.lf | 2 +- .../DistributedCountDecentralizedLate.lf | 6 +- ...tributedCountDecentralizedLateHierarchy.lf | 8 +- .../src/federated/DistributedCountPhysical.lf | 4 +- .../DistributedCountPhysicalAfterDelay.lf | 2 +- .../DistributedCountPhysicalDecentralized.lf | 4 +- test/C/src/federated/DistributedDoublePort.lf | 2 +- .../DistributedLogicalActionUpstreamLong.lf | 4 +- .../src/federated/DistributedLoopedAction.lf | 11 +- .../DistributedLoopedActionDecentralized.lf | 19 +- .../DistributedLoopedPhysicalAction.lf | 15 +- test/C/src/federated/DistributedMultiport.lf | 4 +- .../federated/DistributedMultiportToBank.lf | 4 +- .../federated/DistributedMultiportToken.lf | 2 +- .../src/federated/DistributedNetworkOrder.lf | 2 +- .../DistributedPhysicalActionUpstream.lf | 2 +- .../DistributedPhysicalActionUpstreamLong.lf | 2 +- test/C/src/federated/DistributedStop.lf | 6 +- test/C/src/federated/DistributedToken.lf | 6 +- test/C/src/federated/FeedbackDelay.lf | 8 +- test/C/src/federated/FeedbackDelaySimple.lf | 2 +- test/C/src/federated/HelloDistributed.lf | 2 +- .../federated/LoopDistributedCentralized.lf | 6 +- .../federated/LoopDistributedCentralized2.lf | 10 +- ...oopDistributedCentralizedPhysicalAction.lf | 6 +- .../LoopDistributedCentralizedPrecedence.lf | 8 +- ...stributedCentralizedPrecedenceHierarchy.lf | 12 +- .../federated/LoopDistributedDecentralized.lf | 6 +- test/C/src/federated/LoopDistributedDouble.lf | 6 +- test/C/src/federated/PhysicalSTP.lf | 4 +- test/C/src/federated/PingPongDistributed.lf | 2 +- .../federated/PingPongDistributedPhysical.lf | 10 +- test/C/src/federated/TopLevelArtifacts.lf | 2 +- test/C/src/federated/failing/ClockSync.lf | 2 +- .../failing/DistributedDoublePortLooped.lf | 4 +- .../failing/DistributedDoublePortLooped2.lf | 4 +- .../DistributedNetworkOrderDecentralized.lf | 2 +- .../LoopDistributedDecentralizedPrecedence.lf | 8 +- ...ributedDecentralizedPrecedenceHierarchy.lf | 38 +- test/C/src/lib/Count.lf | 4 +- test/C/src/lib/InternalDelay.lf | 2 +- test/C/src/lib/LoopedActionSender.lf | 4 +- test/C/src/lib/Test.lf | 4 +- test/C/src/lib/TestCount.lf | 6 +- test/C/src/lib/TestCountMultiport.lf | 12 +- test/C/src/modal_models/ConvertCaseTest.lf | 4 +- test/C/src/modal_models/Count3Modes.lf | 2 +- test/C/src/modal_models/MixedReactions.lf | 4 +- test/C/src/modal_models/ModalAfter.lf | 4 +- test/C/src/modal_models/ModalCycleBreaker.lf | 4 +- test/C/src/modal_models/ModalStateReset.lf | 6 +- .../C/src/modal_models/ModalStateResetAuto.lf | 6 +- .../MultipleOutputFeeder_2Connections.lf | 4 +- ...ultipleOutputFeeder_ReactionConnections.lf | 4 +- test/C/src/modal_models/util/TraceTesting.lf | 16 +- test/C/src/multiport/BankIndexInitializer.lf | 8 +- .../src/multiport/BankMultiportToReaction.lf | 4 +- .../src/multiport/BankReactionsInContainer.lf | 6 +- test/C/src/multiport/BankSelfBroadcast.lf | 4 +- test/C/src/multiport/BankToBank.lf | 10 +- test/C/src/multiport/BankToBankMultiport.lf | 10 +- .../src/multiport/BankToBankMultiportAfter.lf | 10 +- test/C/src/multiport/BankToMultiport.lf | 8 +- test/C/src/multiport/BankToReaction.lf | 2 +- test/C/src/multiport/Broadcast.lf | 4 +- test/C/src/multiport/BroadcastAfter.lf | 4 +- .../C/src/multiport/BroadcastMultipleAfter.lf | 6 +- test/C/src/multiport/DualBanks.lf | 4 +- test/C/src/multiport/DualBanksMultiport.lf | 4 +- test/C/src/multiport/FullyConnected.lf | 6 +- .../multiport/FullyConnectedAddressable.lf | 8 +- .../FullyConnectedAddressableAfter.lf | 2 +- test/C/src/multiport/MultiportFromBank.lf | 8 +- .../multiport/MultiportFromBankHierarchy.lf | 4 +- .../C/src/multiport/MultiportFromHierarchy.lf | 14 +- test/C/src/multiport/MultiportFromReaction.lf | 8 +- test/C/src/multiport/MultiportIn.lf | 4 +- .../src/multiport/MultiportInParameterized.lf | 6 +- test/C/src/multiport/MultiportMutableInput.lf | 4 +- .../multiport/MultiportMutableInputArray.lf | 4 +- test/C/src/multiport/MultiportOut.lf | 4 +- test/C/src/multiport/MultiportToBank.lf | 8 +- test/C/src/multiport/MultiportToBankAfter.lf | 8 +- test/C/src/multiport/MultiportToBankDouble.lf | 4 +- .../src/multiport/MultiportToBankHierarchy.lf | 10 +- test/C/src/multiport/MultiportToHierarchy.lf | 12 +- test/C/src/multiport/MultiportToMultiport.lf | 10 +- test/C/src/multiport/MultiportToMultiport2.lf | 10 +- .../multiport/MultiportToMultiport2After.lf | 4 +- .../multiport/MultiportToMultiportArray.lf | 4 +- .../MultiportToMultiportParameter.lf | 10 +- test/C/src/multiport/MultiportToPort.lf | 4 +- test/C/src/multiport/MultiportToReaction.lf | 6 +- test/C/src/multiport/NestedBanks.lf | 10 +- .../C/src/multiport/NestedInterleavedBanks.lf | 4 +- .../src/multiport/ReactionToContainedBank.lf | 4 +- .../src/multiport/ReactionToContainedBank2.lf | 6 +- .../ReactionToContainedBankMultiport.lf | 4 +- test/C/src/multiport/ReactionsToNested.lf | 4 +- test/C/src/multiport/Sparse.lf | 8 +- test/C/src/multiport/SparseFallback.lf | 8 +- .../TriggerDownstreamOnlyIfPresent.lf | 2 +- .../serialization/ROSBuiltInSerialization.lf | 4 +- .../ROSBuiltInSerializationSharedPtr.lf | 4 +- test/C/src/target/HelloWorldCCPP.lf | 2 +- test/C/src/token/TokenContainedPrint.lf | 2 +- test/C/src/token/TokenContainedPrintBank.lf | 2 +- test/C/src/token/TokenContainedSource.lf | 6 +- test/C/src/token/TokenContainedSourceBank.lf | 6 +- test/C/src/token/lib/Token.lf | 10 +- test/Cpp/src/ActionDelay.lf | 2 +- test/Cpp/src/ActionIsPresent.lf | 6 +- test/Cpp/src/ActionValues.lf | 4 +- test/Cpp/src/After.lf | 4 +- test/Cpp/src/AfterOverlapped.lf | 2 +- test/Cpp/src/AfterZero.lf | 4 +- test/Cpp/src/Alignment.lf | 2 +- test/Cpp/src/ArrayAsParameter.lf | 8 +- test/Cpp/src/ArrayPrint.lf | 2 +- test/Cpp/src/ArrayScale.lf | 2 +- test/Cpp/src/CharLiteralInitializer.lf | 2 +- test/Cpp/src/Composition.lf | 6 +- test/Cpp/src/CompositionAfter.lf | 8 +- test/Cpp/src/CountTest.lf | 2 +- test/Cpp/src/Deadline.lf | 8 +- test/Cpp/src/DeadlineHandledAbove.lf | 4 +- test/Cpp/src/DelayInt.lf | 2 +- test/Cpp/src/DelayedAction.lf | 2 +- test/Cpp/src/DoubleInvocation.lf | 4 +- test/Cpp/src/DoublePort.lf | 2 +- test/Cpp/src/DoubleReaction.lf | 6 +- test/Cpp/src/DoubleTrigger.lf | 2 +- test/Cpp/src/FloatLiteral.lf | 8 +- test/Cpp/src/Gain.lf | 2 +- test/Cpp/src/GetMicroStep.lf | 2 +- test/Cpp/src/Hello.lf | 11 +- test/Cpp/src/Hierarchy2.lf | 4 +- test/Cpp/src/ImportComposition.lf | 2 +- test/Cpp/src/ManualDelayedReaction.lf | 2 +- test/Cpp/src/Methods.lf | 2 +- test/Cpp/src/MovingAverage.lf | 6 +- test/Cpp/src/NativeListsAndTimes.lf | 20 +- test/Cpp/src/NestedTriggeredReactions.lf | 4 +- test/Cpp/src/ParameterHierarchy.lf | 6 +- test/Cpp/src/ParameterizedState.lf | 4 +- test/Cpp/src/ParametersOutOfOrder.lf | 2 +- test/Cpp/src/PeriodicDesugared.lf | 4 +- test/Cpp/src/Pipeline.lf | 4 +- test/Cpp/src/ReadOutputOfContainedReactor.lf | 2 +- test/Cpp/src/ScheduleLogicalAction.lf | 2 +- test/Cpp/src/SelfLoop.lf | 2 +- test/Cpp/src/SendingInside.lf | 4 +- test/Cpp/src/SimpleDeadline.lf | 2 +- test/Cpp/src/SlowingClock.lf | 4 +- test/Cpp/src/SlowingClockPhysical.lf | 4 +- test/Cpp/src/Stride.lf | 4 +- test/Cpp/src/StructPrint.lf | 4 +- test/Cpp/src/StructScale.lf | 2 +- test/Cpp/src/TimeLimit.lf | 8 +- test/Cpp/src/TimeState.lf | 4 +- test/Cpp/src/Timeout_Test.lf | 2 +- test/Cpp/src/ToReactionNested.lf | 4 +- .../src/TriggerDownstreamOnlyIfPresent2.lf | 2 +- test/Cpp/src/concurrent/AsyncCallback.lf | 6 +- test/Cpp/src/concurrent/AsyncCallback2.lf | 4 +- .../Cpp/src/concurrent/CompositionThreaded.lf | 6 +- .../DeadlineHandledAboveThreaded.lf | 4 +- test/Cpp/src/concurrent/DeadlineThreaded.lf | 8 +- test/Cpp/src/concurrent/DelayIntThreaded.lf | 2 +- .../src/concurrent/DoubleReactionThreaded.lf | 6 +- test/Cpp/src/concurrent/GainThreaded.lf | 2 +- test/Cpp/src/concurrent/HelloThreaded.lf | 11 +- .../src/concurrent/SendingInsideThreaded.lf | 4 +- test/Cpp/src/concurrent/Threaded.lf | 4 +- test/Cpp/src/concurrent/ThreadedThreaded.lf | 4 +- test/Cpp/src/concurrent/TimeLimitThreaded.lf | 8 +- test/Cpp/src/enclave/EnclaveBank.lf | 8 +- test/Cpp/src/enclave/EnclaveBankEach.lf | 8 +- test/Cpp/src/enclave/EnclaveHelloWorld.lf | 4 +- test/Cpp/src/enclave/EnclaveHierarchy.lf | 6 +- test/Cpp/src/enclave/EnclaveShutdown.lf | 6 +- test/Cpp/src/enclave/EnclaveTimeout.lf | 2 +- test/Cpp/src/lib/Count.lf | 2 +- test/Cpp/src/lib/LoopedActionSender.lf | 4 +- test/Cpp/src/multiport/BankSelfBroadcast.lf | 4 +- test/Cpp/src/multiport/BankToBank.lf | 10 +- test/Cpp/src/multiport/BankToBankMultiport.lf | 10 +- .../src/multiport/BankToBankMultiportAfter.lf | 12 +- test/Cpp/src/multiport/BankToMultiport.lf | 4 +- test/Cpp/src/multiport/Broadcast.lf | 2 +- test/Cpp/src/multiport/BroadcastAfter.lf | 4 +- .../src/multiport/BroadcastMultipleAfter.lf | 6 +- test/Cpp/src/multiport/FullyConnected.lf | 6 +- .../multiport/FullyConnectedAddressable.lf | 6 +- .../FullyConnectedAddressableAfter.lf | 2 +- .../src/multiport/IndexIntoMultiportOutput.lf | 10 +- test/Cpp/src/multiport/Multiport.lf | 2 +- test/Cpp/src/multiport/MultiportFromBank.lf | 8 +- .../multiport/MultiportFromBankHierarchy.lf | 4 +- .../MultiportFromBankHierarchyAfter.lf | 4 +- .../src/multiport/MultiportFromHierarchy.lf | 4 +- test/Cpp/src/multiport/MultiportIn.lf | 4 +- test/Cpp/src/multiport/MultiportOut.lf | 4 +- test/Cpp/src/multiport/MultiportToBank.lf | 2 +- .../Cpp/src/multiport/MultiportToBankAfter.lf | 2 +- .../src/multiport/MultiportToBankHierarchy.lf | 4 +- .../Cpp/src/multiport/MultiportToHierarchy.lf | 12 +- .../Cpp/src/multiport/MultiportToMultiport.lf | 2 +- .../src/multiport/MultiportToMultiport2.lf | 4 +- .../multiport/MultiportToMultiport2After.lf | 4 +- .../multiport/MultiportToMultiportArray.lf | 4 +- test/Cpp/src/multiport/MultiportToPort.lf | 4 +- .../ReadMultiportOutputOfContainedBank.lf | 4 +- .../multiport/ReadOutputOfContainedBank.lf | 4 +- test/Cpp/src/multiport/WidthGivenByCode.lf | 2 +- .../multiport/WriteInputOfContainedBank.lf | 4 +- .../WriteMultiportInputOfContainedBank.lf | 4 +- test/Cpp/src/properties/Fast.lf | 2 +- test/Cpp/src/properties/Timeout.lf | 2 +- test/Cpp/src/properties/TimeoutZero.lf | 2 +- test/Cpp/src/target/AfterVoid.lf | 4 +- .../src/target/BraceAndParenInitialization.lf | 5 +- .../src/target/CliParserGenericArguments.lf | 26 +- test/Cpp/src/target/CombinedTypeNames.lf | 10 +- test/Cpp/src/target/GenericDelay.lf | 2 +- .../src/target/GenericParameterAndState.lf | 4 +- test/Cpp/src/target/InitializerSyntax.lf | 89 + test/Cpp/src/target/PointerParameters.lf | 2 +- test/Python/src/ActionDelay.lf | 2 +- test/Python/src/ActionIsPresent.lf | 6 +- test/Python/src/After.lf | 4 +- test/Python/src/AfterCycles.lf | 2 +- test/Python/src/AfterOverlapped.lf | 4 +- test/Python/src/ArrayAsParameter.lf | 8 +- test/Python/src/ArrayAsType.lf | 2 +- test/Python/src/ArrayFree.lf | 2 +- test/Python/src/ArrayPrint.lf | 2 +- test/Python/src/ArrayScale.lf | 2 +- test/Python/src/Composition.lf | 6 +- test/Python/src/CompositionAfter.lf | 8 +- test/Python/src/CompositionInheritance.lf | 6 +- test/Python/src/CountSelf.lf | 4 +- test/Python/src/CountTest.lf | 2 +- test/Python/src/Deadline.lf | 8 +- test/Python/src/DeadlineHandledAbove.lf | 4 +- test/Python/src/DelayArray.lf | 4 +- test/Python/src/DelayArrayWithAfter.lf | 8 +- test/Python/src/DelayInt.lf | 6 +- test/Python/src/DelayString.lf | 4 +- test/Python/src/DelayStruct.lf | 4 +- test/Python/src/DelayStructWithAfter.lf | 2 +- .../src/DelayStructWithAfterOverlapped.lf | 4 +- test/Python/src/DelayedAction.lf | 2 +- test/Python/src/DoubleInvocation.lf | 4 +- test/Python/src/DoubleReaction.lf | 6 +- test/Python/src/FloatLiteral.lf | 8 +- test/Python/src/Gain.lf | 4 +- test/Python/src/GetMicroStep.lf | 2 +- test/Python/src/Hello.lf | 8 +- test/Python/src/HelloWorld.lf | 2 +- test/Python/src/Hierarchy2.lf | 4 +- test/Python/src/IdentifierLength.lf | 6 +- test/Python/src/ImportComposition.lf | 2 +- test/Python/src/ManualDelayedReaction.lf | 2 +- test/Python/src/Methods.lf | 2 +- test/Python/src/MethodsRecursive.lf | 2 +- test/Python/src/MethodsSameName.lf | 4 +- test/Python/src/MovingAverage.lf | 6 +- test/Python/src/MultipleContained.lf | 2 +- test/Python/src/NativeListsAndTimes.lf | 14 +- test/Python/src/ParameterizedState.lf | 4 +- test/Python/src/PeriodicDesugared.lf | 2 +- test/Python/src/PingPong.lf | 8 +- test/Python/src/Pipeline.lf | 6 +- .../src/ReadOutputOfContainedReactor.lf | 2 +- test/Python/src/ScheduleLogicalAction.lf | 2 +- test/Python/src/SelfLoop.lf | 2 +- test/Python/src/SendingInside.lf | 4 +- test/Python/src/SetArray.lf | 2 +- test/Python/src/SimpleDeadline.lf | 2 +- test/Python/src/SlowingClock.lf | 4 +- test/Python/src/SlowingClockPhysical.lf | 4 +- test/Python/src/Stride.lf | 6 +- test/Python/src/StructAsState.lf | 2 +- test/Python/src/StructAsType.lf | 2 +- test/Python/src/StructAsTypeDirect.lf | 2 +- test/Python/src/StructParallel.lf | 4 +- test/Python/src/StructPrint.lf | 2 +- test/Python/src/StructScale.lf | 4 +- test/Python/src/SubclassesAndStartup.lf | 6 +- test/Python/src/TimeLimit.lf | 8 +- test/Python/src/TimeState.lf | 4 +- test/Python/src/Timers.lf | 2 +- .../src/TriggerDownstreamOnlyIfPresent.lf | 2 +- .../src/TriggerDownstreamOnlyIfPresent2.lf | 2 +- test/Python/src/UnconnectedInput.lf | 4 +- test/Python/src/concurrent/AsyncCallback.lf | 8 +- .../src/concurrent/AsyncCallbackNoTimer.lf | 8 +- .../src/docker/FilesPropertyContainerized.lf | 2 +- .../DistributedCountContainerized.lf | 2 +- .../Python/src/federated/BroadcastFeedback.lf | 2 +- .../BroadcastFeedbackWithHierarchy.lf | 4 +- test/Python/src/federated/CycleDetection.lf | 2 +- .../src/federated/DecentralizedP2PComm.lf | 6 +- .../DecentralizedP2PUnbalancedTimeout.lf | 8 +- ...centralizedP2PUnbalancedTimeoutPhysical.lf | 8 +- test/Python/src/federated/DistributedBank.lf | 2 +- .../federated/DistributedBankToMultiport.lf | 2 +- test/Python/src/federated/DistributedCount.lf | 4 +- .../DistributedCountDecentralized.lf | 2 +- .../DistributedCountDecentralizedLate.lf | 6 +- ...ributedCountDecentralizedLateDownstream.lf | 8 +- ...tributedCountDecentralizedLateHierarchy.lf | 2 +- .../src/federated/DistributedCountPhysical.lf | 4 +- .../DistributedCountPhysicalAfterDelay.lf | 4 +- .../DistributedCountPhysicalDecentralized.lf | 4 +- .../src/federated/DistributedDoublePort.lf | 2 +- .../src/federated/DistributedLoopedAction.lf | 8 +- .../DistributedLoopedPhysicalAction.lf | 12 +- .../src/federated/DistributedMultiport.lf | 4 +- .../federated/DistributedMultiportToBank.lf | 4 +- .../federated/DistributedMultiportToken.lf | 2 +- test/Python/src/federated/DistributedStop.lf | 6 +- test/Python/src/federated/HelloDistributed.lf | 2 +- ...stributedCentralizedPrecedenceHierarchy.lf | 12 +- test/Python/src/federated/PhysicalSTP.lf | 4 +- .../src/federated/PingPongDistributed.lf | 10 +- .../LoopDistributedCentralizedPrecedence.lf | 8 +- .../failing/LoopDistributedDouble.lf | 6 +- test/Python/src/lib/Count.lf | 4 +- test/Python/src/lib/InternalDelay.lf | 2 +- test/Python/src/lib/LoopedActionSender.lf | 4 +- test/Python/src/lib/Test.lf | 2 +- test/Python/src/lib/TestCount.lf | 6 +- test/Python/src/lib/TestCountMultiport.lf | 6 +- .../src/modal_models/ConvertCaseTest.lf | 4 +- test/Python/src/modal_models/Count3Modes.lf | 2 +- test/Python/src/modal_models/ModalAfter.lf | 4 +- .../src/modal_models/ModalCycleBreaker.lf | 4 +- .../src/modal_models/ModalStateReset.lf | 6 +- .../src/modal_models/ModalStateResetAuto.lf | 6 +- .../MultipleOutputFeeder_2Connections.lf | 4 +- ...ultipleOutputFeeder_ReactionConnections.lf | 4 +- .../src/modal_models/util/TraceTesting.lf | 10 +- .../src/multiport/BankIndexInitializer.lf | 8 +- .../src/multiport/BankReactionsInContainer.lf | 6 +- test/Python/src/multiport/BankToBank.lf | 10 +- .../src/multiport/BankToBankMultiport.lf | 10 +- .../src/multiport/BankToBankMultiportAfter.lf | 2 +- test/Python/src/multiport/BankToMultiport.lf | 8 +- test/Python/src/multiport/Broadcast.lf | 6 +- .../src/multiport/BroadcastMultipleAfter.lf | 4 +- .../Python/src/multiport/MultiportFromBank.lf | 4 +- .../multiport/MultiportFromBankHierarchy.lf | 2 +- .../src/multiport/MultiportFromHierarchy.lf | 4 +- .../src/multiport/MultiportFromReaction.lf | 6 +- test/Python/src/multiport/MultiportIn.lf | 4 +- .../src/multiport/MultiportInParameterized.lf | 6 +- .../src/multiport/MultiportMutableInput.lf | 4 +- .../multiport/MultiportMutableInputArray.lf | 4 +- test/Python/src/multiport/MultiportOut.lf | 4 +- test/Python/src/multiport/MultiportToBank.lf | 4 +- .../src/multiport/MultiportToBankAfter.lf | 4 +- .../src/multiport/MultiportToBankHierarchy.lf | 4 +- .../src/multiport/MultiportToHierarchy.lf | 8 +- .../src/multiport/MultiportToMultiport.lf | 4 +- .../src/multiport/MultiportToMultiport2.lf | 4 +- .../multiport/MultiportToMultiport2After.lf | 2 +- .../multiport/MultiportToMultiportArray.lf | 4 +- .../MultiportToMultiportParameter.lf | 2 +- test/Python/src/multiport/MultiportToPort.lf | 4 +- .../src/multiport/MultiportToReaction.lf | 6 +- test/Python/src/multiport/NestedBanks.lf | 10 +- .../src/multiport/NestedInterleavedBanks.lf | 4 +- .../Python/src/multiport/ReactionsToNested.lf | 4 +- test/Python/src/target/AfterNoTypes.lf | 4 +- test/Rust/src/ActionDelay.lf | 2 +- test/Rust/src/ActionImplicitDelay.lf | 2 +- test/Rust/src/ActionIsPresent.lf | 4 +- test/Rust/src/ActionScheduleMicrostep.lf | 2 +- test/Rust/src/ActionValues.lf | 4 +- test/Rust/src/ActionValuesCleanup.lf | 2 +- test/Rust/src/CtorParamDefault.lf | 4 +- test/Rust/src/CtorParamMixed.lf | 12 +- test/Rust/src/CtorParamSimple.lf | 4 +- test/Rust/src/DependencyOnChildPort.lf | 2 +- test/Rust/src/DependencyUseOnLogicalAction.lf | 2 +- test/Rust/src/FloatLiteral.lf | 8 +- test/Rust/src/MainReactorParam.lf | 6 +- test/Rust/src/MovingAverage.lf | 9 +- test/Rust/src/NativeListsAndTimes.lf | 14 +- test/Rust/src/PortConnectionInSelfInChild.lf | 2 +- test/Rust/src/PortConnectionInSelfOutSelf.lf | 2 +- .../Rust/src/PortConnectionOutChildOutSelf.lf | 4 +- test/Rust/src/PortRefCleanup.lf | 4 +- test/Rust/src/PortValueCleanup.lf | 4 +- test/Rust/src/ReservedKeywords.lf | 4 +- test/Rust/src/StateInitializerVisibility.lf | 4 +- test/Rust/src/Stop.lf | 10 +- test/Rust/src/StopIdempotence.lf | 2 +- test/Rust/src/StopTimeoutExact.lf | 2 +- test/Rust/src/StructAsState.lf | 2 +- test/Rust/src/StructAsType.lf | 4 +- test/Rust/src/TimeState.lf | 2 +- test/Rust/src/TimerDefaults.lf | 2 +- test/Rust/src/TimerIsPresent.lf | 4 +- test/Rust/src/TimerPeriodic.lf | 2 +- test/Rust/src/Timers.lf | 2 +- test/Rust/src/TypeVarLengthList.lf | 2 +- test/Rust/src/concurrent/AsyncCallback.lf | 10 +- test/Rust/src/generics/CtorParamGeneric.lf | 4 +- .../Rust/src/generics/CtorParamGenericInst.lf | 6 +- test/Rust/src/generics/GenericReactor.lf | 2 +- .../src/multiport/ConnectionToSelfBank.lf | 8 +- .../multiport/ConnectionToSelfMultiport.lf | 6 +- test/Rust/src/multiport/CycledLhs_SelfLoop.lf | 2 +- test/Rust/src/multiport/CycledLhs_Single.lf | 2 +- test/Rust/src/multiport/FullyConnected.lf | 10 +- .../multiport/FullyConnectedAddressable.lf | 10 +- test/Rust/src/multiport/MultiportFromBank.lf | 6 +- .../src/multiport/MultiportFromHierarchy.lf | 4 +- test/Rust/src/multiport/MultiportIn.lf | 4 +- test/Rust/src/multiport/MultiportOut.lf | 4 +- test/Rust/src/multiport/MultiportToBank.lf | 6 +- .../src/multiport/MultiportToBankHierarchy.lf | 6 +- .../src/multiport/MultiportToMultiport2.lf | 4 +- .../multiport/ReadOutputOfContainedBank.lf | 6 +- test/Rust/src/multiport/WidthWithParameter.lf | 2 +- .../multiport/WriteInputOfContainedBank.lf | 6 +- test/Rust/src/target/CliFeature.lf | 2 +- .../target/MainParameterCanBeExpression.lf | 4 +- test/TypeScript/src/ActionDelay.lf | 2 +- test/TypeScript/src/After.lf | 2 +- test/TypeScript/src/ArrayAsParameter.lf | 6 +- test/TypeScript/src/ArrayAsType.lf | 2 +- test/TypeScript/src/ArrayPrint.lf | 2 +- test/TypeScript/src/ArrayScale.lf | 2 +- test/TypeScript/src/Composition.lf | 6 +- test/TypeScript/src/CompositionAfter.lf | 8 +- test/TypeScript/src/CountTest.lf | 2 +- test/TypeScript/src/Deadline.lf | 8 +- test/TypeScript/src/DeadlineHandledAbove.lf | 4 +- test/TypeScript/src/DelayInt.lf | 6 +- test/TypeScript/src/DelayedAction.lf | 2 +- test/TypeScript/src/DoubleInvocation.lf | 4 +- test/TypeScript/src/DoubleReaction.lf | 6 +- test/TypeScript/src/DoubleTrigger.lf | 2 +- test/TypeScript/src/FloatLiteral.lf | 8 +- test/TypeScript/src/Gain.lf | 4 +- test/TypeScript/src/Hello.lf | 10 +- test/TypeScript/src/Hierarchy2.lf | 4 +- test/TypeScript/src/MovingAverage.lf | 8 +- test/TypeScript/src/NativeListsAndTimes.lf | 14 +- test/TypeScript/src/ParameterizedState.lf | 4 +- test/TypeScript/src/PeriodicDesugared.lf | 4 +- .../src/ReadOutputOfContainedReactor.lf | 2 +- test/TypeScript/src/ScheduleLogicalAction.lf | 2 +- test/TypeScript/src/SendingInside.lf | 4 +- test/TypeScript/src/SendsPointerTest.lf | 2 +- test/TypeScript/src/SimpleDeadline.lf | 2 +- test/TypeScript/src/SlowingClock.lf | 4 +- test/TypeScript/src/SlowingClockPhysical.lf | 4 +- test/TypeScript/src/Stop.lf | 2 +- test/TypeScript/src/Stride.lf | 4 +- test/TypeScript/src/StructAsState.lf | 2 +- test/TypeScript/src/StructAsType.lf | 2 +- test/TypeScript/src/StructAsTypeDirect.lf | 2 +- test/TypeScript/src/StructPrint.lf | 2 +- test/TypeScript/src/StructScale.lf | 2 +- test/TypeScript/src/TimeLimit.lf | 8 +- test/TypeScript/src/TimeState.lf | 4 +- .../src/concurrent/AsyncCallback.lf | 6 +- .../DistributedCountContainerized.lf | 2 +- .../src/federated/DistributedCount.lf | 4 +- .../src/federated/DistributedCountPhysical.lf | 6 +- .../DistributedCountPhysicalAfterDelay.lf | 49 + .../src/federated/DistributedDoublePort.lf | 2 +- .../src/federated/DistributedLoopedAction.lf | 8 +- .../DistributedLoopedPhysicalAction.lf | 12 +- .../src/federated/DistributedStop.lf | 6 +- .../src/federated/HelloDistributed.lf | 2 +- .../federated/LoopDistributedCentralized.lf | 6 +- .../src/federated/LoopDistributedDouble.lf | 6 +- .../src/federated/PingPongDistributed.lf | 10 +- .../federated/PingPongDistributedPhysical.lf | 10 +- .../src/federated/TopLevelArtifacts.lf | 2 +- test/TypeScript/src/lib/Count.lf | 4 +- test/TypeScript/src/lib/InternalDelay.lf | 2 +- test/TypeScript/src/lib/LoopedActionSender.lf | 4 +- test/TypeScript/src/lib/TestCount.lf | 10 +- .../src/multiport/BankMultiportToReaction.lf | 4 +- .../src/multiport/BankReactionsInContainer.lf | 4 +- .../src/multiport/BankSelfBroadcast.lf | 2 +- test/TypeScript/src/multiport/BankToBank.lf | 6 +- .../src/multiport/BankToBankMultiport.lf | 10 +- .../src/multiport/BankToBankMultiportAfter.lf | 10 +- .../src/multiport/BankToMultiport.lf | 2 +- .../src/multiport/BankToReaction.lf | 2 +- .../src/multiport/BroadcastAfter.lf | 2 +- .../src/multiport/BroadcastMultipleAfter.lf | 4 +- .../src/multiport/FullyConnected.lf | 6 +- .../src/multiport/MultiportFromBank.lf | 6 +- .../multiport/MultiportFromBankHierarchy.lf | 2 +- .../src/multiport/MultiportFromHierarchy.lf | 14 +- .../src/multiport/MultiportFromReaction.lf | 8 +- test/TypeScript/src/multiport/MultiportIn.lf | 4 +- .../src/multiport/MultiportInParameterized.lf | 6 +- .../src/multiport/MultiportMutableInput.lf | 4 +- .../multiport/MultiportMutableInputArray.lf | 4 +- test/TypeScript/src/multiport/MultiportOut.lf | 4 +- .../src/multiport/MultiportToBankAfter.lf | 6 +- .../src/multiport/MultiportToBankDouble.lf | 2 +- .../src/multiport/MultiportToBankHierarchy.lf | 2 +- .../src/multiport/MultiportToHierarchy.lf | 12 +- .../src/multiport/MultiportToMultiport.lf | 2 +- .../src/multiport/MultiportToMultiport2.lf | 4 +- .../multiport/MultiportToMultiport2After.lf | 4 +- .../multiport/MultiportToMultiportArray.lf | 4 +- .../MultiportToMultiportParameter.lf | 10 +- .../src/multiport/MultiportToPort.lf | 4 +- .../src/multiport/MultiportToReaction.lf | 6 +- test/TypeScript/src/multiport/NestedBanks.lf | 6 +- .../src/multiport/ReactionToContainedBank.lf | 4 +- .../src/multiport/ReactionsToNested.lf | 4 +- test/TypeScript/src/target/AfterNoTypes.lf | 2 +- 698 files changed, 3285 insertions(+), 3471 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/LfExpressionVisitor.java create mode 100644 org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.kt create mode 100644 org.lflang/src/org/lflang/generator/ts/TSTypes.java delete mode 100644 org.lflang/src/org/lflang/generator/ts/TSTypes.kt create mode 100644 test/Cpp/src/target/InitializerSyntax.lf create mode 100644 test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/FormattingUnitTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/FormattingUnitTests.java index ae325ab94d..d7862e5d25 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/FormattingUnitTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/FormattingUnitTests.java @@ -34,7 +34,7 @@ public void testSimple() { @Test public void testAssignments() { - assertIsFormatted( + assertFormatsTo( """ target C @@ -43,6 +43,15 @@ public void testAssignments() { input in: int state last_invoked: tag_t({= NEVER_TAG_INITIALIZER =}) } + """, + """ + target C + + reactor Destination { + input ok: bool + input in: int + state last_invoked: tag_t = {= NEVER_TAG_INITIALIZER =} + } """ ); } @@ -56,16 +65,34 @@ public void testState() { reactor Destination { state one_init: tag_t( {= NEVER_TAG_INITIALIZER =}) state no_init: tag_t - state list_init(1,2) + state list_init(1,2) // this syntax is deprecated } """, """ target Python + reactor Destination { + state one_init: tag_t = {= NEVER_TAG_INITIALIZER =} + state no_init: tag_t + state list_init(1, 2) # this syntax is deprecated + } + """ + ); + } + + @Test + public void testCppInits() { + assertIsFormatted( + """ + target Cpp + reactor Destination { state one_init: tag_t({= NEVER_TAG_INITIALIZER =}) state no_init: tag_t - state list_init(1, 2) + state assign: int = 0 + state paren: int(0) + state brace: std::vector{1, 2} + state paren_list: std::vector(1, 2) } """ ); diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java index 3e40927de0..a78d41ac4d 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java @@ -101,7 +101,7 @@ public void testLet() throws Exception { )); Assertions.assertNotNull(model); - final var ctypes = new CTypes(new DefaultErrorReporter()); + final var ctypes = CTypes.getInstance(); final var resource = model.eResource(); final var transformation = new DelayedConnectionTransformation(new CDelayBodyGenerator(ctypes), ctypes, resource, true, true); transformation.applyTransformation(ASTUtils.getAllReactors(resource)); diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 4c0d5dee08..67df93ed30 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1,29 +1,30 @@ /* Scoping unit tests. */ /************* -Copyright (c) 2019, 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, 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.tests.compiler; import java.util.LinkedList; @@ -63,7 +64,7 @@ /** * Collection of unit tests to ensure validation is done correctly. - * + * * @author Edward A. Lee * @author Marten Lohstroh * @author Matt Weber @@ -71,7 +72,8 @@ * @author Alexander Schulz-Rosengarten */ public class LinguaFrancaValidationTest { - @Inject + + @Inject ParseHelper parser; @Inject @@ -79,6 +81,7 @@ public class LinguaFrancaValidationTest { /** * Helper function to parse a Lingua Franca program and expect no errors. + * * @return A model representing the parsed string. */ private Model parseWithoutError(String s) throws Exception { @@ -92,6 +95,7 @@ private Model parseWithoutError(String s) throws Exception { /** * Helper function to parse a Lingua Franca program and expect errors. + * * @return A model representing the parsed string. */ private Model parseWithError(String s) throws Exception { @@ -99,105 +103,72 @@ private Model parseWithError(String s) throws Exception { Assertions.assertNotNull(model); Assertions.assertFalse(model.eResource().getErrors().isEmpty()); return model; - } + } /** * Ensure that duplicate identifiers for actions reported. */ @Test public void duplicateVariable() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor Foo { -// logical action bar; -// physical action bar; -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor Foo {", - " logical action bar;", - " physical action bar;", - "}"); - validator.assertError(parseWithoutError(testCase), - LfPackage.eINSTANCE.getAction(), - null, - "Duplicate Variable 'bar' in Reactor 'Foo'"); + String testCase = """ + target TypeScript; + main reactor Foo { + logical action bar; + physical action bar; + } + """; + validator.assertError(parseWithoutError(testCase), + LfPackage.eINSTANCE.getAction(), + null, + "Duplicate Variable 'bar' in Reactor 'Foo'"); } /** - * Check that reactors in C++ cannot be named preamble + * Check that reactors in C++ cannot be named preamble */ @Test public void disallowReactorCalledPreamble() throws Exception { -// Java 17: -// Model model_no_errors = """ -// target Cpp; -// main reactor { -// } -// """ -// Java 11: - Model model_no_errors = parser.parse(String.join( - System.getProperty("line.separator"), - "target Cpp;", - "main reactor {", - "}" - )); - -// Java 17: -// Model model_error_1 = """ -// target Cpp; -// main reactor Preamble { -// } -// """ -// Java 11: - Model model_error_1 = parser.parse(String.join( - System.getProperty("line.separator"), - "target Cpp;", - "main reactor Preamble {", - "}" - )); - -// Java 17: -// Model model_error_2 = """ -// target Cpp; -// reactor Preamble { -// } -// main reactor Main { -// } -// """ -// Java 11: - Model model_error_2 = parser.parse(String.join( - System.getProperty("line.separator"), - "target Cpp;", - "reactor Preamble {", - "}", - "main reactor Main {", - "}" - )); - + Model model_no_errors = parser.parse(""" + target Cpp; + main reactor { + } + """); + + Model model_error_1 = parser.parse(""" + target Cpp; + main reactor Preamble { + } + """); + + Model model_error_2 = parser.parse(""" + target Cpp; + reactor Preamble { + } + main reactor Main { + } + """); + Assertions.assertNotNull(model_no_errors); Assertions.assertNotNull(model_error_1); Assertions.assertNotNull(model_error_2); - Assertions.assertTrue(model_no_errors.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing: " + model_no_errors.eResource().getErrors()); - Assertions.assertTrue(model_error_1.eResource().getErrors().isEmpty(), + Assertions.assertTrue(model_no_errors.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + + model_no_errors.eResource().getErrors()); + Assertions.assertTrue(model_error_1.eResource().getErrors().isEmpty(), "Encountered unexpected error while parsing: " + model_error_1.eResource().getErrors()); - Assertions.assertTrue(model_error_2.eResource().getErrors().isEmpty(), + Assertions.assertTrue(model_error_2.eResource().getErrors().isEmpty(), "Encountered unexpected error while parsing: " + model_error_2.eResource().getErrors()); validator.assertNoIssues(model_no_errors); - validator.assertError(model_error_1, - LfPackage.eINSTANCE.getReactor(), - null, - "Reactor cannot be named 'Preamble'"); - validator.assertError(model_error_2, - LfPackage.eINSTANCE.getReactor(), - null, - "Reactor cannot be named 'Preamble'"); + validator.assertError(model_error_1, + LfPackage.eINSTANCE.getReactor(), + null, + "Reactor cannot be named 'Preamble'"); + validator.assertError(model_error_2, + LfPackage.eINSTANCE.getReactor(), + null, + "Reactor cannot be named 'Preamble'"); } @@ -206,59 +177,39 @@ public void disallowReactorCalledPreamble() throws Exception { */ @Test public void disallowUnderscoreInputs() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor { -// input __bar; -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor {", - " input __bar;", - "}"); + String testCase = """ + target TypeScript; + main reactor { + input __bar; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } - + @Test public void disallowMainWithDifferentNameThanFile() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// main reactor Foo {} -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor Foo {}" - ); + String testCase = """ + target C; + main reactor Foo {} + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Name of main reactor must match the file name (or be omitted)"); } - + /** * Ensure that "__" is not allowed at the start of an output name. */ @Test public void disallowUnderscoreOutputs() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor Foo { -// output __bar; -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor Foo {", - " output __bar;", - "}"); + String testCase = """ + target TypeScript; + main reactor Foo { + output __bar; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); @@ -269,324 +220,207 @@ public void disallowUnderscoreOutputs() throws Exception { */ @Test public void disallowUnderscoreActions() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor Foo { -// logical action __bar; -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor Foo {", - " logical action __bar;", - "}"); + String testCase = """ + target TypeScript; + main reactor Foo { + logical action __bar; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAction(), null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } - + /** * Ensure that "__" is not allowed at the start of a timer name. */ @Test public void disallowUnderscoreTimers() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor Foo { -// timer __bar(0); -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor Foo {", - " timer __bar(0);", - "}"); + String testCase = """ + target TypeScript; + main reactor Foo { + timer __bar(0); + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } - + /** * Ensure that "__" is not allowed at the start of a parameter name. */ @Test public void disallowUnderscoreParameters() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor Foo(__bar) { -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor Foo(__bar) {", - "}"); + String testCase = """ + target TypeScript; + main reactor Foo(__bar) { + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getParameter(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } - + /** * Ensure that "__" is not allowed at the start of an state name. */ @Test public void disallowUnderscoreStates() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor Foo { -// state __bar; -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor Foo {", - " state __bar;", - "}"); + String testCase = """ + target TypeScript; + main reactor Foo { + state __bar; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } - + /** * Ensure that "__" is not allowed at the start of a reactor definition name. */ @Test public void disallowUnderscoreReactorDef() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// main reactor __Foo { -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "main reactor __Foo {", - "}"); + String testCase = """ + target TypeScript; + main reactor __Foo { + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __Foo"); } - + /** * Ensure that "__" is not allowed at the start of a reactor instantiation name. */ @Test public void disallowUnderscoreReactorInstantiation() throws Exception { -// Java 17: -// String testCase = """ -// target TypeScript; -// reactor Foo { -// } -// main reactor Bar { -// __x = new Foo(); -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target TypeScript;", - "reactor Foo {", - "}", - "main reactor Bar {", - " __x = new Foo();", - "}"); + String testCase = """ + target TypeScript; + reactor Foo { + } + main reactor Bar { + __x = new Foo(); + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInstantiation(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __x"); } - + /** * Disallow connection to port that is effect of reaction. */ @Test public void connectionToEffectPort() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor Foo { -// output out:int; -// } -// main reactor Bar { -// output out:int; -// x = new Foo(); -// x.out -> out; -// reaction(startup) -> out {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor Foo {", - " output out:int;", - "}", - "main reactor Bar {", - " output out:int;", - " x = new Foo();", - " x.out -> out;", - " reaction(startup) -> out {=", - " =}", - "}"); + String testCase = """ + target C; + reactor Foo { + output out:int; + } + main reactor Bar { + output out:int; + x = new Foo(); + x.out -> out; + reaction(startup) -> out {= + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'out' is already effect of a reaction."); } - + /** * Disallow connection to port that is effect of reaction. */ @Test public void connectionToEffectPort2() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor Foo { -// input inp:int; -// output out:int; -// } -// main reactor { -// output out:int; -// x = new Foo(); -// y = new Foo(); -// -// y.out -> x.inp; -// reaction(startup) -> x.inp {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor Foo {", - " input inp:int;", - " output out:int;", - "}", - "main reactor {", - " output out:int;", - " x = new Foo();", - " y = new Foo();", - "", - " y.out -> x.inp;", - " reaction(startup) -> x.inp {=", - " =}", - "}"); + String testCase = """ + target C; + reactor Foo { + input inp:int; + output out:int; + } + main reactor { + output out:int; + x = new Foo(); + y = new Foo(); + + y.out -> x.inp; + reaction(startup) -> x.inp {= + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'inp' is already effect of a reaction."); } - + /** - * Allow connection to the port of a contained reactor if another port with same name is effect of a reaction. + * Allow connection to the port of a contained reactor if another port with same name is effect + * of a reaction. */ @Test public void connectionToEffectPort3() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// -// reactor Foo { -// input in:int; -// } -// reactor Bar { -// input in:int; -// x1 = new Foo(); -// x2 = new Foo(); -// in -> x1.in; -// reaction(startup) -> x2.in {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "", - "reactor Foo {", - " input in:int;", - "}", - "reactor Bar {", - " input in:int;", - " x1 = new Foo();", - " x2 = new Foo();", - " in -> x1.in;", - " reaction(startup) -> x2.in {=", - " =}", - "}"); + String testCase = """ + target C; + + reactor Foo { + input in:int; + } + reactor Bar { + input in:int; + x1 = new Foo(); + x2 = new Foo(); + in -> x1.in; + reaction(startup) -> x2.in {= + =} + } + """; validator.assertNoErrors(parseWithoutError(testCase)); } /** - * Allow connection to the port of a contained reactor if another port with same name is effect of a reaction. + * Allow connection to the port of a contained reactor if another port with same name is effect + * of a reaction. */ @Test public void connectionToEffectPort3_5() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// -// reactor Foo { -// input in:int; -// } -// main reactor { -// input in:int; -// x1 = new Foo(); -// x2 = new Foo(); -// in -> x1.in; -// reaction(startup) -> x2.in {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "", - "reactor Foo {", - " input in:int;", - "}", - "main reactor {", - " input in:int;", - " x1 = new Foo();", - " x2 = new Foo();", - " in -> x1.in;", - " reaction(startup) -> x2.in {=", - " =}", - "}"); + String testCase = """ + target C; + + reactor Foo { + input in:int; + } + main reactor { + input in:int; + x1 = new Foo(); + x2 = new Foo(); + in -> x1.in; + reaction(startup) -> x2.in {= + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getVariable(), null, - "Main reactor cannot have inputs."); + "Main reactor cannot have inputs."); } /** - * Disallow connection to the port of a contained reactor if the same port is effect of a reaction. + * Disallow connection to the port of a contained reactor if the same port is effect of a + * reaction. */ @Test public void connectionToEffectPort4() throws Exception { -// Java 17: -// String testCase = """ -// target C; - -// reactor Foo { -// input in:int; -// } -// main reactor { -// input in:int; -// x1 = new Foo(); -// in -> x1.in; -// reaction(startup) -> x1.in {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "", - "reactor Foo {", - " input in:int;", - "}", - "main reactor {", - " input in:int;", - " x1 = new Foo();", - " in -> x1.in;", - " reaction(startup) -> x1.in {=", - " =}", - "}"); + String testCase = """ + target C; + + reactor Foo { + input in:int; + } + main reactor { + input in:int; + x1 = new Foo(); + in -> x1.in; + reaction(startup) -> x1.in {= + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'in' is already effect of a reaction."); } @@ -596,39 +430,22 @@ public void connectionToEffectPort4() throws Exception { */ @Test public void multipleConnectionsToInputTest() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor Source { -// output out:int; -// } -// reactor Sink { -// input in:int; -// } -// main reactor { -// input in:int; -// src = new Source(); -// sink = new Sink(); -// in -> sink.in; -// src.out -> sink.in; -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor Source {", - " output out:int;", - "}", - "reactor Sink {", - " input in:int;", - "}", - "main reactor {", - " input in:int;", - " src = new Source();", - " sink = new Sink();", - " in -> sink.in;", - " src.out -> sink.in;", - "}"); + String testCase = """ + target C; + reactor Source { + output out:int; + } + reactor Sink { + input in:int; + } + main reactor { + input in:int; + src = new Source(); + sink = new Sink(); + in -> sink.in; + src.out -> sink.in; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'in' may only appear once on the right side of a connection."); } @@ -638,146 +455,92 @@ public void multipleConnectionsToInputTest() throws Exception { */ @Test public void detectInstantiationCycle() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor Contained { -// x = new Contained(); -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor Contained {", - " x = new Contained();", - "}"); + String testCase = """ + target C; + reactor Contained { + x = new Contained(); + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInstantiation(), null, "Instantiation is part of a cycle: Contained"); } - - + + /** * Detect cycles in the instantiation graph. */ @Test public void detectInstantiationCycle2() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor Intermediate { -// x = new Contained(); -// } -// -// reactor Contained { -// x = new Intermediate(); -// } -// """ -// Java 11: - Model model = parseWithoutError(String.join( - System.getProperty("line.separator"), - "target C;", - "reactor Intermediate {", - " x = new Contained();", - "}", - "", - "reactor Contained {", - " x = new Intermediate();", - "}" - )); + String testCase = """ + target C; + reactor Intermediate { + x = new Contained(); + } + reactor Contained { + x = new Intermediate(); + } + """; + + Model model = parseWithoutError(testCase); validator.assertError(model, LfPackage.eINSTANCE.getInstantiation(), null, "Instantiation is part of a cycle: Intermediate, Contained."); validator.assertError(model, LfPackage.eINSTANCE.getInstantiation(), null, "Instantiation is part of a cycle: Intermediate, Contained."); } - + /** * Detect causality loop. */ @Test public void detectCausalityLoop() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// -// reactor X { -// input x:int; -// output y:int; -// reaction(x) -> y {= -// =} -// } -// -// main reactor { -// a = new X() -// b = new X() -// a.y -> b.x -// b.y -> a.x -// } -// """ -// Java 11: - Model model = parseWithoutError(String.join( - System.getProperty("line.separator"), - "target C;", - "", - "reactor X {", - " input x:int;", - " output y:int;", - " reaction(x) -> y {=", - " =}", - "}", - "", - "main reactor {", - " a = new X()", - " b = new X()", - " a.y -> b.x", - " b.y -> a.x", - "}" - )); + + String testCase = """ + target C; + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x + b.y -> a.x + } + """; + Model model = parseWithoutError(testCase); validator.assertError(model, LfPackage.eINSTANCE.getReaction(), null, "Reaction triggers involved in cyclic dependency in reactor X: x."); validator.assertError(model, LfPackage.eINSTANCE.getReaction(), - null, "Reaction effects involved in cyclic dependency in reactor X: y."); + null, "Reaction effects involved in cyclic dependency in reactor X: y."); } - + /** * Let cyclic dependencies be broken by "after" clauses. */ @Test public void afterBreaksCycle() throws Exception { -// Java 17: -// String testCase = """ -// target C -// -// reactor X { -// input x:int; -// output y:int; -// reaction(x) -> y {= -// =} -// } -// -// main reactor { -// a = new X() -// b = new X() -// a.y -> b.x after 5 msec -// b.y -> a.x -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C", - " ", - "reactor X {", - " input x:int;", - " output y:int;", - " reaction(x) -> y {=", - " =}", - "}", - "", - "main reactor {", - " a = new X()", - " b = new X()", - " a.y -> b.x after 5 msec", - " b.y -> a.x", - "}"); + String testCase = """ + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 5 msec + b.y -> a.x + } + """; + validator.assertNoErrors(parseWithoutError(testCase)); } @@ -787,42 +550,23 @@ public void afterBreaksCycle() throws Exception { */ @Test public void afterBreaksCycle2() throws Exception { -// Java 17: -// String testCase = """ -// target C -// -// reactor X { -// input x:int; -// output y:int; -// reaction(x) -> y {= -// =} -// } -// -// main reactor { -// a = new X() -// b = new X() -// a.y -> b.x after 0 sec -// b.y -> a.x -// } -// """ -// Java 11: - - String testCase = String.join(System.getProperty("line.separator"), - "target C", - " ", - "reactor X {", - " input x:int;", - " output y:int;", - " reaction(x) -> y {=", - " =}", - "}", - "", - "main reactor {", - " a = new X()", - " b = new X()", - " a.y -> b.x after 0 sec", - " b.y -> a.x", - "}"); + String testCase = """ + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 0 sec + b.y -> a.x + } + """; validator.assertNoErrors(parseWithoutError(testCase)); } @@ -832,41 +576,23 @@ public void afterBreaksCycle2() throws Exception { */ @Test public void afterBreaksCycle3() throws Exception { -// Java 17: -// String testCase = """ -// target C -// -// reactor X { -// input x:int; -// output y:int; -// reaction(x) -> y {= -// =} -// } -// -// main reactor { -// a = new X() -// b = new X() -// a.y -> b.x after 0 -// b.y -> a.x -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C", - " ", - "reactor X {", - " input x:int;", - " output y:int;", - " reaction(x) -> y {=", - " =}", - "}", - "", - "main reactor {", - " a = new X()", - " b = new X()", - " a.y -> b.x after 0", - " b.y -> a.x", - "}"); + String testCase = """ + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 0 + b.y -> a.x + } + """; validator.assertNoErrors(parseWithoutError(testCase)); } @@ -875,311 +601,198 @@ public void afterBreaksCycle3() throws Exception { */ @Test public void nonzeroAfterMustHaveUnits() throws Exception { -// Java 17: -// String testCase = """ -// target C -// -// reactor X { -// input x:int; -// output y:int; -// reaction(x) -> y {= -// =} -// } -// -// main reactor { -// a = new X() -// b = new X() -// a.y -> b.x after 1 -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C", - " ", - "reactor X {", - " input x:int;", - " output y:int;", - " reaction(x) -> y {=", - " =}", - "}", - "", - "main reactor {", - " a = new X()", - " b = new X()", - " a.y -> b.x after 1", - "}"); + String testCase = """ + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 1 + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Missing time unit."); } - /** * Report non-zero time value without units. */ @Test public void nonZeroTimeValueWithoutUnits() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// main reactor { -// timer t(42, 1 sec); -// reaction(t) {= -// printf("Hello World.\\n"); -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " timer t(42, 1 sec);", - " reaction(t) {=", - " printf(\"Hello World.\\n\");", - " =}", - "}"); + String testCase = """ + target C; + main reactor { + timer t(42, 1 sec); + reaction(t) {= + printf("Hello World.\\n"); + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, "Missing time unit."); - } - + } + /** * Report reference to non-time parameter in time argument. */ @Test public void parameterTypeMismatch() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// main reactor (p:int(0)) { -// timer t(p, 1 sec); -// reaction(t) {= -// printf("Hello World.\\n"); -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor (p:int(0)) {", - " timer t(p, 1 sec);", - " reaction(t) {=", - " printf(\"Hello World.\\n\");", - " =}", - "}"); + String testCase = """ + target C; + main reactor (p:int(0)) { + timer t(p, 1 sec); + reaction(t) {= + printf("Hello World.\\n"); + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, "Referenced parameter is not of time type."); } - + /** * Report inappropriate literal in time argument. */ @Test public void targetCodeInTimeArgument() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// main reactor { -// timer t({=foo()=}, 1 sec); -// reaction(t) {= -// printf("Hello World.\\n"); -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " timer t({=foo()=}, 1 sec);", - " reaction(t) {=", - " printf(\"Hello World.\\n\");", - " =}", - "}"); + String testCase = """ + target C; + main reactor { + timer t({=foo()=}, 1 sec); + reaction(t) {= + printf("Hello World.\\n"); + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, "Invalid time value."); - } - + } + /** * Report overflowing deadline. */ @Test public void overflowingDeadlineC() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// main reactor { -// timer t; -// reaction(t) {= -// printf("Hello World.\\n"); -// =} deadline (40 hours) {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - "timer t;", - " reaction(t) {=", - " printf(\"Hello World.\\n\");", - " =} deadline (40 hours) {=", - " =}", - "}"); + String testCase = """ + target C; + main reactor { + timer t; + reaction(t) {= + printf("Hello World.\\n"); + =} deadline (40 hours) {= + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getDeadline(), null, "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + - " nanoseconds."); - } + " nanoseconds."); + } + - /** * Report overflowing parameter. */ @Test public void overflowingParameterC() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// main reactor(d:time(40 hours)) { -// timer t; -// reaction(t) {= -// printf("Hello World.\\n"); -// =} deadline (d) {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor(d:time(40 hours)) {", - "timer t;", - " reaction(t) {=", - " printf(\"Hello World.\\n\");", - " =} deadline (d) {=", - " =}", - "}"); + String testCase = """ + target C; + main reactor(d:time(40 hours)) { + timer t; + reaction(t) {= + printf("Hello World.\\n"); + =} deadline (d) {= + =} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getParameter(), null, "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); - } - - + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + } + + /** * Report overflowing assignment. */ @Test public void overflowingAssignmentC() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor Print(d:time(39 hours)) { -// timer t; -// reaction(t) {= -// printf("Hello World.\\n"); -// =} deadline (d) {= -// =} -// } -// main reactor { -// p = new Print(d=40 hours); -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor Print(d:time(39 hours)) {", - " timer t;", - " reaction(t) {=", - " printf(\"Hello World.\\n\");", - " =} deadline (d) {=", - " =}", - "}", - "main reactor {", - " p = new Print(d=40 hours);", - "}"); + String testCase = """ + target C; + reactor Print(d:time(39 hours)) { + timer t; + reaction(t) {= + printf("Hello World.\\n"); + =} deadline (d) {= + =} + } + main reactor { + p = new Print(d=40 hours); + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAssignment(), null, "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); - } + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + } /** * Report missing trigger. */ @Test public void missingTrigger() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor X { -// reaction() {= -// // -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor X {", - " reaction() {=", - " //", - " =}", - "}"); + String testCase = """ + target C; + reactor X { + reaction() {= + // + =} + } + """; validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getReaction(), null, "Reaction has no trigger."); } - + /** - * Test warnings and errors for the target dependent preamble visibility qualifiers + * Test warnings and errors for the target dependent preamble visibility qualifiers */ @Test public void testPreambleVisibility() throws Exception { for (Target target : Target.values()) { for (Visibility visibility : Visibility.values()) { -// Java 17: -// Model model_reactor_scope = """ -// target %s; -// reactor Foo { -// %spreamble {==} -// } -// """.formatted(target, visibility != java.beans.Visibility.NONE ? visibility + " " : ""); -// Java 11: - Model model_reactor_scope = parseWithoutError(String.join(System.getProperty("line.separator"), - String.format("target %s;", target), - "reactor Foo {", - String.format(" %spreamble {==}", visibility != Visibility.NONE ? visibility + " " : ""), - "}")); - -// Java 17: -// Model model_file_scope = """ -// target %s; -// %spreamble {==} -// reactor Foo { -// } -// """.formatted(target, visibility != java.beans.Visibility.NONE ? visibility + " " : ""); -// Java 11: - Model model_file_scope = parseWithoutError(String.join(System.getProperty("line.separator"), - String.format("target %s;", target), - String.format(" %spreamble {==}", visibility != Visibility.NONE ? visibility + " " : ""), - "reactor Foo {", - "}")); - -// Java 17: -// Model model_no_preamble = """ -// target %s; -// reactor Foo { -// } -// """.formatted(target); -// Java 11: - Model model_no_preamble = parseWithoutError(String.join(System.getProperty("line.separator"), - String.format("target %s;", target), - "reactor Foo {", - "}")); - + Model model_reactor_scope = parseWithoutError(""" + target %s; + reactor Foo { + %spreamble {==} + } + """.formatted(target, visibility != Visibility.NONE ? visibility + " " : "")); + + Model model_file_scope = parseWithoutError(""" + target %s; + %spreamble {==} + reactor Foo { + } + """.formatted(target, visibility != Visibility.NONE ? visibility + " " : "")); + + Model model_no_preamble = parseWithoutError(""" + target %s; + reactor Foo { + } + """.formatted(target)); + validator.assertNoIssues(model_no_preamble); - + if (target == Target.CPP) { if (visibility == Visibility.NONE) { validator.assertError(model_file_scope, LfPackage.eINSTANCE.getPreamble(), null, "Preambles for the C++ target need a visibility qualifier (private or public)!"); validator.assertError(model_reactor_scope, LfPackage.eINSTANCE.getPreamble(), null, - "Preambles for the C++ target need a visibility qualifier (private or public)!"); + "Preambles for the C++ target need a visibility qualifier (private or public)!"); } else { validator.assertNoIssues(model_file_scope); validator.assertNoIssues(model_reactor_scope); @@ -1198,46 +811,30 @@ public void testPreambleVisibility() throws Exception { } } } - - + + /** * Tests for state and parameter declarations, including native lists. */ @Test public void stateAndParameterDeclarationsInC() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// reactor Bar(a(0), // ERROR: type missing -// b:int, // ERROR: uninitialized -// t:time(42), // ERROR: units missing -// x:int(0), -// h:time("bla"), // ERROR: not a type -// q:time(1 msec, 2 msec), // ERROR: not a list -// y:int(t) // ERROR: init using parameter -// ) { -// state offset:time(42); // ERROR: units missing -// state w:time(x); // ERROR: parameter is not a time -// state foo:time("bla"); // ERROR: assigned value not a time; -// timer tick(1); // ERROR: not a time -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor Bar(a(0), // ERROR: type missing", - " b:int, // ERROR: uninitialized", - " t:time(42), // ERROR: units missing", - " x:int(0),", - " h:time(\"bla\"), // ERROR: not a type ", - " q:time(1 msec, 2 msec), // ERROR: not a list", - " y:int(t) // ERROR: init using parameter", - ") {", - " state offset:time(45); // ERROR: units missing", - " state w:time(x); // ERROR: parameter is not a time", - " state foo:time(\"bla\"); // ERROR: assigned value not a time", - " timer tick(1); // ERROR: not a time", - "}"); + String testCase = """ + target C; + reactor Bar(a(0), // ERROR: type missing + b:int, // ERROR: uninitialized + t:time = 42, // ERROR: units missing + x:int = 0, + h:time = "bla", // ERROR: not a type + q:time(1 msec, 2 msec), // ERROR: not a list + y:int = t // ERROR: init using parameter + ) { + state offset:time = 42; // ERROR: units missing + state w:time = x; // ERROR: parameter is not a time + state foo:time = "bla"; // ERROR: assigned value not a time; + timer tick(1); // ERROR: not a time + } + """; + Model model = parseWithoutError(testCase); @@ -1261,9 +858,9 @@ public void stateAndParameterDeclarationsInC() throws Exception { "Invalid time value."); validator.assertError(model, LfPackage.eINSTANCE.getTimer(), null, "Missing time unit."); - } - - + } + + /** * Recognize valid IPV4 addresses, report invalid ones. */ @@ -1275,69 +872,44 @@ public void recognizeIPV4() throws Exception { // Correct IP addresses. for (String addr : correct) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at foo@%s:4242 { -// y = new Y() at %s:2424; -// } -// """.formatted(addr, addr); -// Java 11: + + String testCase = """ + target C; + reactor Y {} + federated reactor X at foo@%s:4242 { + y = new Y() at %s:2424; + } + """.formatted(addr, addr); parseWithoutError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at foo@%s:4242 {", addr), - String.format(" y = new Y() at %s:2424; ", addr), - "}") + testCase ); } // IP addresses that don't parse. for (String addr : parseError) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at foo@%s:4242 { -// y = new Y() at %s:2424; -// } -// """.formatted(addr, addr); -// Java 11: - parseWithError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at foo@%s:4242 {", addr), - String.format(" y = new Y() at %s:2424; ", addr), - "}") - ); + String testCase = """ + target C; + reactor Y {} + federated reactor X at foo@%s:4242 { + y = new Y() at %s:2424; + } + """.formatted(addr, addr); + parseWithError(testCase); } // IP addresses that parse but are invalid. for (String addr : validationError) { -// Java 17: -// Model model = """ -// target C; -// reactor Y {} -// federated reactor X at foo@%s:4242 { -// y = new Y() at %s:2424; -// } -// """.formatted(addr, addr); -// Java 11: - Model model = parseWithoutError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at foo@%s:4242 {", addr), - String.format(" y = new Y() at %s:2424; ", addr), - "}") - ); + Model model = parseWithoutError(""" + target C; + reactor Y {} + federated reactor X at foo@%s:4242 { + y = new Y() at %s:2424; + } + """.formatted(addr, addr)); validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, "Invalid IP address."); } } - + /** * Recognize valid IPV6 addresses, report invalid ones. */ @@ -1355,160 +927,114 @@ public void recognizeIPV6() throws Exception { "::ffff:0.0.0.0", "::ffff:1.2.3.4", "::ffff:10.0.0.1", "1:2:3:4:5:6:77:88", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "2001:db8:3:4::192.0.2.33", "64:ff9b::192.0.2.33", "0:0:0:0:0:0:10.0.0.1"); - + List validationError = List.of("1:2:3:4:5:6:7:8:9", "1:2:3:4:5:6::7:8", "1:2:3:4:5:6:7:8:", "::1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8::", "1:2:3:4:5:6:7:88888", "2001:db8:3:4:5::192.0.2.33", "fe08::7:8interface", "fe08::7:8interface", "fe08::7:8i"); - + List parseError = List.of("fe08::7:8%", ":1:2:3:4:5:6:7:8"); - + // Correct IP addresses. for (String addr : correct) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at [foo@%s]:4242 { -// y = new Y() at [%s]:2424; -// } -// """.formatted(addr, addr); -// Java 11: - Model model = parseWithoutError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor at [foo@%s]:4242 {", addr), - String.format(" y = new Y() at [%s]:2424; ", addr), - "}") - ); + String testCase = """ + target C; + reactor Y {} + federated reactor at [foo@%s]:4242 { + y = new Y() at [%s]:2424; + } + """.formatted(addr, addr); + Model model = parseWithoutError(testCase); validator.assertNoIssues(model); } - + // IP addresses that don't parse. for (String addr : parseError) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at [foo@%s]:4242 { -// y = new Y() at [%s]:2424; -// } -// """.formatted(addr, addr); -// Java 11: - parseWithError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at [foo@%s]:4242 {", addr), - String.format(" y = new Y() at [%s]:2424; ", addr), - "}") - ); + String testCase = """ + target C; + reactor Y {} + federated reactor X at [foo@%s]:4242 { + y = new Y() at [%s]:2424; + } + """.formatted(addr, addr); + parseWithError(testCase); } // IP addresses that parse but are invalid. for (String addr : validationError) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at [foo@%s]:4242 { -// y = new Y() at [%s]:2424; -// } -// """.formatted(addr, addr); -// Java 11: + String testCase = """ + target C; + reactor Y {} + federated reactor X at [foo@%s]:4242 { + y = new Y() at [%s]:2424; + } + """.formatted(addr, addr); Model model = parseWithoutError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at [foo@%s]:4242 {", addr), - String.format(" y = new Y() at [%s]:2424; ", addr), - "}") + testCase ); validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, "Invalid IP address."); } } - + /** * Recognize valid host names and fully qualified names, report invalid ones. */ @Test public void recognizeHostNames() throws Exception { - + List correct = List.of("localhost"); // FIXME: add more - + List validationError = List.of("x.y.z"); // FIXME: add more - + List parseError = List.of("..xyz"); // FIXME: add more // Correct names. for (String addr : correct) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at foo@%s:4242 { -// y = new Y() at %s:2424; -// } -// """.formatted(addr, addr); -// Java 11: + String testCase = """ + target C; + reactor Y {} + federated reactor X at foo@%s:4242 { + y = new Y() at %s:2424; + } + """.formatted(addr, addr); parseWithoutError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at foo@%s:4242 {", addr), - String.format(" y = new Y() at %s:2424; ", addr), - "}") + testCase ); } - + // Names that don't parse. for (String addr : parseError) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at foo@%s:4242 { -// y = new Y() at %s:2424; -// } -// """.formatted(addr, addr); -// Java 11: + String testCase = """ + target C; + reactor Y {} + federated reactor X at foo@%s:4242 { + y = new Y() at %s:2424; + } + """.formatted(addr, addr); parseWithError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at foo@%s:4242 {", addr), - String.format(" y = new Y() at %s:2424; ", addr), - "}") + testCase ); } - + // Names that parse but are invalid. for (String addr : validationError) { -// Java 17: -// String testCase = """ -// target C; -// reactor Y {} -// federated reactor X at foo@%s:4242 { -// y = new Y() at %s:2424; -// } -// """.formatted(addr, addr); -// Java 11: + String testCase = """ + target C; + reactor Y {} + federated reactor X at foo@%s:4242 { + y = new Y() at %s:2424; + } + """.formatted(addr, addr); Model model = parseWithoutError( - String.join(System.getProperty("line.separator"), - "target C;", - "reactor Y {}", - String.format("federated reactor X at foo@%s:4242 {", addr), - String.format(" y = new Y() at %s:2424; ", addr), - "}") + testCase ); - validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, + validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, "Invalid host name or fully qualified domain name."); } } - + /** * Maps a type to a list of known good values. */ @@ -1533,7 +1059,7 @@ public void recognizeHostNames() throws Exception { ); /** - * Maps a type to a list, each entry of which represents a list with + * Maps a type to a list, each entry of which represents a list with * three entries: a known wrong value, the suffix to add to the reported * name, and the type that it should be. */ @@ -1575,13 +1101,14 @@ public void recognizeHostNames() throws Exception { List.of("{trace-file-name: [1, 2, 3]}", ".trace-file-name", PrimitiveType.STRING) ) ); - + /** - * Given an array type, return a list of good or bad examples, + * Given an array type, return a list of good or bad examples, * depending on the value of the second parameter. */ private List synthesizeExamples(ArrayType type, boolean correct) { - Map> values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad; + Map> values = correct ? primitiveTypeToKnownGood + : primitiveTypeToKnownBad; List examples = new LinkedList<>(); if (correct) { // Produce an array that has an entry for each value. @@ -1592,9 +1119,9 @@ private List synthesizeExamples(ArrayType type, boolean correct) { } return examples; } - + /** - * Given an union type, return a list of good or bad examples, + * Given an union type, return a list of good or bad examples, * depending on the value of the second parameter. */ private List synthesizeExamples(UnionType type, boolean correct) { @@ -1610,15 +1137,15 @@ private List synthesizeExamples(UnionType type, boolean correct) { } else { // Return some obviously bad examples for the common // case where the options are from an ordinary Enum. - if (!type.options.stream().anyMatch(it -> (it instanceof TargetPropertyType))) { + if (!type.options.stream().anyMatch(it -> it instanceof TargetPropertyType)) { return List.of("foo", "\"bar\"", "1", "-1", "{x: 42}", "[1, 2, 3]"); } } return examples; } - + /** - * Given an union type, return a list of good or bad examples, + * Given an union type, return a list of good or bad examples, * depending on the value of the second parameter. */ private List synthesizeExamples(DictionaryType type, boolean correct) { @@ -1626,7 +1153,8 @@ private List synthesizeExamples(DictionaryType type, boolean correct) { // Produce a set of singleton dictionaries. // If incorrect examples are wanted, garble the key. for (DictionaryElement option : type.options) { - synthesizeExamples(option.getType(), correct).forEach(it -> examples.add("{" + option + (!correct ? "iamwrong: " : ": ") + it + "}")); + synthesizeExamples(option.getType(), correct).forEach(it -> examples.add( + "{" + option + (!correct ? "iamwrong: " : ": ") + it + "}")); } return examples; } @@ -1649,23 +1177,24 @@ private List synthesizeExamples(StringDictionaryType type, boolean corre } return examples; } - + /** * Synthesize a list of values that either conform to the given type or * do not, depending on whether the second argument 'correct' is true. - * Return an empty set if it is too complicated to generate examples + * Return an empty set if it is too complicated to generate examples * (e.g., because the resulting errors are more sophisticated). - * + *

* Not all cases are covered by this function. Currently, the only cases not * covered are known bad examples for composite types, which should be added * to the compositeTypeToKnownBad map. - * + * * @param correct True to synthesize correct examples automatically, otherwise - * synthesize incorrect examples. + * synthesize incorrect examples. */ private List synthesizeExamples(TargetPropertyType type, boolean correct) { if (type instanceof PrimitiveType) { - Map> values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad; + Map> values = correct ? primitiveTypeToKnownGood + : primitiveTypeToKnownBad; List examples = values.get(type); Assertions.assertNotNull(examples); return examples; @@ -1684,29 +1213,21 @@ private List synthesizeExamples(TargetPropertyType type, boolean correct } return new LinkedList<>(); } - + /** * Create an LF program with the given key and value as a target property, * parse it, and return the resulting model. */ private Model createModel(TargetProperty key, String value) throws Exception { String target = key.supportedBy.get(0).getDisplayName(); - System.out.println(String.format("%s: %s", key, value)); -// Java 17: -// Model model = """ -// target %s {%s: %s}; -// reactor Y {} -// main reactor { -// y = new Y() -// } -// """.formatted(target, key, value); -// Java 11: - return parseWithoutError(String.join(System.getProperty("line.separator"), - String.format("target %s {%s: %s};", target, key, value), - "reactor Y {}", - "main reactor {", - " y = new Y() ", - "}")); + System.out.printf("%s: %s%n", key, value); + return parseWithoutError(""" + target %s {%s: %s}; + reactor Y {} + main reactor { + y = new Y() + } + """.formatted(target, key, value)); } /** @@ -1719,21 +1240,21 @@ public void checkTargetProperties() throws Exception { // we test that separately as it has better error messages return; } - System.out.println(String.format("Testing target property %s which is %s", prop, prop.type)); + System.out.printf("Testing target property %s which is %s%n", prop, prop.type); System.out.println("===="); System.out.println("Known good assignments:"); List knownCorrect = synthesizeExamples(prop.type, true); - + for (String it : knownCorrect) { Model model = createModel(prop, it); validator.assertNoErrors(model); // Also make sure warnings are produced when files are not present. if (prop.type == PrimitiveType.FILE) { validator.assertWarning(model, LfPackage.eINSTANCE.getKeyValuePair(), - null, String.format("Could not find file: '%s'.", StringUtil.removeQuotes(it))); + null, String.format("Could not find file: '%s'.", StringUtil.removeQuotes(it))); } } - + // Extra checks for filenames. (This piece of code was commented out in the original xtend file) // Temporarily disabled because we need a more sophisticated check that looks for files in different places. // if (prop.type == prop.type == ArrayType.FILE_ARRAY || @@ -1746,35 +1267,35 @@ public void checkTargetProperties() throws Exception { // null, '''Could not find file: '«it.withoutQuotes»'.''') // ] // } - + System.out.println("Known bad assignments:"); List knownIncorrect = synthesizeExamples(prop.type, false); if (!(knownIncorrect == null || knownIncorrect.isEmpty())) { for (String it : knownIncorrect) { if (prop.type instanceof StringDictionaryType) { validator.assertError(createModel(prop, it), - LfPackage.eINSTANCE.getKeyValuePair(), null, - String.format("Target property '%s.", prop), "' is required to be a string."); + LfPackage.eINSTANCE.getKeyValuePair(), null, + String.format("Target property '%s.", prop), "' is required to be a string."); } else { validator.assertError(createModel(prop, it), - LfPackage.eINSTANCE.getKeyValuePair(), null, - String.format("Target property '%s' is required to be %s.", prop.toString(), prop.type)); + LfPackage.eINSTANCE.getKeyValuePair(), null, + String.format("Target property '%s' is required to be %s.", prop, prop.type)); } } } else { // No type was synthesized. It must be a composite type. List> list = compositeTypeToKnownBad.get(prop.type); if (list == null) { - System.out.println(String.format("No known incorrect values provided for target property '%s'. Aborting test.", prop)); - Assertions.assertTrue(false); + System.out.printf("No known incorrect values provided for target property '%s'. Aborting test.%n", prop); + Assertions.fail(); } else { for (List it : list) { validator.assertError(createModel(prop, it.get(0).toString()), LfPackage.eINSTANCE.getKeyValuePair(), null, - String.format("Target property '%s%s' is required to be %s.", prop.toString(), it.get(1), it.get(2))); + String.format("Target property '%s%s' is required to be %s.", prop, it.get(1), it.get(2))); } } - } + } System.out.println("===="); } System.out.println("Done!"); @@ -1793,7 +1314,7 @@ public void checkCargoDependencyProperty() throws Exception { validator.assertError(createModel(prop, "{ dep: {/*empty*/} }"), LfPackage.eINSTANCE.getKeyValuePairs(), null, "Must specify one of 'version', 'path', or 'git'" ); - + // vvvvvvvvvvv validator.assertError(createModel(prop, "{ dep: { unknown_key: \"\"} }"), LfPackage.eINSTANCE.getKeyValuePair(), null, "Unknown key: 'unknown_key'" @@ -1884,358 +1405,228 @@ public void testUnusedImport() throws Exception { @Test public void testMissingInputType() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor { - // input i; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " input i;", - "}" - ); + String testCase = """ + target C; + main reactor { + input i; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "Input must have a type."); } @Test public void testMissingOutputType() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor { - // output i; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " output i;", - "}" - ); + String testCase = """ + target C; + main reactor { + output i; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, "Output must have a type."); } @Test public void testMissingStateType() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor { - // state i; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " state i;", - "}" - ); + String testCase = """ + target C; + main reactor { + state i; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, "State must have a type."); } @Test public void testListWithParam() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor (A:int(1)) { state i:int(A, 2, 3) } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor (A:int(1)) { state i:int(A, 2, 3) }" - ); + String testCase = """ + target C; + main reactor (A:int(1)) { state i:int(A, 2, 3) } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, "List items cannot refer to a parameter."); } @Test public void testCppMutableInput() throws Exception { - // Java 17: - // String testCase = """ - // target Cpp; - // main reactor { - // mutable input i:int; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target Cpp;", - "main reactor {", - " mutable input i:int;", - "}" - ); + String testCase = """ + target Cpp; + main reactor { + mutable input i:int; + } + """; validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "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()."); + "In C++, any value can be made mutable by calling get_mutable_copy()."); } @Test public void testOverflowingSTP() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor { - // reaction(startup) {==} STP(2147483648) {==} - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " reaction(startup) {==} STP(2147483648) {==}", - "}" - ); + String testCase = """ + target C; + main reactor { + reaction(startup) {==} STP(2147483648) {==} + } + """; // TODO: Uncomment and fix failing test. See issue #903 on Github. // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getSTP(), null, - // "STP offset exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + // "STP offset exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); } @Test public void testOverflowingDeadline() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor { - // reaction(startup) {==} STP(2147483648) {==} - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " reaction(startup) {==} deadline(2147483648) {==}", - "}" - ); + String testCase = """ + target C; + main reactor { + reaction(startup) {==} STP(2147483648) {==} + } + """; // TODO: Uncomment and fix failing test. See issue #903 on Github. // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getDeadline(), null, - // "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + // "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); } @Test public void testInvalidTargetParam() throws Exception { - // Java 17: - // String testCase = """ - // target C { beefyDesktop: true } - // main reactor {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C { beefyDesktop: true }", - "main reactor {}" - ); + String testCase = """ + target C { beefyDesktop: true } + main reactor {} + """; List issues = validator.validate(parseWithoutError(testCase)); - Assertions.assertTrue(issues.size() == 1 && issues.get(0).getMessage().contains("Unrecognized target parameter: beefyDesktop")); + Assertions.assertTrue(issues.size() == 1 + && issues.get(0).getMessage().contains("Unrecognized target parameter: beefyDesktop")); } @Test public void testTargetParamNotSupportedForTarget() throws Exception { - // Java 17: - // String testCase = """ - // target Python { build: "" } - // main reactor {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target Python { build: \"\" }", - "main reactor {}" - ); + String testCase = """ + target Python { build: "" } + main reactor {} + """; List issues = validator.validate(parseWithoutError(testCase)); - Assertions.assertTrue(issues.size() == 1 && issues.get(0).getMessage().contains("The target parameter: build" + - " is not supported by the Python target and will thus be ignored.")); + Assertions.assertTrue(issues.size() == 1 && issues.get(0).getMessage().contains( + "The target parameter: build" + + " is not supported by the Python target and will thus be ignored.")); } @Test public void testUnnamedReactor() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // reactor {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "reactor {}" - ); + String testCase = """ + target C; + reactor {} + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Reactor must be named."); } @Test public void testMainHasInput() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor { - // input x:int; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " input x:int;", - "}" - ); + String testCase = """ + target C; + main reactor { + input x:int; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "Main reactor cannot have inputs."); } @Test public void testFederatedHasInput() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // federated reactor { - // input x:int; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "federated reactor {", - " input x:int;", - "}" - ); + + String testCase = """ + target C; + federated reactor { + input x:int; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "Main reactor cannot have inputs."); } @Test public void testMainHasOutput() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor { - // output x:int; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " output x:int;", - "}" - ); + + String testCase = """ + target C; + main reactor { + output x:int; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, "Main reactor cannot have outputs."); } @Test public void testFederatedHasOutput() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // federated reactor { - // output x:int; - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "federated reactor {", - " output x:int;", - "}" - ); + + String testCase = """ + target C; + federated reactor { + output x:int; + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, "Main reactor cannot have outputs."); } @Test public void testMultipleMainReactor() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor A {} - // main reactor A {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor A {}", - "main reactor A {}" - ); + + String testCase = """ + target C; + main reactor A {} + main reactor A {} + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Multiple definitions of main or federated reactor."); } @Test public void testMultipleMainReactorUnnamed() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor {} - // main reactor {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {}", - "main reactor {}" - ); + + String testCase = """ + target C; + main reactor {} + main reactor {} + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Multiple definitions of main or federated reactor."); } @Test public void testMultipleFederatedReactor() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // federated reactor A {} - // federated reactor A {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "federated reactor A {}", - "federated reactor A {}" - ); + String testCase = """ + target C; + federated reactor A {} + federated reactor A {} + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Multiple definitions of main or federated reactor."); } @Test public void testMultipleMainOrFederatedReactor() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // federated reactor A {} - // federated reactor A {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor A {}", - "federated reactor A {}" - ); + + String testCase = """ + target C; + federated reactor A {} + federated reactor A {} + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Multiple definitions of main or federated reactor."); } @Test public void testMainReactorHasHost() throws Exception { - // Java 17: - // String testCase = """ - // target C; - // main reactor at 127.0.0.1{} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor at 127.0.0.1{}" - ); + String testCase = """ + target C; + main reactor at 127.0.0.1{} + """; // TODO: Uncomment and fix test // List issues = validator.validate(parseWithoutError(testCase)); // Assertions.assertTrue(issues.size() == 1 && @@ -2245,16 +1636,11 @@ public void testMainReactorHasHost() throws Exception { @Test public void testUnrecognizedTarget() throws Exception { - // Java 17: - // String testCase = """ - // target Pjthon; - // main reactor {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target Pjthon;", - "main reactor {}" - ); + + String testCase = """ + target Pjthon; + main reactor {} + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTargetDecl(), null, "Unrecognized target: Pjthon"); } @@ -2296,132 +1682,132 @@ public void testWrongParamType() throws Exception { @Test public void testInitialMode() throws Exception { String testCase = """ - target C; - main reactor { - mode M {} - } - """; + target C; + main reactor { + mode M {} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, - "Every modal reactor requires one initial mode."); + "Every modal reactor requires one initial mode."); } @Test public void testInitialModes() throws Exception { String testCase = """ - target C; - main reactor { - initial mode IM1 {} - initial mode IM2 {} - } - """; + target C; + main reactor { + initial mode IM1 {} + initial mode IM2 {} + } + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, - "A modal reactor can only have one initial mode."); + "A modal reactor can only have one initial mode."); } @Test public void testModeStateNamespace() throws Exception { String testCase = """ - target C; - main reactor { - initial mode IM { - state s:int; - } - mode M { - state s:int; + target C; + main reactor { + initial mode IM { + state s:int; + } + mode M { + state s:int; + } } - } - """; + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, - "Duplicate state variable 's'. (State variables are currently scoped on reactor level not modes)"); + "Duplicate state variable 's'. (State variables are currently scoped on reactor level not modes)"); } @Test public void testModeTimerNamespace() throws Exception { String testCase = """ - target C; - main reactor { - initial mode IM { - timer t; - } - mode M { - timer t; + target C; + main reactor { + initial mode IM { + timer t; + } + mode M { + timer t; + } } - } - """; + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, - "Duplicate Timer 't'. (Timers are currently scoped on reactor level not modes)"); + "Duplicate Timer 't'. (Timers are currently scoped on reactor level not modes)"); } @Test public void testModeActionNamespace() throws Exception { String testCase = """ - target C; - main reactor { - initial mode IM { - logical action a; - } - mode M { - logical action a; + target C; + main reactor { + initial mode IM { + logical action a; + } + mode M { + logical action a; + } } - } - """; + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAction(), null, - "Duplicate Action 'a'. (Actions are currently scoped on reactor level not modes)"); + "Duplicate Action 'a'. (Actions are currently scoped on reactor level not modes)"); } @Test public void testModeInstanceNamespace() throws Exception { String testCase = """ - target C; - reactor R {} - main reactor { - initial mode IM { - r = new R(); - } - mode M { - r = new R(); + target C; + reactor R {} + main reactor { + initial mode IM { + r = new R(); + } + mode M { + r = new R(); + } } - } - """; + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInstantiation(), null, - "Duplicate Instantiation 'r'. (Instantiations are currently scoped on reactor level not modes)"); + "Duplicate Instantiation 'r'. (Instantiations are currently scoped on reactor level not modes)"); } @Test public void testMissingModeStateReset() throws Exception { String testCase = """ - target C; - main reactor { - initial mode IM { - reaction(startup) -> M {==} - } - mode M { - state s:int(0); + target C; + main reactor { + initial mode IM { + reaction(startup) -> M {==} + } + mode M { + state s:int(0); + } } - } - """; + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getMode(), null, - "State variable is not reset upon mode entry. It is neither marked for automatic reset nor is there a reset reaction."); + "State variable is not reset upon mode entry. It is neither marked for automatic reset nor is there a reset reaction."); } @Test public void testMissingModeStateResetInstance() throws Exception { String testCase = """ - target C; - reactor R { - state s:int(0); - } - main reactor { - initial mode IM { - reaction(startup) -> M {==} + target C; + reactor R { + state s:int(0); } - mode M { - r = new R(); + main reactor { + initial mode IM { + reaction(startup) -> M {==} + } + mode M { + r = new R(); + } } - } - """; + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getMode(), null, - "This reactor contains state variables that are not reset upon mode entry: " + "This reactor contains state variables that are not reset upon mode entry: " + "s in R" + ".\nThe state variables are neither marked for automatic reset nor have a dedicated reset reaction. " + "It is usafe to instatiate this reactor inside a mode entered with reset."); @@ -2430,13 +1816,13 @@ public void testMissingModeStateResetInstance() throws Exception { @Test public void testModeStateResetWithoutInitialValue() throws Exception { String testCase = """ - target C; - main reactor { - initial mode IM { - reset state s:int; + target C; + main reactor { + initial mode IM { + reset state s:int; + } } - } - """; + """; validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, "The state variable can not be automatically reset without an initial value."); } @@ -2444,18 +1830,18 @@ public void testModeStateResetWithoutInitialValue() throws Exception { @Test public void testUnspecifiedTransitionType() throws Exception { String testCase = """ - target C; - main reactor { - initial mode IM { - reaction(startup) -> M {==} - } - mode M { - reset state s:int(0); + target C; + main reactor { + initial mode IM { + reaction(startup) -> M {==} + } + mode M { + reset state s:int(0); + } } - } - """; + """; validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getReaction(), null, - "You should specifiy a transition type! " + "You should specifiy a transition type! " + "Reset and history transitions have different effects on this target mode. " + "Currently, a reset type is implicitly assumed."); } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java index d1ea81e5be..24cb3554ff 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java @@ -3,6 +3,7 @@ import static java.util.Collections.emptyList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; import java.nio.file.Files; import java.nio.file.Path; @@ -28,11 +29,15 @@ public class RoundTripTests { @Test - public void roundTripTest() throws Exception { + public void roundTripTest() { for (Target target : Target.values()) { for (TestCategory category : TestCategory.values()) { for (LFTest test : TestRegistry.getRegisteredTests(target, category, false)) { - run(test.getSrcPath()); + try { + run(test.getSrcPath()); + } catch (Throwable thrown) { + fail("Test case " + test.getSrcPath() + " failed", thrown); + } } } } diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index fc49242322..b4c63ac337 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -254,6 +254,14 @@ public static boolean changeTargetName(Resource resource, String 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. * diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 870b1ad049..2525f2f710 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -140,7 +140,7 @@ TargetDecl: Initializer: parens?='(' (exprs+=Expression (',' exprs+=Expression)* (trailingComma?=',')?)? ')' | braces?='{' (exprs+=Expression (',' exprs+=Expression)* (trailingComma?=',')?)? '}' -// | assign?='=' exprs+=Expression // todo later + | assign?='=' exprs+=Expression ; @@ -292,13 +292,15 @@ VarRefOrModeTransition returns VarRef: Assignment: lhs=[Parameter] - (equals='=')? rhs=AssignmentInitializer + rhs=AssignmentInitializer ; -// TODO Only allow = based parameter assignment AssignmentInitializer returns Initializer: - exprs+=Expression - | parens?='(' (exprs+=Expression (',' exprs+=Expression)* (trailingComma?=',')?)? ')' + assign?='=' exprs+=Expression + // these syntaxes are legacy and now deprecated + // todo remove in future version. + | '='? parens?='(' (exprs+=Expression (',' exprs+=Expression)* (trailingComma?=',')?)? ')' + // note: the previous syntax `= { expr* }` is now parsed as a BracedListExpression | braces?='{' (exprs+=Expression (',' exprs+=Expression)* (trailingComma?=',')?)? '}' ; @@ -317,6 +319,13 @@ Expression: | Time | ParameterReference | {CodeExpr} code=Code + | BracedListExpression +; + +// A list of expressions within braces. +// In C/C++, this is an array initializer, struct initializer, or std::initializer_list. +BracedListExpression: + '{' {BracedListExpression} (items+=Expression (',' items+=Expression)*)? ','? '}' ; ParameterReference: diff --git a/org.lflang/src/org/lflang/Target.java b/org.lflang/src/org/lflang/Target.java index 4842bc1672..11247b6648 100644 --- a/org.lflang/src/org/lflang/Target.java +++ b/org.lflang/src/org/lflang/Target.java @@ -486,9 +486,7 @@ public boolean supportsMultiports() { * on constants). */ public boolean supportsParameterizedWidths() { - return switch (this) { - case C, CCPP, CPP, Python, Rust, TS -> true; - }; + return true; } /** @@ -502,6 +500,20 @@ public boolean buildsUsingDocker() { }; } + /** + * Whether the target requires using an equal sign to assign a default value to a parameter, + * or initialize a state variable. All targets mandate an equal sign when passing + * arguments to a reactor constructor call, regardless of this method. + */ + public boolean mandatesEqualsInitializers() { + return this != CPP; + } + + /** Allow expressions of the form {@code {a, b, c}}. */ + public boolean allowsBracedListExpressions() { + return this == C || this == CCPP || this == CPP; + } + /** * Return a string that demarcates the beginning of a single-line comment. */ diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 93c2074829..e18eea02bd 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -16,6 +16,7 @@ import org.lflang.lf.Assignment; import org.lflang.lf.AttrParm; import org.lflang.lf.Attribute; +import org.lflang.lf.BracedListExpression; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Code; import org.lflang.lf.CodeExpr; @@ -416,7 +417,6 @@ public Boolean caseVarRef(VarRef object) { public Boolean caseAssignment(Assignment object) { return new ComparisonMachine<>(object, Assignment.class) .equivalent(Assignment::getLhs) - .equalAsObjects(Assignment::getEquals) .equivalent(Assignment::getRhs) .conclusion; } @@ -438,10 +438,18 @@ public Boolean caseExpression(Expression object) { Literal.class, Time.class, ParameterReference.class, - Code.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) diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 50722d19eb..be8a68cf7d 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -21,6 +21,7 @@ import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.xbase.lib.StringExtensions; import org.lflang.ASTUtils; +import org.lflang.Target; import org.lflang.ast.MalleableString.Builder; import org.lflang.ast.MalleableString.Joiner; import org.lflang.lf.Action; @@ -29,6 +30,7 @@ import org.lflang.lf.Assignment; import org.lflang.lf.AttrParm; import org.lflang.lf.Attribute; +import org.lflang.lf.BracedListExpression; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Code; import org.lflang.lf.CodeExpr; @@ -46,6 +48,7 @@ import org.lflang.lf.Instantiation; import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; +import org.lflang.lf.LfFactory; import org.lflang.lf.Literal; import org.lflang.lf.Method; import org.lflang.lf.MethodArgument; @@ -479,7 +482,7 @@ public MalleableString caseStateVar(StateVar object) { } msb.append("state ").append(object.getName()); msb.append(typeAnnotationFor(object.getType())); - msb.append(initializer(object.getInit(), true)); + msb.append(initializer(object.getInit())); return msb.get(); } @@ -794,7 +797,9 @@ public MalleableString caseSerializer(Serializer object) { @Override public MalleableString caseKeyValuePairs(KeyValuePairs object) { // {KeyValuePairs} '{' (pairs+=KeyValuePair (',' (pairs+=KeyValuePair))* ','?)? '}' - if (object.getPairs().isEmpty()) return MalleableString.anyOf(""); + if (object.getPairs().isEmpty()) { + return MalleableString.anyOf(""); + } return new Builder() .append("{\n") .append(list(",\n", "", "\n", true, true, object.getPairs()).indent()) @@ -802,6 +807,16 @@ public MalleableString caseKeyValuePairs(KeyValuePairs object) { .get(); } + @Override + public MalleableString caseBracedListExpression(BracedListExpression object) { + if (object.getItems().isEmpty()) { + return MalleableString.anyOf("{}"); + } + // Note that this strips the trailing comma. There is no way + // to implement trailing commas with the current set of list() methods AFAIU. + return list(", ", "{", "}", false, false, object.getItems()); + } + @Override public MalleableString caseKeyValuePair(KeyValuePair object) { // name=Kebab ':' value=Element @@ -856,35 +871,52 @@ public MalleableString caseAssignment(Assignment object) { // )); Builder msb = new Builder(); msb.append(object.getLhs().getName()); - if (object.getEquals() != null) { - msb.append(" = "); - } - msb.append(initializer(object.getRhs(), false)); + msb.append(initializer(object.getRhs())); return msb.get(); } @Override public MalleableString caseInitializer(Initializer object) { - return initializer(object, false); + return initializer(object); } - private MalleableString initializer(Initializer init, boolean nothingIfEmpty) { + /** + * 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() + || init.getExprs().size() == 1 && ASTUtils.getTarget(init).mandatesEqualsInitializers(); + } + + private MalleableString initializer(Initializer init) { if (init == null) { return MalleableString.anyOf(""); } + if (shouldOutputAsAssignment(init)) { + Expression expr = ASTUtils.asSingleExpr(init); + Objects.requireNonNull(expr); + return new Builder().append(" = ").append(doSwitch(expr)).get(); + } + if (ASTUtils.getTarget(init) == Target.C) { + // This turns C array initializers into a braced expression. + // C++ variants are not converted. + BracedListExpression list = LfFactory.eINSTANCE.createBracedListExpression(); + list.getItems().addAll(init.getExprs()); + return new Builder().append(" = ").append(doSwitch(list)).get(); + } String prefix; String suffix; if (init.isBraces()) { prefix = "{"; suffix = "}"; - } else if (init.isParens()) { + } else { + assert init.isParens(); prefix = "("; suffix = ")"; - } else { - // unparenthesized parameter assignment. - prefix = suffix = ""; } - return list(", ", prefix, suffix, nothingIfEmpty, false, init.getExprs()); + return list(", ", prefix, suffix, false, false, init.getExprs()); } @@ -899,7 +931,7 @@ public MalleableString caseParameter(Parameter object) { return builder .append(object.getName()) .append(typeAnnotationFor(object.getType())) - .append(initializer(object.getInit(), true)) + .append(initializer(object.getInit())) .get(); } diff --git a/org.lflang/src/org/lflang/ast/ToText.java b/org.lflang/src/org/lflang/ast/ToText.java index ad9a40c95b..0b703ccbe8 100644 --- a/org.lflang/src/org/lflang/ast/ToText.java +++ b/org.lflang/src/org/lflang/ast/ToText.java @@ -10,6 +10,7 @@ import org.lflang.ASTUtils; import org.lflang.lf.ArraySpec; +import org.lflang.lf.BracedListExpression; import org.lflang.lf.Code; import org.lflang.lf.CodeExpr; import org.lflang.lf.Host; @@ -78,6 +79,11 @@ public String caseCode(Code code) { return ""; } + @Override + public String caseBracedListExpression(BracedListExpression object) { + return ToLf.instance.caseBracedListExpression(object).toString(); + } + @Override public String caseHost(Host host) { return ToLf.instance.caseHost(host).toString(); diff --git a/org.lflang/src/org/lflang/cli/Lff.java b/org.lflang/src/org/lflang/cli/Lff.java index 0eeeea6785..1efc207128 100644 --- a/org.lflang/src/org/lflang/cli/Lff.java +++ b/org.lflang/src/org/lflang/cli/Lff.java @@ -51,7 +51,7 @@ public class Lff extends CliBase { @Option( names = "--no-recurse", description = "Do not format files in subdirectories of the" - + " specified paths.") + + " specified paths.") private boolean noRecurse = false; @Option( @@ -59,6 +59,11 @@ public class Lff extends CliBase { description = "Print more details on files affected.") private boolean verbose = false; + @Option( + names = {"--ignore-errors"}, + description = "Ignore validation errors in files and format them anyway.") + private boolean ignoreErrors = false; + /** * Main function of the formatter. * Caution: this will invoke System.exit. @@ -147,12 +152,13 @@ private void formatSingleFile(Path path, Path inputRoot, Path outputRoot) { if (verbose) { reporter.printInfo("Skipped " + path + ": not an LF file"); } - return; + return; } validateResource(resource); - // todo don't abort whole run if one file has errors - exitIfCollectedErrors(); + if (!ignoreErrors) { + exitIfCollectedErrors(); + } final String formattedFileContents = FormattingUtils.render(resource.getContents().get(0), lineLength); @@ -163,24 +169,30 @@ private void formatSingleFile(Path path, Path inputRoot, Path outputRoot) { FileUtil.writeToFile(formattedFileContents, outputPath, true); } catch (IOException e) { if (e instanceof FileAlreadyExistsException) { - // Only happens if a subdirectory is named with + // Only happens if a subdirectory is named with // ".lf" at the end. reporter.printFatalErrorAndExit( "Error writing to " + outputPath - + ": file already exists. Make sure that no file or" + + ": file already exists. Make sure that no file or" + " directory within provided input paths have the" + " same relative paths."); } } } - exitIfCollectedErrors(); - issueCollector.getAllIssues().forEach(reporter::printIssue); + if (!ignoreErrors) { + exitIfCollectedErrors(); + } + // Only errors are printed. Warnings are not helpful for LFF + // and since they don't prevent the file from being formatted, + // the position of the issue may be wrong in the formatted file. + // issueCollector.getAllIssues().forEach(reporter::printIssue); if (verbose) { String msg = "Formatted " + io.getWd().relativize(path); - if (path != outputPath) msg += - " -> " + io.getWd().relativize(outputPath); + if (path != outputPath) { + msg += " -> " + io.getWd().relativize(outputPath); + } reporter.printInfo(msg); } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index cd013d6c57..1497ee6644 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -66,6 +66,7 @@ import org.lflang.AttributeUtils; import org.lflang.InferredType; import org.lflang.ast.FormattingUtils; +import org.lflang.ast.ToText; import org.lflang.diagram.synthesis.action.CollapseAllReactorsAction; import org.lflang.diagram.synthesis.action.ExpandAllReactorsAction; import org.lflang.diagram.synthesis.action.FilterCycleAction; @@ -1187,12 +1188,12 @@ private String createParameterLabel(ParameterInstance param) { b.append(param.getName()); String t = param.type.toOriginalText(); if (!StringExtensions.isNullOrEmpty(t)) { - b.append(":").append(t); + b.append(": ").append(t); } - if (!IterableExtensions.isNullOrEmpty(param.getInitialValue())) { - b.append("("); - b.append(IterableExtensions.join(param.getInitialValue(), ", ", ASTUtils::toOriginalText)); - b.append(")"); + if (param.getOverride() != null) { + b.append(" = "); + var init = param.getActualValue(); + b.append(FormattingUtils.render(init)); } return b.toString(); } diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index fda727d735..a2ac48216f 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -48,19 +48,16 @@ import org.lflang.federated.launcher.RtiConfig; import org.lflang.federated.serialization.FedROS2CPPSerialization; import org.lflang.generator.CodeBuilder; -import org.lflang.generator.GeneratorBase; import org.lflang.generator.GeneratorUtils; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; -import org.lflang.generator.c.CGenerator; import org.lflang.generator.c.CTypes; import org.lflang.generator.c.CUtil; import org.lflang.lf.Action; import org.lflang.lf.Output; import org.lflang.lf.Port; import org.lflang.lf.VarRef; -import org.lflang.util.FileUtil; /** * An extension class to the CGenerator that enables certain federated @@ -170,7 +167,7 @@ protected void deserialize( CodeBuilder result, ErrorReporter errorReporter ) { - CTypes types = new CTypes(errorReporter); + CTypes types = new CTypes(); // Adjust the type of the action and the receivingPort. // If it is "string", then change it to "char*". // This string is dynamically allocated, and type 'string' is to be @@ -338,7 +335,7 @@ protected void serializeAndSend( String commonArgs, ErrorReporter errorReporter ) { - CTypes types = new CTypes(errorReporter); + CTypes types = new CTypes(); var lengthExpression = ""; var pointerExpression = ""; switch (connection.getSerializer()) { @@ -424,7 +421,7 @@ public String generateNetworkInputControlReactionBody( // Find the maximum STP for decentralized coordination if(coordination == CoordinationType.DECENTRALIZED) { - result.pr("max_STP = "+ GeneratorBase.timeInTargetLanguage(maxSTP)+";"); + result.pr("max_STP = "+ CTypes.getInstance().getTargetTimeExpr(maxSTP) +";"); } result.pr("// Wait until the port status is known"); result.pr("wait_until_port_status_known("+receivingPortID+", max_STP);"); @@ -617,7 +614,7 @@ private String generateCodeToInitializeFederate(FederateInstance federate, RtiCo if (stpParam.isPresent()) { var globalSTP = ASTUtils.initialValue(stpParam.get(), List.of(federate.instantiation)).get(0); var globalSTPTV = ASTUtils.getLiteralTimeValue(globalSTP); - code.pr("lf_set_stp_offset("+ CGenerator.timeInTargetLanguage(globalSTPTV)+");"); + code.pr("lf_set_stp_offset("+ CTypes.getInstance().getTargetTimeExpr(globalSTPTV) +");"); } } @@ -742,7 +739,7 @@ private String generateCodeForPhysicalActions(FederateInstance federate, ErrorRe } code.pr( "_fed.min_delay_from_physical_action_to_federate_output = " - + GeneratorBase.timeInTargetLanguage(minDelay) + ";"); + + CTypes.getInstance().getTargetTimeExpr(minDelay) + ";"); } } return code.getCode(); diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index aabc77ecd5..124ffefb4f 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -430,16 +430,16 @@ public static String generateFederateNeighborStructure(FederateInstance federate // Use NEVER to encode no delay at all. code.pr("candidate_tmp = NEVER;"); } else { - var delayTime = GeneratorBase.getTargetTime(delay); - if (delay instanceof ParameterReference) { - // The delay is given as a parameter reference. Find its value. - final var param = ((ParameterReference)delay).getParameter(); - delayTime = GeneratorBase.timeInTargetLanguage(ASTUtils.getDefaultAsTimeValue(param)); - } + var delayTime = + delay instanceof ParameterReference + // In that case use the default value. + ? CTypes.getInstance().getTargetTimeExpr(ASTUtils.getDefaultAsTimeValue(((ParameterReference) delay).getParameter())) + : CTypes.getInstance().getTargetExpr(delay, InferredType.time()); + code.pr(String.join("\n", - "if ("+delayTime+" < candidate_tmp) {", - " candidate_tmp = "+delayTime+";", - "}" + "if (" + delayTime + " < candidate_tmp) {", + " candidate_tmp = " + delayTime + ";", + "}" )); } } diff --git a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java index ca8ce4dd9c..e438b1b866 100644 --- a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java @@ -11,7 +11,6 @@ import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; -import org.lflang.Target; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.federated.generator.FedASTUtils; @@ -22,7 +21,7 @@ import org.lflang.generator.GeneratorBase; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.ReactorInstance; -import org.lflang.generator.ts.TSExtensionsKt; +import org.lflang.generator.ts.TSTypes; import org.lflang.lf.Action; import org.lflang.lf.Expression; import org.lflang.lf.Output; @@ -118,7 +117,7 @@ public String generatePreamble(FederateInstance federate, FedFileConfig fileConf .collect(Collectors.joining(",")), federate.id, minOutputDelay == null ? "undefined" - : "%s".formatted(TSExtensionsKt.toTsTime(minOutputDelay)), + : "%s".formatted(TSTypes.getInstance().getTargetTimeExpr(minOutputDelay)), federate.networkMessageActions .stream() .map(Variable::getName) diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index f3768cec7c..14b6320bec 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -75,17 +75,17 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 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. - * + * * @author Edward A. Lee * @author Soroush Bateni */ -public class FederateInstance { +public class FederateInstance { // why does this not extend ReactorInstance? /** * Construct a new instance with the specified instantiation of diff --git a/org.lflang/src/org/lflang/generator/CodeMap.java b/org.lflang/src/org/lflang/generator/CodeMap.java index 12d5ea8f65..d00fe9bc9a 100644 --- a/org.lflang/src/org/lflang/generator/CodeMap.java +++ b/org.lflang/src/org/lflang/generator/CodeMap.java @@ -17,6 +17,7 @@ import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.util.LineAndColumn; +import org.lflang.lf.ParameterReference; import org.lflang.lf.impl.ParameterReferenceImpl; /** @@ -147,6 +148,10 @@ public static String tag(EObject astNode, String representation, boolean verbati oneBasedLfLineAndColumn.getLine(), oneBasedLfLineAndColumn.getColumn() ); final URI uri = bestEffortGetEResource(astNode).getURI(); + if (uri == null) { + // no EResource, no correspondence can be found + return representation; + } final Path lfPath = Path.of(uri.isFile() ? uri.toFileString() : uri.path()); if (verbatim) lfStart = lfStart.plus(node.getText().substring(0, indexOf(node.getText(), representation))); return new Correspondence( @@ -162,12 +167,10 @@ public static String tag(EObject astNode, String representation, boolean verbati * This is a dangerous operation which can cause an unrecoverable error. */ private static Resource bestEffortGetEResource(EObject astNode) { - if (astNode instanceof ParameterReferenceImpl pri) return pri.getParameter().eResource(); - Resource ret = astNode.eResource(); - if (ret != null) return ret; - throw new RuntimeException( - "Every non-null AST node should have an EResource, but \"" + astNode + "\" does not." - ); + if (astNode instanceof ParameterReference pri) { + return pri.getParameter().eResource(); + } + return astNode.eResource(); } /** diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index ef35d22be1..ba39a94d9f 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -47,19 +47,15 @@ import org.lflang.MainConflictChecker; import org.lflang.Target; import org.lflang.TargetConfig; -import org.lflang.TimeUnit; -import org.lflang.TimeValue; import org.lflang.ast.AstTransformation; import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Connection; -import org.lflang.lf.Expression; 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.lf.Time; import org.lflang.validation.AbstractLFValidator; import com.google.common.base.Objects; @@ -418,33 +414,6 @@ public int getReactionBankIndex(Reaction reaction) { return reactionBankIndices.get(reaction); } - /** - * Given a representation of time that may possibly include units, return - * a string that the target language can recognize as a value. In this base - * class, if units are given, e.g. "msec", then we convert the units to upper - * case and return an expression of the form "MSEC(value)". Particular target - * generators will need to either define functions or macros for each possible - * time unit or override this method to return something acceptable to the - * target language. - * @param time A TimeValue that represents a time. - * @return A string, such as "MSEC(100)" for 100 milliseconds. - */ - public static String timeInTargetLanguage(TimeValue time) { - if (time != null) { - if (time.unit != null) { - return cMacroName(time.unit) + "(" + time.getMagnitude() + ")"; - } else { - return Long.valueOf(time.getMagnitude()).toString(); - } - } - return "0"; // FIXME: do this or throw exception? - } - - // note that this is moved out by #544 - public static String cMacroName(TimeUnit unit) { - return unit.getCanonicalName().toUpperCase(); - } - // ////////////////////////////////////////// // // Protected methods. @@ -674,50 +643,4 @@ public void printInfo(LFGeneratorContext.Mode mode) { */ public abstract Target getTarget(); - /** - * Get textual representation of a time in the target language. - * - * @param t A time AST node - * @return A time string in the target language - */ - // FIXME: this should be placed in ExpressionGenerator - public static String getTargetTime(Time t) { - TimeValue value = new TimeValue(t.getInterval(), TimeUnit.fromName(t.getUnit())); - return timeInTargetLanguage(value); - } - - /** - * Get textual representation of a value in the target language. - * - * If the value evaluates to 0, it is interpreted as a normal value. - * - * @param expr An AST node - * @return A string in the target language - */ - // FIXME: this should be placed in ExpressionGenerator - public static String getTargetValue(Expression expr) { - if (expr instanceof Time) { - return getTargetTime((Time)expr); - } - return ASTUtils.toText(expr); - } - - /** - * Get textual representation of a value in the target language. - * - * If the value evaluates to 0, it is interpreted as a time. - * - * @param expr A time AST node - * @return A time string in the target language - */ - // FIXME: this should be placed in ExpressionGenerator - public static String getTargetTime(Expression expr) { - if (expr instanceof Time) { - return getTargetTime((Time)expr); - } else if (ASTUtils.isZero(expr)) { - TimeValue value = TimeValue.ZERO; - return timeInTargetLanguage(value); - } - return ASTUtils.toText(expr); - } } diff --git a/org.lflang/src/org/lflang/generator/GeneratorUtils.java b/org.lflang/src/org/lflang/generator/GeneratorUtils.java index 709e1943bd..92a57fd424 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.java @@ -16,8 +16,8 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; -import org.lflang.generator.LFGeneratorContext.Mode; import org.lflang.TargetProperty; +import org.lflang.generator.LFGeneratorContext.Mode; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Instantiation; diff --git a/org.lflang/src/org/lflang/generator/LfExpressionVisitor.java b/org.lflang/src/org/lflang/generator/LfExpressionVisitor.java new file mode 100644 index 0000000000..2bb72789ff --- /dev/null +++ b/org.lflang/src/org/lflang/generator/LfExpressionVisitor.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2023, TU Dresden. + * + * 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 org.lflang.lf.BracedListExpression; +import org.lflang.lf.Code; +import org.lflang.lf.CodeExpr; +import org.lflang.lf.Expression; +import org.lflang.lf.LfFactory; +import org.lflang.lf.Literal; +import org.lflang.lf.ParameterReference; +import org.lflang.lf.Time; + +/** + * A visitor for expressions in LF. + * + * @author Clément Fournier <clement.fournier@tu-dresden.de> + */ +public interface LfExpressionVisitor { + + + R visitLiteral(Literal expr, P param); + + R visitBracedListExpr(BracedListExpression expr, P param); + + R visitTimeLiteral(Time expr, P param); + + R visitCodeExpr(CodeExpr expr, P param); + + R visitParameterRef(ParameterReference expr, P param); + + /** + * Dispatch the visitor on the given expression type. + * + * @param e An expression that will be visited + * @param arg Argument for the visitor + * @param visitor Visitor + * @param

Type of parameter expected by the visitor + * @param Return type of the visitor + * @return The return value of the visitor + */ + static R dispatch(Expression e, P arg, LfExpressionVisitor visitor) { + if (e instanceof Literal) { + return visitor.visitLiteral((Literal) e, arg); + } else if (e instanceof BracedListExpression) { + return visitor.visitBracedListExpr((BracedListExpression) e, arg); + } else if (e instanceof Time) { + return visitor.visitTimeLiteral((Time) e, arg); + } else if (e instanceof CodeExpr) { + return visitor.visitCodeExpr((CodeExpr) e, arg); + } else if (e instanceof ParameterReference) { + return visitor.visitParameterRef((ParameterReference) e, arg); + } + + throw new IllegalArgumentException("Expression of type " + e.getClass() + " not handled"); + } + + /** Base visitor class where methods are defaulted to a common one. */ + abstract class DefaultLfVisitor implements LfExpressionVisitor { + + abstract R visitExpression(Expression expr, P param); + + @Override + public R visitLiteral(Literal expr, P param) { + return visitExpression(expr, param); + } + + @Override + public R visitBracedListExpr(BracedListExpression expr, P param) { + return visitExpression(expr, param); + } + + @Override + public R visitTimeLiteral(Time expr, P param) { + return visitExpression(expr, param); + } + + @Override + public R visitCodeExpr(CodeExpr expr, P param) { + return visitExpression(expr, param); + } + + @Override + public R visitParameterRef(ParameterReference expr, P param) { + return visitExpression(expr, param); + } + } + + /** + * A visitor that deep copies the expression. Can be extended + * to replace certain expressions during the copy. + * + * @param

Parameter type + */ + class LfExpressionDeepCopyVisitor

implements LfExpressionVisitor { + + @Override + public Expression visitLiteral(Literal expr, P param) { + Literal clone = LfFactory.eINSTANCE.createLiteral(); + clone.setLiteral(expr.getLiteral()); + return clone; + } + + @Override + public Expression visitBracedListExpr(BracedListExpression expr, P param) { + BracedListExpression clone = LfFactory.eINSTANCE.createBracedListExpression(); + for (Expression item : expr.getItems()) { + clone.getItems().add(dispatch(item, param, this)); + } + return clone; + } + + @Override + public Expression visitTimeLiteral(Time expr, P param) { + Time clone = LfFactory.eINSTANCE.createTime(); + clone.setUnit(expr.getUnit()); + clone.setInterval(expr.getInterval()); + return clone; + } + + @Override + public Expression visitParameterRef(ParameterReference expr, P param) { + ParameterReference clone = LfFactory.eINSTANCE.createParameterReference(); + clone.setParameter(expr.getParameter()); + return clone; + } + + @Override + public Expression visitCodeExpr(CodeExpr expr, P param) { + CodeExpr codeExpr = LfFactory.eINSTANCE.createCodeExpr(); + Code code = LfFactory.eINSTANCE.createCode(); + code.setBody(expr.getCode().getBody()); + codeExpr.setCode(code); + return codeExpr; + } + } + +} diff --git a/org.lflang/src/org/lflang/generator/ParameterInstance.java b/org.lflang/src/org/lflang/generator/ParameterInstance.java index 9da4f1f247..9287710e6b 100644 --- a/org.lflang/src/org/lflang/generator/ParameterInstance.java +++ b/org.lflang/src/org/lflang/generator/ParameterInstance.java @@ -30,10 +30,12 @@ import java.util.List; import java.util.Optional; -import org.lflang.InferredType; import org.lflang.ASTUtils; +import org.lflang.InferredType; import org.lflang.lf.Assignment; import org.lflang.lf.Expression; +import org.lflang.lf.Initializer; +import org.lflang.lf.LfFactory; import org.lflang.lf.Parameter; /** @@ -71,13 +73,26 @@ public ParameterInstance(Parameter definition, ReactorInstance parent) { //// Public Methods /** - * Get the initial value(s) of this parameter as a list of - * Value objects, where each Value is either an instance - * of Time, Literal, or Code. That is, references to other - * parameters have been replaced with their initial values. + * Get the initial value of this parameter. */ - public List getInitialValue() { - return parent.initialParameterValue(this.definition); + private Initializer getInitialValue() { + return definition.getInit(); + } + + /** + * Return the (possibly overridden) value of this parameter + * in the containing instance. Parameter references are resolved + * to actual expressions. + */ + public Initializer getActualValue() { + Assignment override = getOverride(); + Initializer init; + if (override != null) { + init = override.getRhs(); + } else { + init = getInitialValue(); + } + return init; } /** diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index cc049f971c..833cd7e934 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -26,6 +26,9 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; +import static org.lflang.ASTUtils.belongsTo; +import static org.lflang.ASTUtils.getLiteralTimeValue; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -33,6 +36,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import org.eclipse.emf.ecore.util.EcoreUtil; @@ -43,10 +47,12 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.TimeValue; import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable; import org.lflang.lf.Action; +import org.lflang.lf.Assignment; import org.lflang.lf.BuiltinTrigger; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Connection; import org.lflang.lf.Expression; +import org.lflang.lf.Initializer; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; @@ -451,29 +457,50 @@ public boolean hasCycles() { */ public Integer initialIntParameterValue(Parameter parameter) { return ASTUtils.initialValueInt(parameter, instantiations()); - } + } - /** - * Given a parameter definition for this reactor, return the initial 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. - * - * The returned list of Value objects is such that each element is an - * instance of Time, String, or Code, never Parameter. - * For most uses, this list has only one element, but parameter - * values can be lists of elements, so the returned value is a list. - * - * @param parameter The parameter definition (a syntactic object in the AST). - * - * @return A list of Value objects, or null if the parameter is not found. - * Return an empty list if no initial value is given. - * Each value is an instance of Literal if a literal value is given, - * a Time if a time value was given, or a Code, if a code value was - * given (text in the target language delimited by {= ... =} - */ - public List initialParameterValue(Parameter parameter) { - return ASTUtils.initialValue(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(); + + @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; + } + } } /** @@ -686,14 +713,8 @@ public String toString() { * precise time value assigned to this reactor instance. */ public TimeValue getTimeValue(Expression expr) { - if (expr instanceof ParameterReference) { - final var param = ((ParameterReference)expr).getParameter(); - // Avoid a runtime error in validator for invalid programs. - if (lookupParameterInstance(param).getInitialValue().isEmpty()) return null; - return ASTUtils.getLiteralTimeValue(lookupParameterInstance(param).getInitialValue().get(0)); - } else { - return ASTUtils.getLiteralTimeValue(expr); - } + Expression resolved = resolveParameters(expr); + return getLiteralTimeValue(resolved); } ////////////////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/TargetTypes.java b/org.lflang/src/org/lflang/generator/TargetTypes.java index fe73bd78cc..8d9788e19e 100644 --- a/org.lflang/src/org/lflang/generator/TargetTypes.java +++ b/org.lflang/src/org/lflang/generator/TargetTypes.java @@ -8,6 +8,7 @@ import org.lflang.InferredType; import org.lflang.TimeValue; import org.lflang.lf.Action; +import org.lflang.lf.BracedListExpression; import org.lflang.lf.CodeExpr; import org.lflang.lf.Expression; import org.lflang.lf.Initializer; @@ -68,6 +69,13 @@ default String getTargetParamRef(ParameterReference expr, InferredType typeOrNul return escapeIdentifier(expr.getParameter().getName()); } + /** Translate the braced list expression into target language syntax. */ + default String getTargetBracedListExpr(BracedListExpression expr, InferredType typeOrNull) { + InferredType t = typeOrNull == null ? InferredType.undefined() : typeOrNull; + return expr.getItems().stream().map(e -> getTargetExpr(e, t)) + .collect(Collectors.joining(",", "{", "}")); + } + /** * Return an "undefined" type which is used as a default * when a type cannot be inferred. @@ -231,10 +239,8 @@ default String getTargetInitializer(Initializer init, Type type) { var targetValues = init.getExprs().stream().map(it -> getTargetExpr(it, inferredType)).collect(Collectors.toList()); if (inferredType.isFixedSizeList) { return getFixedSizeListInitExpression(targetValues, inferredType.listSize, init.isBraces()); - } else if (inferredType.isVariableSizeList) { + } else { return getVariableSizeListInitExpression(targetValues, init.isBraces()); - } else { - return getMissingExpr(inferredType); } } @@ -254,6 +260,8 @@ default String getTargetExpr(Expression expr, InferredType type) { return ASTUtils.addZeroToLeadingDot(((Literal) expr).getLiteral()); // here we don't escape } else if (expr instanceof CodeExpr) { return ASTUtils.toText(((CodeExpr) expr).getCode()); + } else if (expr instanceof BracedListExpression) { + return getTargetBracedListExpr((BracedListExpression) expr, type); } else { throw new IllegalStateException("Invalid value " + expr); } diff --git a/org.lflang/src/org/lflang/generator/c/CActionGenerator.java b/org.lflang/src/org/lflang/generator/c/CActionGenerator.java index e800328e18..1c4a1b45e8 100644 --- a/org.lflang/src/org/lflang/generator/c/CActionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CActionGenerator.java @@ -4,10 +4,8 @@ import java.util.ArrayList; import org.lflang.ASTUtils; import org.lflang.Target; -import org.lflang.federated.generator.FederateInstance; import org.lflang.generator.ActionInstance; import org.lflang.generator.CodeBuilder; -import org.lflang.generator.GeneratorBase; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.Reactor; @@ -40,9 +38,10 @@ public static String generateInitializers( var triggerStructName = CUtil.reactorRef(action.getParent()) + "->_lf__" + action.getName(); var minDelay = action.getMinDelay(); var minSpacing = action.getMinSpacing(); - var offsetInitializer = triggerStructName+".offset = " + GeneratorBase.timeInTargetLanguage(minDelay) + ";"; + var offsetInitializer = triggerStructName+".offset = " + CTypes.getInstance().getTargetTimeExpr(minDelay) + + ";"; var periodInitializer = triggerStructName+".period = " + (minSpacing != null ? - GeneratorBase.timeInTargetLanguage(minSpacing) : + CTypes.getInstance().getTargetTimeExpr(minSpacing) : CGenerator.UNDEFINED_MIN_SPACING) + ";"; code.addAll(List.of( "// Initializing action "+action.getFullName(), diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 2ef5074455..70f76c282e 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -394,9 +394,9 @@ public CGenerator(LFGeneratorContext context, boolean ccppMode) { this( context, ccppMode, - new CTypes(context.getErrorReporter()), + new CTypes(), new CCmakeGenerator(context.getFileConfig(), List.of()), - new CDelayBodyGenerator(new CTypes(context.getErrorReporter())) + new CDelayBodyGenerator(new CTypes()) ); } @@ -1910,7 +1910,7 @@ private void generateSetDeadline(ReactorInstance instance) { var selfRef = CUtil.reactorRef(reaction.getParent())+"->_lf__reaction_"+reaction.index; if (reaction.declaredDeadline != null) { var deadline = reaction.declaredDeadline.maxDelay; - initializeTriggerObjects.pr(selfRef+".deadline = "+GeneratorBase.timeInTargetLanguage(deadline)+";"); + initializeTriggerObjects.pr(selfRef+".deadline = "+types.getTargetTimeExpr(deadline)+";"); } else { // No deadline. initializeTriggerObjects.pr(selfRef+".deadline = NEVER;"); } diff --git a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java b/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java index 23906eae73..1534f0ea02 100644 --- a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java @@ -1,15 +1,16 @@ package org.lflang.generator.c; -import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; + +import org.lflang.InferredType; import org.lflang.generator.ParameterInstance; import org.lflang.ASTUtils; import org.lflang.generator.CodeBuilder; -import org.lflang.generator.GeneratorBase; import org.lflang.lf.Assignment; import org.lflang.lf.Expression; +import org.lflang.lf.Initializer; import org.lflang.lf.Parameter; -import org.lflang.lf.ParameterReference; import org.lflang.lf.Reactor; /** @@ -32,46 +33,9 @@ public static String getInitializer(ParameterInstance p) { return CUtil.bankIndex(p.getParent()); } - // Handle overrides in the intantiation. - // In case there is more than one assignment to this parameter, we need to - // find the last one. - Assignment lastAssignment = null; - for (Assignment assignment: p.getParent().getDefinition().getParameters()) { - if (assignment.getLhs() == p.getDefinition()) { - lastAssignment = assignment; - } - } - List list = new LinkedList<>(); - if (lastAssignment != null) { - // The parameter has an assignment. - // Right hand side can be a list. Collect the entries. - for (Expression expr: lastAssignment.getRhs().getExprs()) { - if (expr instanceof ParameterReference) { - // The parameter is being assigned a parameter value. - // Assume that parameter belongs to the parent's parent. - // This should have been checked by the validator. - final var param = ((ParameterReference) expr).getParameter(); - list.add(CUtil.reactorRef(p.getParent().getParent()) + "->" + param.getName()); - } else { - list.add(GeneratorBase.getTargetTime(expr)); - } - } - } else { - // there was no assignment in the instantiation. So just use the - // parameter's initial value. - for (Expression expr : p.getParent().initialParameterValue(p.getDefinition())) { - if (ASTUtils.isOfTimeType(p.getDefinition())) { - list.add(GeneratorBase.getTargetTime(expr)); - } else { - list.add(GeneratorBase.getTargetTime(expr)); - } - } - } - if (list.size() == 1) { - return list.get(0); - } else { - return "{" + String.join(", ", list) + "}"; - } + CTypes ctypes = CTypes.generateParametersIn(p.getParent().getParent()); + Initializer values = p.getActualValue(); + return ctypes.getTargetInitializer(values, p.getDefinition().getType()); } /** diff --git a/org.lflang/src/org/lflang/generator/c/CStateGenerator.java b/org.lflang/src/org/lflang/generator/c/CStateGenerator.java index a0bcf28823..46910f4167 100644 --- a/org.lflang/src/org/lflang/generator/c/CStateGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CStateGenerator.java @@ -3,6 +3,7 @@ import java.util.LinkedList; import org.lflang.ASTUtils; +import org.lflang.InferredType; import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; import org.lflang.generator.ModeInstance; @@ -126,17 +127,7 @@ private static String generateModalReset( * references are replaced with accesses to the self struct of the parent. */ private static String getInitializerExpr(StateVar state, ReactorInstance parent) { - var list = new LinkedList(); - for (Expression expr : state.getInit().getExprs()) { - if (expr instanceof ParameterReference) { - final var param = ((ParameterReference)expr).getParameter(); - list.add(CUtil.reactorRef(parent) + "->" + param.getName()); - } else { - list.add(GeneratorBase.getTargetTime(expr)); - } - } - return list.size() == 1 ? - list.get(0) : - "{" + String.join(", ", list) + "}"; + var ctypes = CTypes.generateParametersIn(parent); + return ctypes.getTargetInitializer(state.getInit(), state.getType()); } } diff --git a/org.lflang/src/org/lflang/generator/c/CTimerGenerator.java b/org.lflang/src/org/lflang/generator/c/CTimerGenerator.java index e0c2000e87..75623fdfe7 100644 --- a/org.lflang/src/org/lflang/generator/c/CTimerGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CTimerGenerator.java @@ -1,7 +1,7 @@ package org.lflang.generator.c; import java.util.List; -import org.lflang.generator.GeneratorBase; + import org.lflang.generator.TimerInstance; /** @@ -18,8 +18,8 @@ public class CTimerGenerator { */ public static String generateInitializer(TimerInstance timer) { var triggerStructName = CUtil.reactorRef(timer.getParent()) + "->_lf__" + timer.getName(); - var offset = GeneratorBase.timeInTargetLanguage(timer.getOffset()); - var period = GeneratorBase.timeInTargetLanguage(timer.getPeriod()); + var offset = CTypes.getInstance().getTargetTimeExpr(timer.getOffset()); + var period = CTypes.getInstance().getTargetTimeExpr(timer.getPeriod()); var mode = timer.getMode(false); var modeRef = mode != null ? "&"+CUtil.reactorRef(mode.getParent())+"->_lf__modes["+mode.getParent().modes.indexOf(mode)+"];" : diff --git a/org.lflang/src/org/lflang/generator/c/CTypes.java b/org.lflang/src/org/lflang/generator/c/CTypes.java index 803d9e8432..ba5b3631a9 100644 --- a/org.lflang/src/org/lflang/generator/c/CTypes.java +++ b/org.lflang/src/org/lflang/generator/c/CTypes.java @@ -1,11 +1,18 @@ package org.lflang.generator.c; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.TimeUnit; +import org.lflang.TimeValue; +import org.lflang.generator.ReactorInstance; import org.lflang.generator.TargetTypes; +import org.lflang.lf.Initializer; +import org.lflang.lf.ParameterReference; +import org.lflang.lf.Type; public class CTypes implements TargetTypes { @@ -13,20 +20,9 @@ public class CTypes implements TargetTypes { // For example, for "foo[10]", the first match will be "foo" and the second "[10]". // For "foo[]", the first match will be "foo" and the second "". static final Pattern arrayPattern = Pattern.compile("^\\s*(?:/\\*.*?\\*/)?\\s*(\\w+)\\s*\\[([0-9]*)]\\s*$"); + private static final CTypes INSTANCE = new CTypes(); - // FIXME: Instead of using the ErrorReporter, perhaps we should be raising assertion errors or - // UnsupportedOperationExceptions or some other non-recoverable errors. - private final ErrorReporter errorReporter; - - /** - * Initializes a {@code CTargetTypes} with the given - * error reporter. - * @param errorReporter The error reporter for any - * errors raised in the code - * generation process. - */ - public CTypes(ErrorReporter errorReporter) { - this.errorReporter = errorReporter; + public CTypes() { } @Override @@ -56,7 +52,7 @@ public String getTargetVariableSizeListType(String baseType) { @Override public String getTargetUndefinedType() { - return String.format("/* %s */", errorReporter.reportError("undefined type")); + return "/*undefined*/"; } /** @@ -78,6 +74,33 @@ public String getTargetType(InferredType type) { return result; } + @Override + public String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { + throw new UnsupportedOperationException("No context defined"); + } + + @Override + public String getTargetTimeExpr(TimeValue time) { + if (time != null) { + if (time.unit != null) { + return cMacroName(time.unit) + "(" + time.getMagnitude() + ")"; + } else { + return Long.valueOf(time.getMagnitude()).toString(); + } + } + return "0"; // FIXME: do this or throw exception? + } + + @Override + public String getFixedSizeListInitExpression(List contents, int listSize, boolean withBraces) { + return contents.stream().collect(Collectors.joining(", ", "{ ", " }")); + } + + @Override + public String getVariableSizeListInitExpression(List contents, boolean withBraces) { + return contents.stream().collect(Collectors.joining(", ", "{ ", " }")); + } + /** * Return a variable declaration of the form "{@code type name}". * The type is as returned by {@link #getTargetType(InferredType)}, except with @@ -119,4 +142,23 @@ public String getVariableDeclaration( } return declaration; } + + // note that this is moved out by #544 + public static String cMacroName(TimeUnit unit) { + return unit.getCanonicalName().toUpperCase(); + } + + public static CTypes getInstance() { + return INSTANCE; + } + + + public static CTypes generateParametersIn(ReactorInstance instance) { + return new CTypes() { + @Override + public String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { + return CUtil.reactorRef(instance) + "->" + expr.getParameter().getName(); + } + }; + } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt index a460290287..3c2224e031 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt @@ -40,32 +40,28 @@ class CppParameterGenerator(private val reactor: Reactor) { val Parameter.typeAlias get(): String = "__lf_${name}_t" } - /** Generate all constructor initializers for parameters */ - fun generateInitializers() = - reactor.parameters.joinWithLn(prefix = "// parameters\n") { - ", ${it.name}(parameters.${it.name})" - } - /** Generate all parameter declarations as used in the parameter struct */ fun generateParameterStructDeclarations() = reactor.parameters.joinToString("\n", postfix = "\n") { with(it) { """ using $typeAlias = $targetType; - const $typeAlias $name${ - if (init == null) "" else " = " + typeAlias + CppTypes.getCppInitializer( + $typeAlias $name${ + if (init == null) "" else CppTypes.getCppInitializer( init, - inferredType + inferredType, + typeAlias = typeAlias ) }; """.trimIndent() } } - /** Generate using declarations for each parameter for use in the inner reactor class. - * This is required for C++ to bring templated parameters into scope. + /** Generate alias declarations for each parameter for use in the inner reactor class. + * This is required to bring parameters into scope. */ - fun generateUsingDeclarations() = reactor.parameters.joinToString(separator = "") { "using Parameters::${it.name};\n" } + fun generateInnerAliasDeclarations() = + reactor.parameters.joinToString(separator = "") { "const typename Parameters::${it.typeAlias}& ${it.name} = __lf_parameters.${it.name};\n" } /** Generate alias declarations for each parameter for use in the outer reactor class. * This is required for some code bodies (e.g. target code in parameter initializers) to have access to the local parameters. diff --git a/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt index de8deb7a4f..50fff0bdb5 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt @@ -94,8 +94,9 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi | private: ${" | "..reactions.generateReactionViewForwardDeclarations()} | - | class Inner: public lfutil::LFScope, public Parameters { - ${" | "..parameters.generateUsingDeclarations()} + | class Inner: public lfutil::LFScope { + | const Parameters __lf_parameters; + ${" | "..parameters.generateInnerAliasDeclarations()} ${" | "..state.generateDeclarations()} ${" | "..methods.generateDeclarations()} ${" | "..reactions.generateBodyDeclarations()} @@ -161,9 +162,9 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi return with(PrependOperator) { """ |${reactor.templateLine} - |${reactor.templateName}::Inner::Inner(::reactor::Reactor* reactor, Parameters&& __lf_parameters) + |${reactor.templateName}::Inner::Inner(::reactor::Reactor* reactor, Parameters&& parameters) | : LFScope(reactor) - ${" | , Parameters(std::forward(__lf_parameters))"} + ${" | , __lf_parameters(std::forward(parameters))"} ${" | "..state.generateInitializers()} |{} """.trimMargin() diff --git a/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt index 4b26328155..1d5050fee9 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt @@ -42,6 +42,6 @@ class CppStateGenerator(private val reactor: Reactor) { fun generateInitializers(): String = reactor.stateVars.filter { it.isInitialized } .joinWithLn(prefix = "// state variables\n") { - ", " + it.name + CppTypes.getCppInitializer(it.init, it.inferredType) + ", " + it.name + CppTypes.getCppInitializer(it.init, it.inferredType, disableEquals = true) } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppTypes.kt b/org.lflang/src/org/lflang/generator/cpp/CppTypes.kt index e0a7103f20..6a908392ab 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppTypes.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppTypes.kt @@ -28,6 +28,7 @@ import org.lflang.InferredType import org.lflang.TimeUnit import org.lflang.TimeValue import org.lflang.generator.TargetTypes +import org.lflang.joinWithCommas import org.lflang.lf.Initializer import org.lflang.lf.ParameterReference @@ -71,14 +72,24 @@ val TimeUnit?.cppUnit /** * Returns a C++ variable initializer. */ -fun CppTypes.getCppInitializer(init: Initializer?, inferredType: InferredType): String { - return if (init == null) { +fun CppTypes.getCppInitializer( + init: Initializer?, + inferredType: InferredType, + disableEquals: Boolean = false, + typeAlias: String? = null +): String = + if (init == null) { "/*uninitialized*/" + } else if (init.isAssign && !disableEquals) { + val e = init.exprs.single() + " = " + getTargetExpr(e, inferredType) + } else if (init.isParens && typeAlias != null && !disableEquals) { + " = $typeAlias" + init.exprs.joinWithCommas("(", ")", trailing = false) { + getTargetExpr(it, inferredType.componentType) + } } else { - assert(init.isBraces || init.isParens) val (prefix, postfix) = if (init.isBraces) Pair("{", "}") else Pair("(", ")") - init.exprs.joinToString(", ", prefix, postfix) { + init.exprs.joinWithCommas(prefix, postfix, trailing = false) { getTargetExpr(it, inferredType.componentType) } } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/python/PyUtil.java b/org.lflang/src/org/lflang/generator/python/PyUtil.java index 78375c910a..9ca2f7222f 100644 --- a/org.lflang/src/org/lflang/generator/python/PyUtil.java +++ b/org.lflang/src/org/lflang/generator/python/PyUtil.java @@ -26,6 +26,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator.python; +import org.lflang.InferredType; import org.lflang.generator.ReactorInstance; import org.lflang.generator.GeneratorBase; import org.lflang.generator.c.CUtil; @@ -143,25 +144,6 @@ public static String generateGILReleaseCode() { * @return A value string in the target language */ protected static String getPythonTargetValue(Expression expr) { - String returnValue; - switch (ASTUtils.toOriginalText(expr)) { - case "false": - returnValue = "False"; - break; - case "true": - returnValue = "True"; - break; - default: - returnValue = GeneratorBase.getTargetValue(expr); - } - - // Parameters in Python are always prepended with a 'self.' - // predicate. Therefore, we need to append the returned value - // if it is a parameter. - if (expr instanceof ParameterReference) { - returnValue = "self." + returnValue; - } - - return returnValue; + return PythonTypes.getInstance().getTargetExpr(expr, InferredType.undefined()); } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 46bcc4f811..3f82f20016 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -96,7 +96,7 @@ public class PythonGenerator extends CGenerator { public PythonGenerator(LFGeneratorContext context) { this(context, - new PythonTypes(context.getErrorReporter()), + new PythonTypes(), new CCmakeGenerator( context.getFileConfig(), List.of("lib/python_action.c", diff --git a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java index 5617350062..fe78d8187f 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java @@ -8,6 +8,7 @@ import com.google.common.base.Objects; import org.lflang.ASTUtils; +import org.lflang.InferredType; import org.lflang.generator.GeneratorBase; import org.lflang.generator.ParameterInstance; import org.lflang.lf.Expression; @@ -95,8 +96,7 @@ private static List getAllParameters(ReactorDecl decl) { * @return Initialization code */ private static String generatePythonInitializer(Parameter p) { - List values = p.getInit().getExprs().stream().map(PyUtil::getPythonTargetValue).toList(); - return values.size() > 1 ? "(" + String.join(", ", values) + ")" : values.get(0); + return PythonTypes.getInstance().getTargetInitializer(p.getInit(), p.getType()); } /** @@ -110,47 +110,8 @@ private static String generatePythonInitializer(Parameter p) { * @return Initialization code */ public static String generatePythonInitializer(ParameterInstance p) { - // Handle overrides in the instantiation. - // In case there is more than one assignment to this parameter, we need to - // find the last one. - Assignment lastAssignment = getLastAssignment(p); - List list = new LinkedList<>(); - if (lastAssignment != null) { - // The parameter has an assignment. - // Right hand side can be a list. Collect the entries. - for (Expression expr : lastAssignment.getRhs().getExprs()) { - if (expr instanceof ParameterReference) { - // The parameter is being assigned a parameter value. - // Assume that parameter belongs to the parent's parent. - // This should have been checked by the validator. - final var param = ((ParameterReference) expr).getParameter(); - list.add(PyUtil.reactorRef(p.getParent().getParent()) + "." + param.getName()); - } else { - list.add(GeneratorBase.getTargetTime(expr)); - } - } - } else { - for (Expression expr : p.getParent().initialParameterValue(p.getDefinition())) { - list.add(PyUtil.getPythonTargetValue(expr)); - } - } - return list.size() > 1 ? "(" + String.join(", ", list) + ")" : list.get(0); + PythonTypes pyTypes = PythonTypes.generateParametersIn(p.getParent().getParent()); + return pyTypes.getTargetInitializer(p.getActualValue(), p.getDefinition().getType()); } - /** - * Returns the last assignment to "p" if there is one, - * or null if there is no assignment to "p" - * - * @param p The parameter instance to create initializer for - * @return The last assignment of the parameter instance - */ - private static Assignment getLastAssignment(ParameterInstance p) { - Assignment lastAssignment = null; - for (Assignment assignment : p.getParent().getDefinition().getParameters()) { - if (Objects.equal(assignment.getLhs(), p.getDefinition())) { - lastAssignment = assignment; - } - } - return lastAssignment; - } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonTypes.java b/org.lflang/src/org/lflang/generator/python/PythonTypes.java index c066494047..5b62ee5f97 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonTypes.java +++ b/org.lflang/src/org/lflang/generator/python/PythonTypes.java @@ -1,27 +1,20 @@ package org.lflang.generator.python; +import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Collectors; -import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.generator.ReactorInstance; import org.lflang.generator.c.CTypes; +import org.lflang.generator.c.CUtil; +import org.lflang.lf.ParameterReference; public class PythonTypes extends CTypes { // Regular expression pattern for pointer types. The star at the end has to be visible. static final Pattern pointerPatternVariable = Pattern.compile("^\\s*+(\\w+)\\s*\\*\\s*$"); - - /** - * Initializes a {@code CTargetTypes} with the given - * error reporter. - * - * @param errorReporter The error reporter for any - * errors raised in the code - * generation process. - */ - public PythonTypes(ErrorReporter errorReporter) { - super(errorReporter); - } + private static final PythonTypes INSTANCE = new PythonTypes(); @Override public String getTargetUndefinedType() { @@ -39,16 +32,45 @@ public String getTargetUndefinedType() { public String getPythonType(InferredType type) { var result = super.getTargetType(type); - switch(result){ - case "double": result = "float"; - case "string": result = "object"; - } + result = switch (result) { + case "double" -> "float"; + case "string" -> "object"; + default -> result; + }; var matcher = pointerPatternVariable.matcher(result); - if(matcher.find()) { + if (matcher.find()) { return matcher.group(1); } return result; } + + @Override + public String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { + return "self." + expr.getParameter().getName(); + } + + @Override + public String getFixedSizeListInitExpression(List contents, int listSize, boolean withBraces) { + return contents.stream().collect(Collectors.joining(", ", "[ ", " ]")); + } + + @Override + public String getVariableSizeListInitExpression(List contents, boolean withBraces) { + return contents.stream().collect(Collectors.joining(", ", "[ ", " ]")); + } + + public static PythonTypes getInstance() { + return INSTANCE; + } + + public static PythonTypes generateParametersIn(ReactorInstance instance) { + return new PythonTypes() { + @Override + public String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { + return PyUtil.reactorRef(instance) + "." + expr.getParameter().getName(); + } + }; + } } diff --git a/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt index c65cd157c5..0075950a1f 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -23,7 +23,7 @@ class TSConstructorGenerator( ) { private fun initializeParameter(p: Parameter): String = - "${p.name}: ${TSTypes.getTargetType(p)} = ${TSTypes.getTargetInitializer(p)}" + "${p.name}: ${TSTypes.getInstance().getTargetType(p)} = ${TSTypes.getInstance().getTargetInitializer(p)}" private fun generateConstructorArguments(reactor: Reactor): String { val arguments = StringJoiner(", \n") diff --git a/org.lflang/src/org/lflang/generator/ts/TSDelayBodyGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSDelayBodyGenerator.kt index b0a011d042..61aac6750e 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSDelayBodyGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSDelayBodyGenerator.kt @@ -16,7 +16,7 @@ object TSDelayBodyGenerator : DelayBodyGenerator { */ private fun getActionType(action: Action): String { return if (action.type != null) { - TSTypes.getTargetType(action.type) + TSTypes.getInstance().getTargetType(action.type) } else { "Present" } diff --git a/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java b/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java new file mode 100644 index 0000000000..cf89596a56 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java @@ -0,0 +1,29 @@ +package org.lflang.generator.ts; + +import org.lflang.generator.DockerGenerator; +import org.lflang.generator.LFGeneratorContext; + +/** + * Generates the docker file related code for the Typescript target. + * + * @author Hou Seng Wong + */ +public class TSDockerGenerator extends DockerGenerator { + + /** Construct a new Docker generator. */ + public TSDockerGenerator(LFGeneratorContext context) { + super(context); + } + + /** + * Return the content of the docker file for [tsFileName]. + */ + public String generateDockerFileContent() { + return """ + |FROM node:alpine + |WORKDIR /linguafranca/$name + |COPY . . + |ENTRYPOINT ["node", "dist/%s.js"] + """.formatted(context.getFileConfig().name); + } +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.kt deleted file mode 100644 index 1ca762340e..0000000000 --- a/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.lflang.generator.ts - -import org.lflang.generator.DockerGenerator -import org.lflang.generator.LFGeneratorContext - -/** - * Generates the docker file related code for the Typescript target. - * - * @author Hou Seng Wong - */ -class TSDockerGenerator(context: LFGeneratorContext) : DockerGenerator(context) { - - /** - * Returns the content of the docker file for [tsFileName]. - */ - override fun generateDockerFileContent(): String { - val name = context.fileConfig.name - val dockerFileContent = """ - |FROM node:alpine - |WORKDIR /linguafranca/$name - |COPY . . - |ENTRYPOINT ["node", "dist/$name.js"] - """ - return dockerFileContent.trimMargin() - } -} diff --git a/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt b/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt index 4ed12daa13..a28e462921 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt @@ -39,7 +39,7 @@ fun WidthSpec.toTSCode(): String = terms.joinToString(" + ") { * @return The TS type. */ val Port.tsPortType: String - get() = type?.let { TSTypes.getTargetType(it) } ?: "Present" + get() = type?.let { TSTypes.getInstance().getTargetType(it) } ?: "Present" /** * Return a TS type for the specified action. @@ -48,7 +48,7 @@ val Port.tsPortType: String * @return The TS type. */ val Action.tsActionType: String - get() = type?.let { TSTypes.getTargetType(it) } ?: "Present" + get() = type?.let { TSTypes.getInstance().getTargetType(it) } ?: "Present" -fun Expression.toTsTime(): String = TSTypes.getTargetTimeExpr(this) -fun TimeValue.toTsTime(): String = TSTypes.getTargetTimeExpr(this) +fun Expression.toTsTime(): String = TSTypes.getInstance().getTargetTimeExpr(this) +fun TimeValue.toTsTime(): String = TSTypes.getInstance().getTargetTimeExpr(this) diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 3fd3675d52..9262e44e64 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -429,7 +429,7 @@ class TSGenerator( return true } - override fun getTargetTypes(): TargetTypes = TSTypes + override fun getTargetTypes(): TargetTypes = TSTypes.getInstance() override fun getTarget(): Target { return Target.TS diff --git a/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt index 63857c0132..97e753f99b 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -25,10 +25,10 @@ class TSInstanceGenerator( private fun getTypeParams(typeParms: List): String = if (typeParms.isEmpty()) "" - else typeParms.joinToString(", ", "<", ">") { TSTypes.getTargetType(it) } + else typeParms.joinToString(", ", "<", ">") { TSTypes.getInstance().getTargetType(it) } private fun getReactorParameterList(parameters: List): String = - parameters.joinToString(", ", "[__Reactor, ", "]") { TSTypes.getTargetType(it) } + parameters.joinToString(", ", "[__Reactor, ", "]") { TSTypes.getInstance().getTargetType(it) } fun generateClassProperties(): String = @@ -50,7 +50,7 @@ class TSInstanceGenerator( childReactorArguments.add("this") for (parameter in childReactor.reactorClass.toDefinition().parameters) { - childReactorArguments.add(TSTypes.getTargetInitializer(parameter, childReactor)) + childReactorArguments.add(TSTypes.getInstance().getTargetInitializer(parameter, childReactor)) } if (childReactor.isBank) { childReactorInstantiations.add( diff --git a/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt index 5ded5814b0..838186a6dc 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt @@ -12,7 +12,7 @@ class TSParameterGenerator( fun generateClassProperties(): String = parameters.joinWithLn { - "${it.name}: __Parameter<${TSTypes.getTargetType(it)}>;" + "${it.name}: __Parameter<${TSTypes.getInstance().getTargetType(it)}>;" } fun generateInstantiations(): String = diff --git a/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt index c9fef820f0..305369832e 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt @@ -69,7 +69,7 @@ class TSParameterPreambleGenerator( mainParameters.joinWithLn { parameter -> """ - |let __CL${parameter.name}: ${TSTypes.getTargetType(parameter)} | undefined = undefined; + |let __CL${parameter.name}: ${TSTypes.getInstance().getTargetType(parameter)} | undefined = undefined; |if (__processedCLArgs.${parameter.name} !== undefined) { | if (__processedCLArgs.${parameter.name} !== null) { | __CL${parameter.name} = __processedCLArgs.${parameter.name}; @@ -114,7 +114,7 @@ class TSParameterPreambleGenerator( var customArgType: String? = null var customTypeLabel: String? = null - val paramType = TSTypes.getTargetType(parameter) + val paramType = TSTypes.getInstance().getTargetType(parameter) if (paramType == "string") { mainParameters.add(parameter) customArgType = "String"; diff --git a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt index 4354ac1ea9..aca577aad0 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt @@ -358,7 +358,7 @@ class TSReactionGenerator( // Underscores are added to parameter names to prevent conflict with prologue val name = param.name - reactSignature.add("__$name: __Parameter<${TSTypes.getTargetType(param)}>") + reactSignature.add("__$name: __Parameter<${TSTypes.getInstance().getTargetType(param)}>") reactFunctArgs.add("this.$name") reactPrologue.add("let $name = __$name.get();") } @@ -367,7 +367,7 @@ class TSReactionGenerator( for (state in reactor.stateVars) { // Underscores are added to state names to prevent conflict with prologue val name = state.name - reactSignature.add("__$name: __State<${TSTypes.getTargetType(state)}>") + reactSignature.add("__$name: __State<${TSTypes.getInstance().getTargetType(state)}>") reactFunctArgs.add("this.$name") reactPrologue.add("let $name = __$name.get();") reactEpilogue.add( diff --git a/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt index 091da1d958..6ac2d8b28a 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt @@ -15,7 +15,7 @@ class TSStateGenerator( fun generateClassProperties(): String { val stateClassProperties = LinkedList() for (stateVar in stateVars) { - stateClassProperties.add("${stateVar.name}: __State<${TSTypes.getTargetType(stateVar)}>;"); + stateClassProperties.add("${stateVar.name}: __State<${TSTypes.getInstance().getTargetType(stateVar)}>;"); } return stateClassProperties.joinToString("\n") } @@ -25,7 +25,7 @@ class TSStateGenerator( // Next handle states. for (stateVar in stateVars) { if (ASTUtils.isInitialized(stateVar)) { - stateInstantiations.add("this.${stateVar.name} = new __State(${TSTypes.getTargetInitializer(stateVar)});"); + stateInstantiations.add("this.${stateVar.name} = new __State(${TSTypes.getInstance().getTargetInitializer(stateVar)});"); } else { stateInstantiations.add("this.${stateVar.name} = new __State(undefined);"); } diff --git a/org.lflang/src/org/lflang/generator/ts/TSTypes.java b/org.lflang/src/org/lflang/generator/ts/TSTypes.java new file mode 100644 index 0000000000..e89fff00d4 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/ts/TSTypes.java @@ -0,0 +1,75 @@ +package org.lflang.generator.ts; + +import java.util.List; + +import org.lflang.ASTUtils; +import org.lflang.TimeValue; +import org.lflang.generator.TargetTypes; +import org.lflang.generator.UnsupportedGeneratorFeatureException; +import org.lflang.lf.StateVar; + +public class TSTypes implements TargetTypes { + + private static TSTypes INSTANCE = new TSTypes(); + + private TSTypes() { + + } + + @Override + public String getTargetType(StateVar s) { + var type = TargetTypes.super.getTargetType(s); + if (!ASTUtils.isInitialized(s)) { + return "%s | undefined".formatted(type); + } else { + return type; + } + } + + @Override + public boolean supportsGenerics() { + return true; + } + + @Override + public String getTargetTimeType() { + return "TimeValue"; + } + + @Override + public String getTargetTagType() { + return "TimeValue"; + } + + @Override + public String getTargetUndefinedType() { + return "Present"; + } + + public String getTargetTimeExpr(TimeValue value) { + if (value.unit != null) { + return "TimeValue.%s(%s)".formatted(value.unit.getCanonicalName(), value.time); + } else { + // The value must be zero. + return "TimeValue.zero()"; + } + } + + @Override + public String getTargetFixedSizeListType(String baseType, int size) { + throw new UnsupportedGeneratorFeatureException("TypeScript does not support fixed-size array types."); + } + + @Override + public String getTargetVariableSizeListType(String baseType) { + return "Array<%s>".formatted(baseType); // same as "$baseType[]" + } + + public String getVariableSizeListInitExpression(List contents, boolean withBraces) { + return "[" + String.join(", ", contents) + "]"; + } + + public static TSTypes getInstance() { + return INSTANCE; + } +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSTypes.kt b/org.lflang/src/org/lflang/generator/ts/TSTypes.kt deleted file mode 100644 index 9b7b66f62f..0000000000 --- a/org.lflang/src/org/lflang/generator/ts/TSTypes.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.lflang.generator.ts - -import org.lflang.ASTUtils -import org.lflang.TimeValue -import org.lflang.generator.TargetTypes -import org.lflang.generator.UnsupportedGeneratorFeatureException -import org.lflang.joinWithCommas -import org.lflang.lf.StateVar - -object TSTypes : TargetTypes { - - override fun getTargetType(s: StateVar): String { - val type = super.getTargetType(s) - return if (!ASTUtils.isInitialized(s)) { - "$type | undefined" - } else { - type - } - } - - override fun supportsGenerics(): Boolean { - return true - } - - override fun getTargetTimeType(): String { - return "TimeValue" - } - - override fun getTargetTagType(): String { - return "TimeValue" - } - - override fun getTargetUndefinedType(): String { - return "Present" - } - - override fun getTargetTimeExpr(value: TimeValue): String { - return if (value.unit != null) { - "TimeValue.${value.unit.canonicalName}(${value.time})" - } else { - // The value must be zero. - "TimeValue.zero()" - } - } - - override fun getTargetFixedSizeListType(baseType: String?, size: Int): String { - throw UnsupportedGeneratorFeatureException("TypeScript does not support fixed-size array types.") - } - - override fun getTargetVariableSizeListType(baseType: String): String { - return "Array<$baseType>" // same as "$baseType[]" - } - - override fun getVariableSizeListInitExpression(contents: MutableList, withBraces: Boolean): String { - return contents.joinWithCommas("[", "]") - } -} diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 3f1fbef2fc..2ea579b753 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -69,6 +69,7 @@ import org.lflang.lf.ActionOrigin; import org.lflang.lf.Assignment; import org.lflang.lf.Attribute; +import org.lflang.lf.BracedListExpression; import org.lflang.lf.BuiltinTrigger; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.CodeExpr; @@ -172,6 +173,30 @@ public void checkAction(Action action) { 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 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()); } } @@ -1596,9 +1621,12 @@ public void typeCheck(Initializer init, InferredType type, EStructuralFeature fe // list of times var exprs = init.getExprs(); if (exprs.isEmpty()) { - error("Expected exactly one time value.", feature); + 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); } @@ -1608,7 +1636,7 @@ public void typeCheck(Initializer init, InferredType type, EStructuralFeature fe } } - public void checkExpressionIsTime(Initializer init, EStructuralFeature feature) { + private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) { if (init == null) { return; } @@ -1620,7 +1648,7 @@ public void checkExpressionIsTime(Initializer init, EStructuralFeature feature) } } - public void checkExpressionIsTime(Expression value, EStructuralFeature feature) { + private void checkExpressionIsTime(Expression value, EStructuralFeature feature) { if (value == null || value instanceof Time) { return; } diff --git a/test/C/src/ActionDelay.lf b/test/C/src/ActionDelay.lf index 6a4deddda6..86e510080a 100644 --- a/test/C/src/ActionDelay.lf +++ b/test/C/src/ActionDelay.lf @@ -4,7 +4,7 @@ target C reactor GeneratedDelay { input y_in: int output y_out: int - state y_state: int(0) + state y_state: int = 0 logical action act(100 msec) reaction(y_in) -> act {= diff --git a/test/C/src/ActionIsPresent.lf b/test/C/src/ActionIsPresent.lf index 4f8164bea8..b94108949e 100644 --- a/test/C/src/ActionIsPresent.lf +++ b/test/C/src/ActionIsPresent.lf @@ -1,9 +1,9 @@ // Tests the is_present variable for actions. target C -main reactor ActionIsPresent(offset: time(1 nsec), period: time(500 msec)) { +main reactor ActionIsPresent(offset: time = 1 nsec, period: time = 500 msec) { logical action a - state success: bool(false) + state success: bool = false reaction(startup, a) -> a {= if (!a->is_present) { diff --git a/test/C/src/After.lf b/test/C/src/After.lf index f354d9d7c8..d3663457e4 100644 --- a/test/C/src/After.lf +++ b/test/C/src/After.lf @@ -13,8 +13,8 @@ reactor foo { } reactor print { - state expected_time: time(10 msec) - state received: int(0) + state expected_time: time = 10 msec + state received: int = 0 input x: int reaction(x) {= diff --git a/test/C/src/AfterCycles.lf b/test/C/src/AfterCycles.lf index 3bc9622944..d7d621046b 100644 --- a/test/C/src/AfterCycles.lf +++ b/test/C/src/AfterCycles.lf @@ -16,7 +16,7 @@ reactor Work { } main reactor AfterCycles { - state count: int(0) + state count: int = 0 s = new Source() w0 = new Work() w1 = new Work() diff --git a/test/C/src/AfterOverlapped.lf b/test/C/src/AfterOverlapped.lf index 49d2c22bd2..0925dc5164 100644 --- a/test/C/src/AfterOverlapped.lf +++ b/test/C/src/AfterOverlapped.lf @@ -8,8 +8,8 @@ import Count from "lib/Count.lf" reactor Test { input c: int - state i: int(0) - state received: int(0) + state i: int = 0 + state received: int = 0 reaction(c) {= self->received++; diff --git a/test/C/src/AfterZero.lf b/test/C/src/AfterZero.lf index 85e1d2e000..378114c730 100644 --- a/test/C/src/AfterZero.lf +++ b/test/C/src/AfterZero.lf @@ -13,8 +13,8 @@ reactor foo { } reactor print { - state expected_time: time(0) - state received: int(0) + state expected_time: time = 0 + state received: int = 0 input x: int reaction(x) {= diff --git a/test/C/src/Alignment.lf b/test/C/src/Alignment.lf index 7420bd51c1..551b7c8342 100644 --- a/test/C/src/Alignment.lf +++ b/test/C/src/Alignment.lf @@ -7,7 +7,7 @@ target C { reactor Source { output out: int - state count: int(1) + state count: int = 1 timer t(0, 100 msec) reaction(t) -> out {= lf_set(out, self->count++); =} @@ -16,8 +16,8 @@ reactor Source { reactor Sieve { input in: int output out: bool - state primes: int*({= NULL =}) - state last_prime: int(0) + state primes: int* = {= NULL =} + state last_prime: int = 0 reaction(startup) {= // There are 1229 primes between 1 and 10,000. @@ -74,7 +74,7 @@ reactor Sieve { reactor Destination { input ok: bool input in: int - state last_invoked: tag_t({= NEVER_TAG_INITIALIZER =}) + state last_invoked: tag_t = {= NEVER_TAG_INITIALIZER =} reaction(ok, in) {= if (ok->is_present && in->is_present) { diff --git a/test/C/src/ArrayAsParameter.lf b/test/C/src/ArrayAsParameter.lf index d51778980e..a9df29cb03 100644 --- a/test/C/src/ArrayAsParameter.lf +++ b/test/C/src/ArrayAsParameter.lf @@ -3,9 +3,9 @@ // encode their own length. target C -reactor Source(sequence: int[](0, 1, 2), n_sequence: int(3)) { +reactor Source(sequence: int[] = {0, 1, 2}, n_sequence: int = 3) { output out: int - state count: int(0) + state count: int = 0 logical action next reaction(startup, next) -> out, next {= @@ -19,8 +19,8 @@ reactor Source(sequence: int[](0, 1, 2), n_sequence: int(3)) { reactor Print { input in: int - state count: int(1) - state received: int(0) + state count: int = 1 + state received: int = 0 reaction(in) {= self->received++; @@ -41,7 +41,7 @@ reactor Print { } main reactor ArrayAsParameter { - s = new Source(sequence = (1, 2, 3, 4), n_sequence = 4) + s = new Source(sequence = {1, 2, 3, 4}, n_sequence = 4) p = new Print() s.out -> p.in } diff --git a/test/C/src/ArrayAsType.lf b/test/C/src/ArrayAsType.lf index ad326c9288..c95e5bf385 100644 --- a/test/C/src/ArrayAsType.lf +++ b/test/C/src/ArrayAsType.lf @@ -13,7 +13,7 @@ reactor Source { =} } -reactor Print(scale: int(1)) { // The scale parameter is just for testing. +reactor Print(scale: int = 1) { // The scale parameter is just for testing. input in: int[3] reaction(in) {= diff --git a/test/C/src/ArrayFree.lf b/test/C/src/ArrayFree.lf index eea521aa72..11b31c123f 100644 --- a/test/C/src/ArrayFree.lf +++ b/test/C/src/ArrayFree.lf @@ -10,7 +10,7 @@ target C { import Source, Print from "ArrayPrint.lf" import Scale from "ArrayScale.lf" -reactor Free(scale: int(2), size: int(3)) { +reactor Free(scale: int = 2, size: int = 3) { mutable input in: int[] reaction(in) {= diff --git a/test/C/src/ArrayFreeMultiple.lf b/test/C/src/ArrayFreeMultiple.lf index b82bea9162..7b933ce518 100644 --- a/test/C/src/ArrayFreeMultiple.lf +++ b/test/C/src/ArrayFreeMultiple.lf @@ -12,7 +12,7 @@ import Print from "ArrayPrint.lf" reactor Source { output out: int[] - state c: int(0) + state c: int = 0 timer t(0, 1 sec) reaction(t) -> out {= @@ -26,7 +26,7 @@ reactor Source { =} } -reactor Free(scale: int(2)) { +reactor Free(scale: int = 2) { mutable input in: int[] reaction(in) {= diff --git a/test/C/src/ArrayPrint.lf b/test/C/src/ArrayPrint.lf index 33d75c6455..937bc51bea 100644 --- a/test/C/src/ArrayPrint.lf +++ b/test/C/src/ArrayPrint.lf @@ -6,9 +6,9 @@ target C { fast: true } -reactor Source(size: int(3)) { +reactor Source(size: int = 3) { output out: int[] - state count: int(0) + state count: int = 0 timer t(0, 1 sec) reaction(t) -> out {= @@ -26,9 +26,9 @@ reactor Source(size: int(3)) { } // The scale parameter is just for testing. -reactor Print(scale: int(1), size: int(3)) { +reactor Print(scale: int = 1, size: int = 3) { input in: int[] - state count: int(0) + state count: int = 0 reaction(in) {= bool failed = false; // For testing. diff --git a/test/C/src/ArrayScale.lf b/test/C/src/ArrayScale.lf index 257b0645c3..63edfc421c 100644 --- a/test/C/src/ArrayScale.lf +++ b/test/C/src/ArrayScale.lf @@ -9,7 +9,7 @@ target C { import Print, Source from "ArrayPrint.lf" -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { mutable input in: int[] output out: int[] diff --git a/test/C/src/CharLiteralInitializer.lf b/test/C/src/CharLiteralInitializer.lf index 9ee206efbf..319af0224c 100644 --- a/test/C/src/CharLiteralInitializer.lf +++ b/test/C/src/CharLiteralInitializer.lf @@ -2,7 +2,7 @@ target C main reactor CharLiteralInitializer { - state c: char('x') + state c: char = 'x' reaction(startup) {= if (self->c != 'x') { diff --git a/test/C/src/Composition.lf b/test/C/src/Composition.lf index c450bc0a64..7c024a9eba 100644 --- a/test/C/src/Composition.lf +++ b/test/C/src/Composition.lf @@ -5,10 +5,10 @@ target C { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -19,7 +19,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= (self->count)++; diff --git a/test/C/src/CompositionAfter.lf b/test/C/src/CompositionAfter.lf index 43f604de06..cb98ef7d7e 100644 --- a/test/C/src/CompositionAfter.lf +++ b/test/C/src/CompositionAfter.lf @@ -5,10 +5,10 @@ target C { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -18,7 +18,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= (self->count)++; @@ -30,7 +30,7 @@ reactor Test { =} } -main reactor CompositionAfter(delay: time(5 sec)) { +main reactor CompositionAfter(delay: time = 5 sec) { s = new Source() d = new Test() s.y -> d.x after delay diff --git a/test/C/src/CompositionInheritance.lf b/test/C/src/CompositionInheritance.lf index f57c1a688e..06221e9788 100644 --- a/test/C/src/CompositionInheritance.lf +++ b/test/C/src/CompositionInheritance.lf @@ -5,11 +5,11 @@ target C { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { input foo: int output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= printf("Hello World. At time %lld, my count is: %d.\n", lf_time_logical_elapsed(), self->count); @@ -30,7 +30,7 @@ reactor SourceExtended extends Source { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= (self->count)++; diff --git a/test/C/src/CountSelf.lf b/test/C/src/CountSelf.lf index 1a7a6d1ff2..1ef27187ae 100644 --- a/test/C/src/CountSelf.lf +++ b/test/C/src/CountSelf.lf @@ -6,7 +6,7 @@ target C { import TestCount from "lib/TestCount.lf" -reactor CountSelf2(delay: time(100 msec)) { +reactor CountSelf2(delay: time = 100 msec) { output out: int logical action a: int diff --git a/test/C/src/Deadline.lf b/test/C/src/Deadline.lf index 62116353a5..c72dc16dcf 100644 --- a/test/C/src/Deadline.lf +++ b/test/C/src/Deadline.lf @@ -5,10 +5,10 @@ target C { timeout: 6 sec } -reactor Source(period: time(3 sec)) { +reactor Source(period: time = 3 sec) { output y: int timer t(0, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= if (2 * (self->count / 2) != self->count) { @@ -22,9 +22,9 @@ reactor Source(period: time(3 sec)) { =} } -reactor Destination(timeout: time(1 sec)) { +reactor Destination(timeout: time = 1 sec) { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= printf("Destination receives: %d\n", x->value); diff --git a/test/C/src/DeadlineHandledAbove.lf b/test/C/src/DeadlineHandledAbove.lf index d186bf9016..92d7e9dac8 100644 --- a/test/C/src/DeadlineHandledAbove.lf +++ b/test/C/src/DeadlineHandledAbove.lf @@ -2,7 +2,7 @@ // container reacts to that output. target C -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { input x: int output deadline_violation: bool @@ -16,7 +16,7 @@ reactor Deadline(threshold: time(100 msec)) { } main reactor DeadlineHandledAbove { - state violation_detected: bool(false) + state violation_detected: bool = false d = new Deadline(threshold = 10 msec) reaction(startup) -> d.x {= diff --git a/test/C/src/DeadlineWithBanks.lf b/test/C/src/DeadlineWithBanks.lf index 266208cdac..18bc8c3682 100644 --- a/test/C/src/DeadlineWithBanks.lf +++ b/test/C/src/DeadlineWithBanks.lf @@ -11,7 +11,7 @@ target C { preamble {= volatile int global_cnt = 0; =} -reactor Bank(bank_index: int(0)) { +reactor Bank(bank_index: int = 0) { timer t(0, 100 msec) output out: int @@ -46,7 +46,7 @@ reactor Bank(bank_index: int(0)) { =} } -reactor Sink(dead: time(0)) { +reactor Sink(dead: time = 0) { input in: int reaction(in) {= =} deadline(dead) {= =} diff --git a/test/C/src/DeadlineZero.lf b/test/C/src/DeadlineZero.lf index 4e946bb63d..3ae49a9409 100644 --- a/test/C/src/DeadlineZero.lf +++ b/test/C/src/DeadlineZero.lf @@ -5,7 +5,7 @@ target C { reactor Detector { input trigger: int - state cnt: int(0) + state cnt: int = 0 reaction(trigger) {= printf("ERROR: failed to detect zero-duration deadline at iteration %d.\n", self->cnt); diff --git a/test/C/src/DelayArray.lf b/test/C/src/DelayArray.lf index 19e49e2464..72eb734888 100644 --- a/test/C/src/DelayArray.lf +++ b/test/C/src/DelayArray.lf @@ -4,7 +4,7 @@ target C { build-type: RelWithDebInfo } -reactor DelayPointer(delay: time(100 msec)) { +reactor DelayPointer(delay: time = 100 msec) { input in: int[] output out: int[] logical action a: int[] @@ -30,7 +30,7 @@ reactor Source { =} } -reactor Print(scale: int(1)) { // The scale parameter is just for testing. +reactor Print(scale: int = 1) { // The scale parameter is just for testing. input in: int[] reaction(in) {= diff --git a/test/C/src/DelayArrayWithAfter.lf b/test/C/src/DelayArrayWithAfter.lf index 42d7ff5275..c94be3b9a1 100644 --- a/test/C/src/DelayArrayWithAfter.lf +++ b/test/C/src/DelayArrayWithAfter.lf @@ -7,7 +7,7 @@ target C { reactor Source { output out: int[] - state iteration: int(1) + state iteration: int = 1 timer t(0, 1 sec) reaction(t) -> out {= @@ -23,10 +23,10 @@ reactor Source { =} } -reactor Print(scale: int(1)) { // The scale parameter is just for testing. +reactor Print(scale: int = 1) { // The scale parameter is just for testing. input in: int[] - state iteration: int(1) - state inputs_received: int(0) + state iteration: int = 1 + state inputs_received: int = 0 reaction(in) {= self->inputs_received++; diff --git a/test/C/src/DelayInt.lf b/test/C/src/DelayInt.lf index c7c27132bc..78ca793716 100644 --- a/test/C/src/DelayInt.lf +++ b/test/C/src/DelayInt.lf @@ -1,7 +1,7 @@ // This tests actions with payloads by delaying an input by a fixed amount. target C -reactor Delay(delay: time(100 msec)) { +reactor Delay(delay: time = 100 msec) { input in: int output out: int logical action a: int @@ -18,8 +18,8 @@ reactor Delay(delay: time(100 msec)) { reactor Test { input in: int - state start_time: time(0) - state received_value: bool(false) + state start_time: time = 0 + state received_value: bool = false reaction(startup) {= // Record the logical time at the start. diff --git a/test/C/src/DelayPointer.lf b/test/C/src/DelayPointer.lf index 87455d32ec..c6313c199d 100644 --- a/test/C/src/DelayPointer.lf +++ b/test/C/src/DelayPointer.lf @@ -1,7 +1,7 @@ // Test delaying a pointer type. target C -reactor DelayPointer2(delay: time(100 msec)) { +reactor DelayPointer2(delay: time = 100 msec) { input in: int* output out: int* logical action a: int* @@ -30,8 +30,8 @@ reactor Source { reactor Test { input in: int* - state start_time: time(0) - state received_value: bool(false) + state start_time: time = 0 + state received_value: bool = false reaction(startup) {= // Record the logical time at the start. diff --git a/test/C/src/DelayString.lf b/test/C/src/DelayString.lf index 1c77190581..b14e680d72 100644 --- a/test/C/src/DelayString.lf +++ b/test/C/src/DelayString.lf @@ -2,7 +2,7 @@ // freed. target C -reactor DelayString2(delay: time(100 msec)) { +reactor DelayString2(delay: time = 100 msec) { input in: string output out: string logical action a: string @@ -17,7 +17,7 @@ reactor DelayString2(delay: time(100 msec)) { reactor Test { input in: string - state start_time: time(0) + state start_time: time = 0 reaction(in) {= printf("Received: %s.\n", in->value); diff --git a/test/C/src/DelayStruct.lf b/test/C/src/DelayStruct.lf index a92acdc483..9cd201aa11 100644 --- a/test/C/src/DelayStruct.lf +++ b/test/C/src/DelayStruct.lf @@ -7,7 +7,7 @@ preamble {= #include "hello.h" =} -reactor DelayPointer(delay: time(100 msec)) { +reactor DelayPointer(delay: time = 100 msec) { input in: hello_t* output out: hello_t* logical action a: hello_t* @@ -38,7 +38,7 @@ reactor Source { =} } -reactor Print(expected: int(42)) { // expected parameter is for testing. +reactor Print(expected: int = 42) { // expected parameter is for testing. input in: hello_t* reaction(in) {= diff --git a/test/C/src/DelayStructWithAfter.lf b/test/C/src/DelayStructWithAfter.lf index 14f24aaf88..6693a437e6 100644 --- a/test/C/src/DelayStructWithAfter.lf +++ b/test/C/src/DelayStructWithAfter.lf @@ -20,7 +20,7 @@ reactor Source { =} } -reactor Print(expected: int(42)) { // expected parameter is for testing. +reactor Print(expected: int = 42) { // expected parameter is for testing. input in: hello_t* reaction(in) {= diff --git a/test/C/src/DelayStructWithAfterOverlapped.lf b/test/C/src/DelayStructWithAfterOverlapped.lf index 671b1ca4b3..8161022653 100644 --- a/test/C/src/DelayStructWithAfterOverlapped.lf +++ b/test/C/src/DelayStructWithAfterOverlapped.lf @@ -12,7 +12,7 @@ preamble {= reactor Source { output out: hello_t* timer t(0, 1 sec) - state s: int(0) + state s: int = 0 reaction(t) -> out {= self->s++; @@ -27,7 +27,7 @@ reactor Source { reactor Print { // expected parameter is for testing. input in: hello_t* - state s: int(0) + state s: int = 0 reaction(in) {= self->s++; diff --git a/test/C/src/DelayedAction.lf b/test/C/src/DelayedAction.lf index b37f6b9710..28a3e81ac5 100644 --- a/test/C/src/DelayedAction.lf +++ b/test/C/src/DelayedAction.lf @@ -6,7 +6,7 @@ target C { main reactor DelayedAction { timer t(0, 1 sec) logical action a - state count: int(0) + state count: int = 0 reaction(t) -> a {= lf_schedule(a, MSEC(100)); =} diff --git a/test/C/src/DoubleInvocation.lf b/test/C/src/DoubleInvocation.lf index 4b60f26fa8..973c8b18f6 100644 --- a/test/C/src/DoubleInvocation.lf +++ b/test/C/src/DoubleInvocation.lf @@ -13,7 +13,7 @@ target C { reactor Ball { output position: int output velocity: int - state p: int(200) + state p: int = 200 timer trigger(0, 1 sec) reaction(trigger) -> position, velocity {= @@ -26,7 +26,7 @@ reactor Ball { reactor Print { input velocity: int input position: int - state previous: int(-1) + state previous: int = -1 reaction(startup) {= printf("####### Print startup\n"); diff --git a/test/C/src/DoublePort.lf b/test/C/src/DoublePort.lf index 97d18f1761..c8bf9811f8 100644 --- a/test/C/src/DoublePort.lf +++ b/test/C/src/DoublePort.lf @@ -13,7 +13,7 @@ target C { import Count from "lib/Count.lf" reactor CountMicrostep { - state count: int(1) + state count: int = 1 output out: int logical action act: int timer t(0, 1 sec) diff --git a/test/C/src/DoubleReaction.lf b/test/C/src/DoubleReaction.lf index fb6aaaabaa..8da6210825 100644 --- a/test/C/src/DoubleReaction.lf +++ b/test/C/src/DoubleReaction.lf @@ -5,10 +5,10 @@ target C { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -19,7 +19,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int input w: int - state s: int(2) + state s: int = 2 reaction(x, w) {= int sum = 0; diff --git a/test/C/src/DoubleTrigger.lf b/test/C/src/DoubleTrigger.lf index 201ac35d7c..68ad603168 100644 --- a/test/C/src/DoubleTrigger.lf +++ b/test/C/src/DoubleTrigger.lf @@ -8,7 +8,7 @@ target C { main reactor DoubleTrigger { timer t1 timer t2 - state s: int(0) + state s: int = 0 reaction(t1, t2) {= self->s++; diff --git a/test/C/src/FloatLiteral.lf b/test/C/src/FloatLiteral.lf index 6d55f6e988..576a20d1ac 100644 --- a/test/C/src/FloatLiteral.lf +++ b/test/C/src/FloatLiteral.lf @@ -6,10 +6,10 @@ main reactor { #include =} - state N: double(6.0221409e+23) - state charge: double(-1.6021766E-19) - state minus_epsilon: double(-.01e0) - state expected: double(.964853323188E5) + state N: double = 6.0221409e+23 + state charge: double = -1.6021766E-19 + state minus_epsilon: double = -.01e0 + state expected: double = .964853323188E5 reaction(startup) {= double F = - self->N * self->charge; diff --git a/test/C/src/Gain.lf b/test/C/src/Gain.lf index 4096ec5973..7cac5eaae6 100644 --- a/test/C/src/Gain.lf +++ b/test/C/src/Gain.lf @@ -1,7 +1,7 @@ // Example in the Wiki. target C -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { input x: int output y: int @@ -10,7 +10,7 @@ reactor Scale(scale: int(2)) { reactor Test { input x: int - state received_value: bool(false) + state received_value: bool = false reaction(x) {= printf("Received %d.\n", x->value); diff --git a/test/C/src/GetMicroStep.lf b/test/C/src/GetMicroStep.lf index 4c7094c42e..0a428fd07d 100644 --- a/test/C/src/GetMicroStep.lf +++ b/test/C/src/GetMicroStep.lf @@ -2,7 +2,7 @@ target C main reactor GetMicroStep { - state s: int(1) + state s: int = 1 logical action l diff --git a/test/C/src/Hello.lf b/test/C/src/Hello.lf index f47b66ecc4..ea37265a23 100644 --- a/test/C/src/Hello.lf +++ b/test/C/src/Hello.lf @@ -8,9 +8,9 @@ target C { fast: true } -reactor Reschedule(period: time(2 sec), message: string("Hello C")) { - state count: int(0) - state previous_time: time(0) +reactor Reschedule(period: time = 2 sec, message: string = "Hello C") { + state count: int = 0 + state previous_time: time = 0 timer t(1 sec, period) logical action a @@ -43,8 +43,8 @@ reactor Reschedule(period: time(2 sec), message: string("Hello C")) { } reactor Inside( - period: time(1 sec), - message: string("Composite default message.") + period: time = 1 sec, + message: string = "Composite default message." ) { third_instance = new Reschedule(period = period, message = message) } diff --git a/test/C/src/HelloWorld.lf b/test/C/src/HelloWorld.lf index dc17cc0626..7de913c8ad 100644 --- a/test/C/src/HelloWorld.lf +++ b/test/C/src/HelloWorld.lf @@ -7,7 +7,7 @@ target C { } reactor HelloWorld2 { - state success: bool(false) + state success: bool = false reaction(startup) {= printf("Hello World.\n"); diff --git a/test/C/src/Hierarchy2.lf b/test/C/src/Hierarchy2.lf index 07fa4c73a0..0a4f20d4bc 100644 --- a/test/C/src/Hierarchy2.lf +++ b/test/C/src/Hierarchy2.lf @@ -14,7 +14,7 @@ reactor Source { reactor Count { output out: int timer t(0, 1 sec) - state i: int(0) + state i: int = 0 reaction(t) -> out {= (self->i)++; @@ -37,7 +37,7 @@ reactor Add { reactor Print { input in: int - state expected: int(2) + state expected: int = 2 reaction(in) {= printf("Received: %d.\n", in->value); diff --git a/test/C/src/IdentifierLength.lf b/test/C/src/IdentifierLength.lf index 69f1fcfd75..8fa38b96f0 100644 --- a/test/C/src/IdentifierLength.lf +++ b/test/C/src/IdentifierLength.lf @@ -5,10 +5,10 @@ target C { fast: true } -reactor A_Really_Long_Name_For_A_Source(period: time(2 sec)) { +reactor A_Really_Long_Name_For_A_Source(period: time = 2 sec) { output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -18,7 +18,7 @@ reactor A_Really_Long_Name_For_A_Source(period: time(2 sec)) { reactor Another_Really_Long_Name_For_A_Test_Class { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= (self->count)++; diff --git a/test/C/src/ImportComposition.lf b/test/C/src/ImportComposition.lf index 023c9736b2..22a1bfef87 100644 --- a/test/C/src/ImportComposition.lf +++ b/test/C/src/ImportComposition.lf @@ -6,7 +6,7 @@ import ImportedComposition from "lib/ImportedComposition.lf" main reactor ImportComposition { a = new ImportedComposition() - state received: bool(false) + state received: bool = false reaction(startup) -> a.x {= lf_set(a.x, 42); =} diff --git a/test/C/src/InheritanceAction.lf b/test/C/src/InheritanceAction.lf index 3941882f2c..680ffe2f75 100644 --- a/test/C/src/InheritanceAction.lf +++ b/test/C/src/InheritanceAction.lf @@ -17,7 +17,7 @@ reactor SourceExtended extends Source { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= (self->count)++; diff --git a/test/C/src/ManualDelayedReaction.lf b/test/C/src/ManualDelayedReaction.lf index 1402e00c62..72c57105ce 100644 --- a/test/C/src/ManualDelayedReaction.lf +++ b/test/C/src/ManualDelayedReaction.lf @@ -9,7 +9,7 @@ target C { reactor GeneratedDelay { input y_in: int output y_out: int - state y_state: int(0) + state y_state: int = 0 physical action act(0 msec) // TODO: delay in act or the schedule call? diff --git a/test/C/src/Methods.lf b/test/C/src/Methods.lf index 02f1f68c8e..edbdd2f7f6 100644 --- a/test/C/src/Methods.lf +++ b/test/C/src/Methods.lf @@ -1,7 +1,7 @@ target C main reactor { - state foo: int(2) + state foo: int = 2 method getFoo(): int {= return self->foo; =} diff --git a/test/C/src/MethodsRecursive.lf b/test/C/src/MethodsRecursive.lf index 73588e5115..5465e0c92c 100644 --- a/test/C/src/MethodsRecursive.lf +++ b/test/C/src/MethodsRecursive.lf @@ -2,7 +2,7 @@ target C main reactor { - state foo: int(2) + state foo: int = 2 method fib(n: int): int {= // Return the n-th Fibonacci number. if (n <= 1) return 1; diff --git a/test/C/src/MethodsSameName.lf b/test/C/src/MethodsSameName.lf index d2978ba4db..b274d153c8 100644 --- a/test/C/src/MethodsSameName.lf +++ b/test/C/src/MethodsSameName.lf @@ -2,7 +2,7 @@ target C reactor Foo { - state foo: int(2) + state foo: int = 2 method add(x: int) {= self->foo += x; =} @@ -16,7 +16,7 @@ reactor Foo { } main reactor { - state foo: int(2) + state foo: int = 2 a = new Foo() diff --git a/test/C/src/MovingAverage.lf b/test/C/src/MovingAverage.lf index cd135dbd26..a8309aa9d3 100644 --- a/test/C/src/MovingAverage.lf +++ b/test/C/src/MovingAverage.lf @@ -10,7 +10,7 @@ import TestDouble from "lib/Test.lf" reactor MASource { output out: double - state count: int(0) + state count: int = 0 timer clock(0, 200 msec) reaction(clock) -> out {= @@ -20,8 +20,8 @@ reactor MASource { } reactor MovingAverageImpl { - state delay_line: double[](0.0, 0.0, 0.0) - state index: int(0) + state delay_line: double[] = {0.0, 0.0, 0.0} + state index: int = 0 input in: double output out: double @@ -47,7 +47,7 @@ reactor MovingAverageImpl { main reactor MovingAverage { s = new MASource() m = new MovingAverageImpl() - p = new TestDouble(expected = (0.0, 0.25, 0.75, 1.5, 2.5, 3.5)) + p = new TestDouble(expected = {0.0, 0.25, 0.75, 1.5, 2.5, 3.5}) s.out -> m.in m.out -> p.in } diff --git a/test/C/src/MultipleContained.lf b/test/C/src/MultipleContained.lf index f3058c9dd4..540052ba87 100644 --- a/test/C/src/MultipleContained.lf +++ b/test/C/src/MultipleContained.lf @@ -6,7 +6,7 @@ reactor Contained { output trigger: int input in1: int input in2: int - state count: int(0) + state count: int = 0 reaction(startup) -> trigger {= lf_set(trigger, 42); =} diff --git a/test/C/src/MultipleOutputs.lf b/test/C/src/MultipleOutputs.lf index 8db9e5e6f9..37b5ddba9f 100644 --- a/test/C/src/MultipleOutputs.lf +++ b/test/C/src/MultipleOutputs.lf @@ -20,7 +20,7 @@ reactor C { main reactor { c = new C() - state triggered: bool(true) + state triggered: bool = true reaction(c.z) {= lf_print("c.z = %d", c.z->value); diff --git a/test/C/src/NativeListsAndTimes.lf b/test/C/src/NativeListsAndTimes.lf index eb3658f8a4..a1d09beb6c 100644 --- a/test/C/src/NativeListsAndTimes.lf +++ b/test/C/src/NativeListsAndTimes.lf @@ -2,22 +2,24 @@ target C // This test passes if it is successfully compiled into valid target code. main reactor( - x: int(0), - y: time(0), // Units are missing but not required - z(1 msec), // Type is missing but not required - p: int[](1, 2, 3, 4), // List of integers - q: interval_t[](1 msec, 2 msec, 3 msec), // list of time values - g: time[](1 msec, 2 msec) // List of time values + x: int = 0, + y: time = 0, // Units are missing but not required + z = 1 msec, // Type is missing but not required + p: int[] = {1, 2, 3, 4}, // List of integers + q: interval_t[] = {1 msec, 2 msec, 3 msec}, // list of time values + g: time[] = {1 msec, 2 msec} // List of time values ) { - state s: time(y) // Reference to explicitly typed time parameter - state t: time(z) // Reference to implicitly typed time parameter + state s: time = y // Reference to explicitly typed time parameter + state t: time = z // Reference to implicitly typed time parameter state v: bool // Uninitialized boolean state variable state w: time // Uninitialized time state variable timer tick(0) // Units missing but not required timer tock(1 sec) // Implicit type time timer toe(z) // Implicit type time - state baz(p) // Implicit type int[] - state period(z) // Implicit type time + state baz = p // Implicit type int[] + state period = z // Implicit type time + + state empty_list: int[] reaction(tick) {= // Target code diff --git a/test/C/src/NestedTriggeredReactions.lf b/test/C/src/NestedTriggeredReactions.lf index 6cc5f911b1..df52d36a14 100644 --- a/test/C/src/NestedTriggeredReactions.lf +++ b/test/C/src/NestedTriggeredReactions.lf @@ -3,7 +3,7 @@ target C reactor Container { input in: bool - state triggered: bool(false) + state triggered: bool = false contained = new Contained() @@ -21,7 +21,7 @@ reactor Container { reactor Contained { input in: bool - state triggered: bool(false) + state triggered: bool = false reaction(in) {= self->triggered = true; =} diff --git a/test/C/src/ParameterHierarchy.lf b/test/C/src/ParameterHierarchy.lf index 433948bd2b..825f9f5a60 100644 --- a/test/C/src/ParameterHierarchy.lf +++ b/test/C/src/ParameterHierarchy.lf @@ -1,24 +1,24 @@ // Test that parameter values pass down a deep hierarchy. target C -reactor Deep(p: int(0)) { +reactor Deep(p0: int = 0) { reaction(startup) {= - if (self->p != 42) { - lf_print_error_and_exit("Parameter value is %d. Should have been 42."); + if (self->p0 != 42) { + lf_print_error_and_exit("Parameter value is %d. Should have been 42.", self->p0); } else { lf_print("Success."); } =} } -reactor Intermediate(p: int(10)) { - a = new Deep(p = p) +reactor Intermediate(p1: int = 10) { + a0 = new Deep(p0 = p1) } -reactor Another(p: int(20)) { - a = new Intermediate(p = p) +reactor Another(p2: int = 20) { + a1 = new Intermediate(p1 = p2) } main reactor ParameterHierarchy { - a = new Intermediate(p = 42) + a2 = new Another(p2 = 42) } diff --git a/test/C/src/ParameterizedState.lf b/test/C/src/ParameterizedState.lf index 1965fa1929..3d92345ca9 100644 --- a/test/C/src/ParameterizedState.lf +++ b/test/C/src/ParameterizedState.lf @@ -1,7 +1,7 @@ target C -reactor Foo(bar: int(42)) { - state baz(bar) +reactor Foo(bar: int = 42) { + state baz = bar reaction(startup) {= printf("Baz: %d\n", self->baz); =} } diff --git a/test/C/src/PeriodicDesugared.lf b/test/C/src/PeriodicDesugared.lf index 6d9f8a8ab4..46eb97db9c 100644 --- a/test/C/src/PeriodicDesugared.lf +++ b/test/C/src/PeriodicDesugared.lf @@ -3,7 +3,7 @@ target C { timeout: 1 sec } -main reactor(offset: time(0), period: time(500 msec)) { +main reactor(offset: time = 0, period: time = 500 msec) { logical action init(offset) logical action recur(period) diff --git a/test/C/src/PingPong.lf b/test/C/src/PingPong.lf index 5ce6caaaaa..2f1699a51d 100644 --- a/test/C/src/PingPong.lf +++ b/test/C/src/PingPong.lf @@ -25,10 +25,10 @@ target C { fast: true } -reactor Ping(count: int(10)) { +reactor Ping(count: int = 10) { input receive: int output send: int - state pingsLeft: int(count) + state pingsLeft: int = count logical action serve reaction(startup, serve) -> send {= lf_set(send, self->pingsLeft--); =} @@ -42,10 +42,10 @@ reactor Ping(count: int(10)) { =} } -reactor Pong(expected: int(10)) { +reactor Pong(expected: int = 10) { input receive: int output send: int - state count: int(0) + state count: int = 0 reaction(receive) -> send {= self->count++; diff --git a/test/C/src/ReadOutputOfContainedReactor.lf b/test/C/src/ReadOutputOfContainedReactor.lf index 6b94d4a44b..ce2129069b 100644 --- a/test/C/src/ReadOutputOfContainedReactor.lf +++ b/test/C/src/ReadOutputOfContainedReactor.lf @@ -10,7 +10,7 @@ reactor Contained { main reactor ReadOutputOfContainedReactor { c = new Contained() - state count: int(0) + state count: int = 0 reaction(startup) c.out {= printf("Startup reaction reading output of contained reactor: %d.\n", c.out->value); diff --git a/test/C/src/ScheduleLogicalAction.lf b/test/C/src/ScheduleLogicalAction.lf index e3c898ebb5..762f8c6f52 100644 --- a/test/C/src/ScheduleLogicalAction.lf +++ b/test/C/src/ScheduleLogicalAction.lf @@ -20,7 +20,7 @@ reactor foo { } reactor print { - state expected_time: time(0) + state expected_time: time = 0 input x: int reaction(x) {= diff --git a/test/C/src/SelfLoop.lf b/test/C/src/SelfLoop.lf index 68ca61278e..855be46792 100644 --- a/test/C/src/SelfLoop.lf +++ b/test/C/src/SelfLoop.lf @@ -7,7 +7,7 @@ reactor Self { input x: int output y: int logical action a: int - state expected: int(43) + state expected: int = 43 reaction(a) -> y {= printf("a = %d\n", a->value); diff --git a/test/C/src/SendingInside.lf b/test/C/src/SendingInside.lf index fcb495305a..b1e8f25f93 100644 --- a/test/C/src/SendingInside.lf +++ b/test/C/src/SendingInside.lf @@ -7,7 +7,7 @@ target C { reactor Printer { input x: int - state count: int(1) + state count: int = 1 reaction(x) {= printf("Inside reactor received: %d\n", x->value); @@ -20,7 +20,7 @@ reactor Printer { } main reactor SendingInside { - state count: int(0) + state count: int = 0 timer t(0, 1 sec) p = new Printer() diff --git a/test/C/src/SendsPointerTest.lf b/test/C/src/SendsPointerTest.lf index c7336ff91f..1f17bd1388 100644 --- a/test/C/src/SendsPointerTest.lf +++ b/test/C/src/SendsPointerTest.lf @@ -13,7 +13,7 @@ reactor SendsPointer { =} } -reactor Print(expected: int(42)) { // expected parameter is for testing. +reactor Print(expected: int = 42) { // expected parameter is for testing. input in: int_pointer reaction(in) {= diff --git a/test/C/src/SetArray.lf b/test/C/src/SetArray.lf index 605eed4081..f30457f9f8 100644 --- a/test/C/src/SetArray.lf +++ b/test/C/src/SetArray.lf @@ -17,7 +17,7 @@ reactor Source { =} } -reactor Print(scale: int(1)) { // The scale parameter is just for testing. +reactor Print(scale: int = 1) { // The scale parameter is just for testing. input in: int[] reaction(in) {= diff --git a/test/C/src/SetToken.lf b/test/C/src/SetToken.lf index c2295dbaa0..b9af4bff20 100644 --- a/test/C/src/SetToken.lf +++ b/test/C/src/SetToken.lf @@ -10,7 +10,7 @@ reactor Source { reaction(a) -> out {= lf_set_token(out, a->token); =} } -reactor Print(expected: int(42)) { // expected parameter is for testing. +reactor Print(expected: int = 42) { // expected parameter is for testing. input in: int* reaction(in) {= diff --git a/test/C/src/SimpleDeadline.lf b/test/C/src/SimpleDeadline.lf index a59a6728e9..3e2d786eec 100644 --- a/test/C/src/SimpleDeadline.lf +++ b/test/C/src/SimpleDeadline.lf @@ -3,7 +3,7 @@ // violation. target C -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { input x: int output deadlineViolation: bool diff --git a/test/C/src/SlowingClock.lf b/test/C/src/SlowingClock.lf index 6655e87014..d4a1e1d5f4 100644 --- a/test/C/src/SlowingClock.lf +++ b/test/C/src/SlowingClock.lf @@ -11,8 +11,8 @@ target C { main reactor SlowingClock { logical action a(100 msec) - state interval: time(100 msec) - state expected_time: time(100 msec) + state interval: time = 100 msec + state expected_time: time = 100 msec reaction(startup) -> a {= lf_schedule(a, 0); =} diff --git a/test/C/src/SlowingClockPhysical.lf b/test/C/src/SlowingClockPhysical.lf index b3255428c2..0e82730112 100644 --- a/test/C/src/SlowingClockPhysical.lf +++ b/test/C/src/SlowingClockPhysical.lf @@ -11,8 +11,8 @@ target C { main reactor SlowingClockPhysical { physical action a(100 msec) - state interval: time(100 msec) - state expected_time: time(100 msec) + state interval: time = 100 msec + state expected_time: time = 100 msec reaction(startup) -> a {= self->expected_time = MSEC(100); diff --git a/test/C/src/Starvation.lf b/test/C/src/Starvation.lf index 38f2a247b6..a1c1167850 100644 --- a/test/C/src/Starvation.lf +++ b/test/C/src/Starvation.lf @@ -6,10 +6,10 @@ */ target C -reactor SuperDenseSender(number_of_iterations: int(10)) { +reactor SuperDenseSender(number_of_iterations: int = 10) { logical action loop output out: int - state iterator: int(0) + state iterator: int = 0 reaction(startup, loop) -> out {= if (self->iterator < self->number_of_iterations) { @@ -34,7 +34,7 @@ reactor SuperDenseSender(number_of_iterations: int(10)) { =} } -reactor SuperDenseReceiver(number_of_iterations: int(10)) { +reactor SuperDenseReceiver(number_of_iterations: int = 10) { input in: int reaction(in) {= diff --git a/test/C/src/Stop.lf b/test/C/src/Stop.lf index fcfa407d35..1731bced42 100644 --- a/test/C/src/Stop.lf +++ b/test/C/src/Stop.lf @@ -11,7 +11,7 @@ import Sender from "lib/LoopedActionSender.lf" reactor Consumer { input in: int - state reaction_invoked_correctly: bool(false) + state reaction_invoked_correctly: bool = false reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/StopZero.lf b/test/C/src/StopZero.lf index 316cce5c6c..e845c6de48 100644 --- a/test/C/src/StopZero.lf +++ b/test/C/src/StopZero.lf @@ -8,7 +8,7 @@ target C reactor Sender { output out: int - state reaction_invoked_correctly: bool(false) + state reaction_invoked_correctly: bool = false timer t(0, 1 usec) logical action act diff --git a/test/C/src/Stride.lf b/test/C/src/Stride.lf index 137855990d..62b9077878 100644 --- a/test/C/src/Stride.lf +++ b/test/C/src/Stride.lf @@ -5,8 +5,8 @@ target C { fast: true } -reactor Count(stride: int(1)) { - state count: int(1) +reactor Count(stride: int = 1) { + state count: int = 1 output y: int timer t(0, 100 msec) @@ -18,7 +18,7 @@ reactor Count(stride: int(1)) { reactor Display { input x: int - state expected: int(1) // for testing. + state expected: int = 1 // for testing. reaction(x) {= printf("Received: %d.\n", x->value); diff --git a/test/C/src/StructAsState.lf b/test/C/src/StructAsState.lf index b57b15d07b..59ae495853 100644 --- a/test/C/src/StructAsState.lf +++ b/test/C/src/StructAsState.lf @@ -10,7 +10,7 @@ main reactor StructAsState { } hello_t; =} // Notice that target code delimiters are no longer necessary. - state s: hello_t("Earth", 42) + state s: hello_t = {"Earth", 42} reaction(startup) {= printf("State s.name=\"%s\", value=%d.\n", self->s.name, self->s.value); diff --git a/test/C/src/StructAsType.lf b/test/C/src/StructAsType.lf index 9e43173110..8674685530 100644 --- a/test/C/src/StructAsType.lf +++ b/test/C/src/StructAsType.lf @@ -23,7 +23,7 @@ reactor Source { =} } -reactor Print(expected: int(42)) { // expected parameter is for testing. +reactor Print(expected: int = 42) { // expected parameter is for testing. input in: hello_t reaction(in) {= diff --git a/test/C/src/StructAsTypeDirect.lf b/test/C/src/StructAsTypeDirect.lf index f9d85799e9..29472f057b 100644 --- a/test/C/src/StructAsTypeDirect.lf +++ b/test/C/src/StructAsTypeDirect.lf @@ -17,7 +17,7 @@ reactor Source { =} } -reactor Print(expected: int(42)) { // expected parameter is for testing. +reactor Print(expected: int = 42) { // expected parameter is for testing. input in: hello_t reaction(in) {= diff --git a/test/C/src/StructParallel.lf b/test/C/src/StructParallel.lf index 0d3e78cc3e..3c8e61a3a2 100644 --- a/test/C/src/StructParallel.lf +++ b/test/C/src/StructParallel.lf @@ -11,9 +11,9 @@ preamble {= #include "hello.h" =} -reactor Check(expected: int(42)) { +reactor Check(expected: int = 42) { input in: hello_t* - state invoked: bool(false) + state invoked: bool = false reaction(in) {= printf("Received: name = %s, value = %d\n", in->value->name, in->value->value); @@ -32,7 +32,7 @@ reactor Check(expected: int(42)) { =} } -reactor Print(scale: int(2)) { +reactor Print(scale: int = 2) { // Mutable keyword indicates that this reactor wants a writable copy of the // input. mutable input in: hello_t* diff --git a/test/C/src/StructPrint.lf b/test/C/src/StructPrint.lf index adaf7fbf24..6af0d60c4f 100644 --- a/test/C/src/StructPrint.lf +++ b/test/C/src/StructPrint.lf @@ -20,7 +20,7 @@ reactor Print { =} } -reactor Check(expected: int(42)) { // expected parameter is for testing. +reactor Check(expected: int = 42) { // expected parameter is for testing. input in: hello_t* reaction(in) {= diff --git a/test/C/src/StructScale.lf b/test/C/src/StructScale.lf index 076d0e7f3f..3b92bab002 100644 --- a/test/C/src/StructScale.lf +++ b/test/C/src/StructScale.lf @@ -22,9 +22,9 @@ reactor Source { =} } -reactor TestInput(expected: int(42)) { // expected parameter is for testing. +reactor TestInput(expected: int = 42) { // expected parameter is for testing. input in: hello_t* - state invoked: bool(false) + state invoked: bool = false reaction(in) {= printf("Received: name = %s, value = %d\n", in->value->name, in->value->value); @@ -43,7 +43,7 @@ reactor TestInput(expected: int(42)) { // expected parameter is for testing. =} } -reactor Print(scale: int(2)) { +reactor Print(scale: int = 2) { // Mutable keyword indicates that this reactor wants a writable copy of the // input. mutable input in: hello_t* diff --git a/test/C/src/SubclassesAndStartup.lf b/test/C/src/SubclassesAndStartup.lf index a8001a907d..2dbc1f688f 100644 --- a/test/C/src/SubclassesAndStartup.lf +++ b/test/C/src/SubclassesAndStartup.lf @@ -1,7 +1,7 @@ target C reactor Super { - state count: int(0) + state count: int = 0 reaction(startup) {= printf("%s(Super) started\n", self->name); @@ -16,7 +16,7 @@ reactor Super { =} } -reactor SubA(name: string("SubA")) extends Super { +reactor SubA(name: string = "SubA") extends Super { reaction(startup) {= printf("%s started\n", self->name); if (self->count == 0) { @@ -26,7 +26,7 @@ reactor SubA(name: string("SubA")) extends Super { =} } -reactor SubB(name: string("SubB")) extends Super { +reactor SubB(name: string = "SubB") extends Super { reaction(startup) {= printf("%s started\n", self->name); if (self->count == 0) { diff --git a/test/C/src/TimeLimit.lf b/test/C/src/TimeLimit.lf index 2497296efd..ae178d53b5 100644 --- a/test/C/src/TimeLimit.lf +++ b/test/C/src/TimeLimit.lf @@ -5,10 +5,10 @@ target C { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -19,7 +19,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int - state s: int(1) + state s: int = 1 reaction(x) {= // printf("%d\n", x->value); @@ -39,7 +39,7 @@ reactor Destination { =} } -main reactor TimeLimit(period: time(1 sec)) { +main reactor TimeLimit(period: time = 1 sec) { timer stop(10 sec) c = new Clock(period = period) d = new Destination() diff --git a/test/C/src/TimeState.lf b/test/C/src/TimeState.lf index 790679ccda..cb0a9627f3 100644 --- a/test/C/src/TimeState.lf +++ b/test/C/src/TimeState.lf @@ -1,7 +1,7 @@ target C -reactor Foo(bar: int(42)) { - state baz: time(500 msec) +reactor Foo(bar: int = 42) { + state baz: time = 500 msec reaction(startup) {= printf("Baz: %lld\n", self->baz); =} } diff --git a/test/C/src/Timeout.lf b/test/C/src/Timeout.lf index e0bb841b3e..cb13eafba4 100644 --- a/test/C/src/Timeout.lf +++ b/test/C/src/Timeout.lf @@ -11,7 +11,7 @@ import Sender from "lib/LoopedActionSender.lf" reactor Consumer { input in: int - state success: bool(false) + state success: bool = false reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/TimeoutZero.lf b/test/C/src/TimeoutZero.lf index 36a65d6031..2b146bb57c 100644 --- a/test/C/src/TimeoutZero.lf +++ b/test/C/src/TimeoutZero.lf @@ -12,7 +12,7 @@ import Sender from "lib/LoopedActionSender.lf" reactor Consumer { input in: int - state success: bool(false) + state success: bool = false reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/ToReactionNested.lf b/test/C/src/ToReactionNested.lf index 4f89c20e9c..11b3577c84 100644 --- a/test/C/src/ToReactionNested.lf +++ b/test/C/src/ToReactionNested.lf @@ -12,8 +12,8 @@ reactor CountContainer { } main reactor { - state count: int(1) - state received: bool(false) + state count: int = 1 + state received: bool = false s = new CountContainer() diff --git a/test/C/src/TriggerDownstreamOnlyIfPresent2.lf b/test/C/src/TriggerDownstreamOnlyIfPresent2.lf index 544d269242..bc62b214e9 100644 --- a/test/C/src/TriggerDownstreamOnlyIfPresent2.lf +++ b/test/C/src/TriggerDownstreamOnlyIfPresent2.lf @@ -9,7 +9,7 @@ target C { reactor Source { output[2] out: int - state count: int(0) + state count: int = 0 timer t(0, 200 msec) reaction(t) -> out {= diff --git a/test/C/src/UnconnectedInput.lf b/test/C/src/UnconnectedInput.lf index f41524fa01..21e8cc5a69 100644 --- a/test/C/src/UnconnectedInput.lf +++ b/test/C/src/UnconnectedInput.lf @@ -7,7 +7,7 @@ target C { reactor Source { output out: int timer t(0, 1 sec) - state s: int(1) + state s: int = 1 reaction(t) -> out {= lf_set(out, self->s++); =} } @@ -27,7 +27,7 @@ reactor Add { reactor Print { input in: int - state expected: int(1) + state expected: int = 1 reaction(in) {= printf("Received: %d.\n", in->value); diff --git a/test/C/src/arduino/DigitalReadSerial.lf b/test/C/src/arduino/DigitalReadSerial.lf index febc640eb3..d79f86ad9b 100644 --- a/test/C/src/arduino/DigitalReadSerial.lf +++ b/test/C/src/arduino/DigitalReadSerial.lf @@ -11,7 +11,7 @@ target C { main reactor DigitalReadSerial { timer t1(0, 1 msec) - state pushButton: int(2) + state pushButton: int = 2 reaction(startup) {= pinMode(self->pushButton, INPUT); =} diff --git a/test/C/src/arduino/Fade.lf b/test/C/src/arduino/Fade.lf index 0408fb5a1a..d23aabd646 100644 --- a/test/C/src/arduino/Fade.lf +++ b/test/C/src/arduino/Fade.lf @@ -14,9 +14,9 @@ target C { main reactor Fade { timer t1(0, 30 msec) - state led: int(9) - state brightness: int(9) - state fadeAmount: int(5) + state led: int = 9 + state brightness: int = 9 + state fadeAmount: int = 5 reaction(startup) {= pinMode(self->led, OUTPUT); =} diff --git a/test/C/src/concurrent/AsyncCallback.lf b/test/C/src/concurrent/AsyncCallback.lf index 90c300a8b2..ec2beae766 100644 --- a/test/C/src/concurrent/AsyncCallback.lf +++ b/test/C/src/concurrent/AsyncCallback.lf @@ -33,12 +33,12 @@ main reactor AsyncCallback { lf_thread_t threadId; =} timer t(0, 200 msec) - state thread_id: lf_thread_t(0) - state expected_time: time(100 msec) - state toggle: bool(false) + state thread_id: lf_thread_t = 0 + state expected_time: time = 100 msec + state toggle: bool = false physical action a(100 msec): int - state i: int(0) + state i: int = 0 reaction(t) -> a {= // start new thread, provide callback diff --git a/test/C/src/concurrent/AsyncCallbackDrop.lf b/test/C/src/concurrent/AsyncCallbackDrop.lf index 17cb113a99..f123bcdbdf 100644 --- a/test/C/src/concurrent/AsyncCallbackDrop.lf +++ b/test/C/src/concurrent/AsyncCallbackDrop.lf @@ -28,12 +28,12 @@ main reactor { lf_thread_t threadId; =} timer t(0, 200 msec) - state thread_id: lf_thread_t(0) - state expected_time: time(100 msec) - state toggle: bool(false) + state thread_id: lf_thread_t = 0 + state expected_time: time = 100 msec + state toggle: bool = false physical action a(100 msec, 100 msec, "drop"): int - state i: int(0) + state i: int = 0 reaction(t) -> a {= // start new thread, provide callback diff --git a/test/C/src/concurrent/AsyncCallbackReplace.lf b/test/C/src/concurrent/AsyncCallbackReplace.lf index 49dc3e75e9..b7afeb2363 100644 --- a/test/C/src/concurrent/AsyncCallbackReplace.lf +++ b/test/C/src/concurrent/AsyncCallbackReplace.lf @@ -28,12 +28,12 @@ main reactor { lf_thread_t threadId; =} timer t(0, 200 msec) - state thread_id: lf_thread_t(0) - state expected_time: time(100 msec) - state toggle: bool(false) + state thread_id: lf_thread_t = 0 + state expected_time: time = 100 msec + state toggle: bool = false physical action a(100 msec, 100 msec, "replace"): int - state i: int(0) + state i: int = 0 reaction(t) -> a {= // start new thread, provide callback diff --git a/test/C/src/concurrent/CompositionThreaded.lf b/test/C/src/concurrent/CompositionThreaded.lf index decf56d687..cf0bafeb82 100644 --- a/test/C/src/concurrent/CompositionThreaded.lf +++ b/test/C/src/concurrent/CompositionThreaded.lf @@ -5,10 +5,10 @@ target C { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -18,7 +18,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= (self->count)++; diff --git a/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf b/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf index 87359286ed..545206de91 100644 --- a/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf +++ b/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf @@ -2,7 +2,7 @@ // container reacts to that output. target C -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { input x: int output deadline_violation: bool @@ -16,7 +16,7 @@ reactor Deadline(threshold: time(100 msec)) { } main reactor { - state violation_detected: bool(false) + state violation_detected: bool = false d = new Deadline(threshold = 10 msec) reaction(startup) -> d.x {= diff --git a/test/C/src/concurrent/DeadlineThreaded.lf b/test/C/src/concurrent/DeadlineThreaded.lf index fccb60001b..ac765fe6f7 100644 --- a/test/C/src/concurrent/DeadlineThreaded.lf +++ b/test/C/src/concurrent/DeadlineThreaded.lf @@ -5,10 +5,10 @@ target C { timeout: 6 sec } -reactor Source(period: time(3000 msec)) { +reactor Source(period: time = 3000 msec) { output y: int timer t(0, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= if (2 * (self->count / 2) != self->count) { @@ -22,9 +22,9 @@ reactor Source(period: time(3000 msec)) { =} } -reactor Destination(timeout: time(1 sec)) { +reactor Destination(timeout: time = 1 sec) { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= printf("Destination receives: %d\n", x->value); diff --git a/test/C/src/concurrent/DelayIntThreaded.lf b/test/C/src/concurrent/DelayIntThreaded.lf index 5c2c983508..449b17eb60 100644 --- a/test/C/src/concurrent/DelayIntThreaded.lf +++ b/test/C/src/concurrent/DelayIntThreaded.lf @@ -1,7 +1,7 @@ // This tests actions with payloads by delaying an input by a fixed amount. target C -reactor Delay(delay: time(100 msec)) { +reactor Delay(delay: time = 100 msec) { input in: int output out: int logical action a: int @@ -16,8 +16,8 @@ reactor Delay(delay: time(100 msec)) { reactor Test { input in: int - state start_time: time(0) - state received_value: bool(false) + state start_time: time = 0 + state received_value: bool = false reaction(startup) {= // Record the logical time at the start. diff --git a/test/C/src/concurrent/DoubleReactionThreaded.lf b/test/C/src/concurrent/DoubleReactionThreaded.lf index 66885ab7a0..ba6c38ca19 100644 --- a/test/C/src/concurrent/DoubleReactionThreaded.lf +++ b/test/C/src/concurrent/DoubleReactionThreaded.lf @@ -5,10 +5,10 @@ target C { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -19,7 +19,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int input w: int - state s: int(2) + state s: int = 2 reaction(x, w) {= int sum = 0; diff --git a/test/C/src/concurrent/GainThreaded.lf b/test/C/src/concurrent/GainThreaded.lf index b59d2b00fb..d9d6ec7673 100644 --- a/test/C/src/concurrent/GainThreaded.lf +++ b/test/C/src/concurrent/GainThreaded.lf @@ -1,7 +1,7 @@ // Example in the Wiki. target C -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { input x: int output y: int @@ -10,7 +10,7 @@ reactor Scale(scale: int(2)) { reactor Test { input x: int - state received_value: bool(false) + state received_value: bool = false reaction(x) {= printf("Received %d.\n", x->value); diff --git a/test/C/src/concurrent/HelloThreaded.lf b/test/C/src/concurrent/HelloThreaded.lf index 72c7560ede..a8ea34dd05 100644 --- a/test/C/src/concurrent/HelloThreaded.lf +++ b/test/C/src/concurrent/HelloThreaded.lf @@ -8,9 +8,9 @@ target C { fast: true } -reactor Reschedule(period: time(2 sec), message: string("Hello C")) { - state count: int(0) - state previous_time: time(0) +reactor Reschedule(period: time = 2 sec, message: string = "Hello C") { + state count: int = 0 + state previous_time: time = 0 timer t(1 sec, period) logical action a @@ -43,8 +43,8 @@ reactor Reschedule(period: time(2 sec), message: string("Hello C")) { } reactor Inside( - period: time(1 sec), - message: string("Composite default message.") + period: time = 1 sec, + message: string = "Composite default message." ) { third_instance = new Reschedule(period = period, message = message) } diff --git a/test/C/src/concurrent/PingPongThreaded.lf b/test/C/src/concurrent/PingPongThreaded.lf index 0f2e60f065..4fae17911e 100644 --- a/test/C/src/concurrent/PingPongThreaded.lf +++ b/test/C/src/concurrent/PingPongThreaded.lf @@ -25,10 +25,10 @@ target C { fast: true } -reactor Ping(count: int(10)) { +reactor Ping(count: int = 10) { input receive: int output send: int - state pingsLeft: int(count) + state pingsLeft: int = count logical action serve reaction(startup, serve) -> send {= lf_set(send, self->pingsLeft--); =} @@ -42,10 +42,10 @@ reactor Ping(count: int(10)) { =} } -reactor Pong(expected: int(10)) { +reactor Pong(expected: int = 10) { input receive: int output send: int - state count: int(0) + state count: int = 0 reaction(receive) -> send {= self->count++; diff --git a/test/C/src/concurrent/ScheduleAt.lf b/test/C/src/concurrent/ScheduleAt.lf index 7e5e8318b1..e68b3956e8 100644 --- a/test/C/src/concurrent/ScheduleAt.lf +++ b/test/C/src/concurrent/ScheduleAt.lf @@ -12,7 +12,7 @@ target C { reactor Scheduler { logical action act // List of microsteps. Size = 16 - state microstep_delay_list: uint32_t[]( + state microstep_delay_list: uint32_t[] = { 0, 1, 1, @@ -29,8 +29,9 @@ reactor Scheduler { 4, 4, 5 - ) - state times: int[]( + } + + state times: int[] = { 0, 0, 0, @@ -47,10 +48,10 @@ reactor Scheduler { 900 msec, 900 msec, 900 msec - ) + } // Size = 9 - state action_hit_list_microstep: int[](1, 2, 0, 1, 0, 2, 3, 4, 5) - state action_hit_list_times: int[]( + state action_hit_list_microstep: int[] = {1, 2, 0, 1, 0, 2, 3, 4, 5} + state action_hit_list_times: int[] = { 0, 0, 400 msec, @@ -60,9 +61,9 @@ reactor Scheduler { 800 msec, 900 msec, 900 msec - ) + } // Size = 9 - state action_hit_list_index: int(0) + state action_hit_list_index: int = 0 reaction(startup) -> act {= for (int i=0; i < 16; i++) { diff --git a/test/C/src/concurrent/ScheduleTwice.lf b/test/C/src/concurrent/ScheduleTwice.lf index 41512f3c7d..e19f4d02d5 100644 --- a/test/C/src/concurrent/ScheduleTwice.lf +++ b/test/C/src/concurrent/ScheduleTwice.lf @@ -2,7 +2,7 @@ target C main reactor ScheduleTwice { logical action a: int - state rc_count: int(0) + state rc_count: int = 0 preamble {= #define VERBOSE =} diff --git a/test/C/src/concurrent/ScheduleTwiceThreaded.lf b/test/C/src/concurrent/ScheduleTwiceThreaded.lf index a368bec1ec..499e0e4999 100644 --- a/test/C/src/concurrent/ScheduleTwiceThreaded.lf +++ b/test/C/src/concurrent/ScheduleTwiceThreaded.lf @@ -2,7 +2,7 @@ target C main reactor { logical action a: int - state rc_count: int(0) + state rc_count: int = 0 reaction(startup) -> a {= lf_schedule_int(a, MSEC(100), 42); diff --git a/test/C/src/concurrent/SendingInsideThreaded.lf b/test/C/src/concurrent/SendingInsideThreaded.lf index e08a5dffb5..e5185ecfc8 100644 --- a/test/C/src/concurrent/SendingInsideThreaded.lf +++ b/test/C/src/concurrent/SendingInsideThreaded.lf @@ -7,7 +7,7 @@ target C { reactor Printer { input x: int - state count: int(1) + state count: int = 1 reaction(x) {= printf("Inside reactor received: %d\n", x->value); @@ -20,7 +20,7 @@ reactor Printer { } main reactor SendingInsideThreaded { - state count: int(0) + state count: int = 0 timer t(0, 1 sec) p = new Printer() diff --git a/test/C/src/concurrent/StarvationThreaded.lf b/test/C/src/concurrent/StarvationThreaded.lf index 58d063647d..a16b5e46b6 100644 --- a/test/C/src/concurrent/StarvationThreaded.lf +++ b/test/C/src/concurrent/StarvationThreaded.lf @@ -7,10 +7,10 @@ */ target C -reactor SuperDenseSender(number_of_iterations: int(10)) { +reactor SuperDenseSender(number_of_iterations: int = 10) { logical action loop output out: int - state iterator: int(0) + state iterator: int = 0 reaction(startup, loop) -> out {= if (self->iterator < self->number_of_iterations) { @@ -35,7 +35,7 @@ reactor SuperDenseSender(number_of_iterations: int(10)) { =} } -reactor SuperDenseReceiver(number_of_iterations: int(10)) { +reactor SuperDenseReceiver(number_of_iterations: int = 10) { input in: int reaction(in) {= diff --git a/test/C/src/concurrent/StopThreaded.lf b/test/C/src/concurrent/StopThreaded.lf index aa69bda345..1deb1c4ff6 100644 --- a/test/C/src/concurrent/StopThreaded.lf +++ b/test/C/src/concurrent/StopThreaded.lf @@ -13,7 +13,7 @@ import Sender from "../lib/LoopedActionSender.lf" reactor Consumer { input in: int - state reaction_invoked_correctly: bool(false) + state reaction_invoked_correctly: bool = false reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/concurrent/StopZeroThreaded.lf b/test/C/src/concurrent/StopZeroThreaded.lf index 051c615566..7afd4b0069 100644 --- a/test/C/src/concurrent/StopZeroThreaded.lf +++ b/test/C/src/concurrent/StopZeroThreaded.lf @@ -8,7 +8,7 @@ target C reactor Sender { output out: int - state reaction_invoked_correctly: bool(false) + state reaction_invoked_correctly: bool = false timer t(0, 1 usec) logical action act diff --git a/test/C/src/concurrent/Threaded.lf b/test/C/src/concurrent/Threaded.lf index abfcff2c1b..01f67c1bd4 100644 --- a/test/C/src/concurrent/Threaded.lf +++ b/test/C/src/concurrent/Threaded.lf @@ -16,7 +16,7 @@ target C { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= lf_set(out, self->s); @@ -40,8 +40,8 @@ reactor TakeTime { =} } -reactor Destination(width: int(4)) { - state s: int(400000000) +reactor Destination(width: int = 4) { + state s: int = 400000000 input[width] in: int reaction(in) {= @@ -58,7 +58,7 @@ reactor Destination(width: int(4)) { =} } -main reactor(width: int(4)) { +main reactor(width: int = 4) { a = new Source() t = new[width] TakeTime() (a.out)+ -> t.in diff --git a/test/C/src/concurrent/ThreadedMultiport.lf b/test/C/src/concurrent/ThreadedMultiport.lf index b24ead7cd9..2878393dac 100644 --- a/test/C/src/concurrent/ThreadedMultiport.lf +++ b/test/C/src/concurrent/ThreadedMultiport.lf @@ -5,10 +5,10 @@ target C { flags: "" } -reactor Source(width: int(4)) { +reactor Source(width: int = 4) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < out_width; i++) { @@ -18,7 +18,7 @@ reactor Source(width: int(4)) { =} } -reactor Computation(iterations: int(100000000)) { +reactor Computation(iterations: int = 100000000) { input in: int output out: int @@ -34,8 +34,8 @@ reactor Computation(iterations: int(100000000)) { =} } -reactor Destination(width: int(4), iterations: int(100000000)) { - state s: int(0) +reactor Destination(width: int = 4, iterations: int = 100000000) { + state s: int = 0 input[width] in: int reaction(in) {= @@ -61,7 +61,7 @@ reactor Destination(width: int(4), iterations: int(100000000)) { =} } -main reactor ThreadedMultiport(width: int(4), iterations: int(100000000)) { +main reactor ThreadedMultiport(width: int = 4, iterations: int = 100000000) { a = new Source(width = width) t = new[width] Computation(iterations = iterations) b = new Destination(width = width, iterations = iterations) diff --git a/test/C/src/concurrent/ThreadedThreaded.lf b/test/C/src/concurrent/ThreadedThreaded.lf index 9db95faaf0..4037e8c193 100644 --- a/test/C/src/concurrent/ThreadedThreaded.lf +++ b/test/C/src/concurrent/ThreadedThreaded.lf @@ -15,7 +15,7 @@ target C { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= lf_set(out, self->s); @@ -39,8 +39,8 @@ reactor TakeTime { =} } -reactor Destination(width: int(4)) { - state s: int(400000000) +reactor Destination(width: int = 4) { + state s: int = 400000000 input[width] in: int reaction(in) {= @@ -57,7 +57,7 @@ reactor Destination(width: int(4)) { =} } -main reactor ThreadedThreaded(width: int(4)) { +main reactor ThreadedThreaded(width: int = 4) { a = new Source() t = new[width] TakeTime() (a.out)+ -> t.in diff --git a/test/C/src/concurrent/TimeLimitThreaded.lf b/test/C/src/concurrent/TimeLimitThreaded.lf index 97bb9cfec6..8d3824af47 100644 --- a/test/C/src/concurrent/TimeLimitThreaded.lf +++ b/test/C/src/concurrent/TimeLimitThreaded.lf @@ -4,10 +4,10 @@ target C { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -18,7 +18,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int - state s: int(1) + state s: int = 1 reaction(x) {= // printf("%d\n", x->value); @@ -38,7 +38,7 @@ reactor Destination { =} } -main reactor(period: time(1 sec)) { +main reactor(period: time = 1 sec) { timer stop(10 sec) c = new Clock(period = period) d = new Destination() diff --git a/test/C/src/concurrent/TimeoutThreaded.lf b/test/C/src/concurrent/TimeoutThreaded.lf index 993366a2c0..3a7e2181d7 100644 --- a/test/C/src/concurrent/TimeoutThreaded.lf +++ b/test/C/src/concurrent/TimeoutThreaded.lf @@ -12,7 +12,7 @@ import Sender from "../lib/LoopedActionSender.lf" reactor Consumer { input in: int - state success: bool(false) + state success: bool = false reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/concurrent/TimeoutZeroThreaded.lf b/test/C/src/concurrent/TimeoutZeroThreaded.lf index 4a0bdd62a3..9b24ef080f 100644 --- a/test/C/src/concurrent/TimeoutZeroThreaded.lf +++ b/test/C/src/concurrent/TimeoutZeroThreaded.lf @@ -12,7 +12,7 @@ import Sender from "../lib/LoopedActionSender.lf" reactor Consumer { input in: int - state success: bool(false) + state success: bool = false reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/concurrent/Tracing.lf b/test/C/src/concurrent/Tracing.lf index ff2f436e85..71da79c7ef 100644 --- a/test/C/src/concurrent/Tracing.lf +++ b/test/C/src/concurrent/Tracing.lf @@ -10,7 +10,7 @@ target C { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= lf_set(out, self->s); @@ -18,10 +18,10 @@ reactor Source { =} } -reactor TakeTime(bank_index: int(0)) { +reactor TakeTime(bank_index: int = 0) { input in: int output out: int - state event: char*("No ID") + state event: char* = "No ID" reaction(startup) {= // Construct an id string for a user trace event. @@ -57,9 +57,9 @@ reactor TakeTime(bank_index: int(0)) { =} } -reactor Destination(width: int(4)) { - state s: int(400000000) - state count: int(0) +reactor Destination(width: int = 4) { + state s: int = 400000000 + state count: int = 0 input[width] in: int reaction(startup) {= @@ -86,7 +86,7 @@ reactor Destination(width: int(4)) { =} } -main reactor(width: int(4)) { +main reactor(width: int = 4) { a = new Source() t = new[width] TakeTime() (a.out)+ -> t.in diff --git a/test/C/src/docker/federated/DistributedCountContainerized.lf b/test/C/src/docker/federated/DistributedCountContainerized.lf index 9eb6898a9d..4a120c5e1e 100644 --- a/test/C/src/docker/federated/DistributedCountContainerized.lf +++ b/test/C/src/docker/federated/DistributedCountContainerized.lf @@ -15,7 +15,9 @@ target C { import Count from "../../lib/Count.lf" import Print from "../../federated/DistributedCount.lf" -federated reactor DistributedCountContainerized(offset: time(200 msec)) at rti { +federated reactor DistributedCountContainerized( + offset: time = 200 msec +) at rti { c = new Count() p = new Print() c.out -> p.in after offset diff --git a/test/C/src/federated/BroadcastFeedback.lf b/test/C/src/federated/BroadcastFeedback.lf index 0c7146b8f7..2bef908d2d 100644 --- a/test/C/src/federated/BroadcastFeedback.lf +++ b/test/C/src/federated/BroadcastFeedback.lf @@ -9,7 +9,7 @@ target C { reactor SenderAndReceiver { output out: int input[2] in: int - state received: bool(false) + state received: bool = false reaction(startup) -> out {= lf_set(out, 42); =} diff --git a/test/C/src/federated/BroadcastFeedbackWithHierarchy.lf b/test/C/src/federated/BroadcastFeedbackWithHierarchy.lf index 1632f0332a..d9edbc954b 100644 --- a/test/C/src/federated/BroadcastFeedbackWithHierarchy.lf +++ b/test/C/src/federated/BroadcastFeedbackWithHierarchy.lf @@ -8,7 +8,7 @@ target C { reactor SenderAndReceiver { output out: int input[2] in: int - state received: bool(false) + state received: bool = false r = new Receiver() in -> r.in @@ -18,7 +18,7 @@ reactor SenderAndReceiver { reactor Receiver { input[2] in: int - state received: bool(false) + state received: bool = false reaction(in) {= if (in[0]->is_present && in[1]->is_present && in[0]->value == 42 && in[1]->value == 42) { diff --git a/test/C/src/federated/CycleDetection.lf b/test/C/src/federated/CycleDetection.lf index 90ce0faba2..d22837bc1f 100644 --- a/test/C/src/federated/CycleDetection.lf +++ b/test/C/src/federated/CycleDetection.lf @@ -10,7 +10,7 @@ reactor CAReplica { input remote_update: int input query: int - state balance: int(0) + state balance: int = 0 output response: int diff --git a/test/C/src/federated/DecentralizedP2PComm.lf b/test/C/src/federated/DecentralizedP2PComm.lf index 5791ddd8a7..0ca6a10e6f 100644 --- a/test/C/src/federated/DecentralizedP2PComm.lf +++ b/test/C/src/federated/DecentralizedP2PComm.lf @@ -6,15 +6,15 @@ target C { } reactor Platform( - start: int(0), - expected_start: int(0), - stp_offset_param: time(0) + start: int = 0, + expected_start: int = 0, + stp_offset_param: time = 0 ) { input in: int output out: int timer t(0, 100 msec) - state count: int(start) - state expected: int(expected_start) + state count: int = start + state expected: int = expected_start reaction(t) -> out {= lf_set(out, self->count++); =} diff --git a/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf b/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf index 3b872b8408..fca2149fcf 100644 --- a/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf +++ b/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf @@ -11,10 +11,10 @@ target C { coordination: decentralized } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -29,7 +29,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int - state s: int(1) + state s: int = 1 reaction(x) {= lf_print("Received %d", x->value); @@ -48,7 +48,7 @@ reactor Destination { =} } -federated reactor(period: time(10 usec)) { +federated reactor(period: time = 10 usec) { c = new Clock(period = period) d = new Destination() c.y -> d.x diff --git a/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf b/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf index 49562c7e77..8468e1dc81 100644 --- a/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf +++ b/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf @@ -12,10 +12,10 @@ target C { coordination: decentralized } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= (self->count)++; @@ -30,7 +30,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int - state s: int(1) + state s: int = 1 reaction(x) {= // printf("%d\n", x->value); @@ -46,7 +46,7 @@ reactor Destination { =} } -federated reactor(period: time(10 usec)) { +federated reactor(period: time = 10 usec) { c = new Clock(period = period) d = new Destination() c.y ~> d.x diff --git a/test/C/src/federated/DistributedBank.lf b/test/C/src/federated/DistributedBank.lf index 9dd1247980..84acb00031 100644 --- a/test/C/src/federated/DistributedBank.lf +++ b/test/C/src/federated/DistributedBank.lf @@ -6,7 +6,7 @@ target C { reactor Node { timer t(0, 100 msec) - state count: int(0) + state count: int = 0 reaction(t) {= lf_print("Hello world %d.", self->count++); =} diff --git a/test/C/src/federated/DistributedBankToMultiport.lf b/test/C/src/federated/DistributedBankToMultiport.lf index b392b3e603..796db0718e 100644 --- a/test/C/src/federated/DistributedBankToMultiport.lf +++ b/test/C/src/federated/DistributedBankToMultiport.lf @@ -7,7 +7,7 @@ import Count from "../lib/Count.lf" reactor Destination { input[2] in: int - state count: int(1) + state count: int = 1 reaction(in) {= for (int i = 0; i < in_width; i++) { diff --git a/test/C/src/federated/DistributedCount.lf b/test/C/src/federated/DistributedCount.lf index 7ffd4eb8e2..2ebdda169a 100644 --- a/test/C/src/federated/DistributedCount.lf +++ b/test/C/src/federated/DistributedCount.lf @@ -14,7 +14,7 @@ import Count from "../lib/Count.lf" reactor Print { input in: int - state c: int(1) + state c: int = 1 reaction(in) {= interval_t elapsed_time = lf_time_logical_elapsed(); @@ -35,7 +35,7 @@ reactor Print { =} } -federated reactor DistributedCount(offset: time(200 msec)) { +federated reactor DistributedCount(offset: time = 200 msec) { c = new Count() p = new Print() c.out -> p.in after offset diff --git a/test/C/src/federated/DistributedCountDecentralized.lf b/test/C/src/federated/DistributedCountDecentralized.lf index e7ee0a7d77..631dfab0c0 100644 --- a/test/C/src/federated/DistributedCountDecentralized.lf +++ b/test/C/src/federated/DistributedCountDecentralized.lf @@ -14,7 +14,7 @@ import Count from "../lib/Count.lf" reactor Print { input in: int - state c: int(1) + state c: int = 1 reaction(in) {= interval_t elapsed_time = lf_time_logical_elapsed(); diff --git a/test/C/src/federated/DistributedCountDecentralizedLate.lf b/test/C/src/federated/DistributedCountDecentralizedLate.lf index fa85987e87..9fb6d3b064 100644 --- a/test/C/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/C/src/federated/DistributedCountDecentralizedLate.lf @@ -15,12 +15,12 @@ import Count from "../lib/Count.lf" reactor Print { input in: int // STP () - state success: int(0) // STP(in, 30 msec); - state success_stp_violation: int(0) + state success: int = 0 // STP(in, 30 msec); + state success_stp_violation: int = 0 // Force a timer to be invoke periodically to ensure logical time will // advance in the absence of incoming messages. timer t(0, 10 msec) - state c: int(0) + state c: int = 0 reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf b/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf index 26c20521e3..31ab87e110 100644 --- a/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf +++ b/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf @@ -17,12 +17,12 @@ import Count from "../lib/Count.lf" reactor ImportantActuator { input in: int - state success: int(0) - state success_stp_violation: int(0) + state success: int = 0 + state success_stp_violation: int = 0 // Force a timer to be invoke periodically timer t(0, 10 msec) // to ensure logical time will advance in the absence of incoming messages. - state c: int(0) + state c: int = 0 reaction(in) {= tag_t current_tag = lf_tag(); @@ -79,7 +79,7 @@ reactor Receiver { // Force a timer to be invoke periodically timer t(0, 10 msec) // to ensure logical time will advance in the absence of incoming messages. - state c: int(0) + state c: int = 0 p = new Print() a = new ImportantActuator() in -> p.in diff --git a/test/C/src/federated/DistributedCountPhysical.lf b/test/C/src/federated/DistributedCountPhysical.lf index 67d90ec4c1..9e1e15f775 100644 --- a/test/C/src/federated/DistributedCountPhysical.lf +++ b/test/C/src/federated/DistributedCountPhysical.lf @@ -13,7 +13,7 @@ target C { reactor Count { timer t(200 msec, 1 sec) - state s: int(0) + state s: int = 0 output out: int reaction(t) -> out {= @@ -24,7 +24,7 @@ reactor Count { reactor Print { input in: int - state c: int(0) + state c: int = 0 reaction(in) {= interval_t elapsed_time = lf_time_logical_elapsed(); diff --git a/test/C/src/federated/DistributedCountPhysicalAfterDelay.lf b/test/C/src/federated/DistributedCountPhysicalAfterDelay.lf index 2d13891bae..b0da42218a 100644 --- a/test/C/src/federated/DistributedCountPhysicalAfterDelay.lf +++ b/test/C/src/federated/DistributedCountPhysicalAfterDelay.lf @@ -12,7 +12,7 @@ import Count from "../lib/Count.lf" reactor Print { input in: int - state c: int(1) + state c: int = 1 reaction(in) {= interval_t elapsed_time = lf_time_logical_elapsed(); diff --git a/test/C/src/federated/DistributedCountPhysicalDecentralized.lf b/test/C/src/federated/DistributedCountPhysicalDecentralized.lf index 6bc50fce88..e821e4fb70 100644 --- a/test/C/src/federated/DistributedCountPhysicalDecentralized.lf +++ b/test/C/src/federated/DistributedCountPhysicalDecentralized.lf @@ -14,7 +14,7 @@ target C { reactor Count { timer t(200 msec, 1 sec) - state s: int(0) + state s: int = 0 output out: int reaction(t) -> out {= @@ -25,7 +25,7 @@ reactor Count { reactor Print { input in: int - state c: int(0) + state c: int = 0 reaction(in) {= interval_t elapsed_time = lf_time_logical_elapsed(); diff --git a/test/C/src/federated/DistributedDoublePort.lf b/test/C/src/federated/DistributedDoublePort.lf index 2490df67bf..87de1ddb28 100644 --- a/test/C/src/federated/DistributedDoublePort.lf +++ b/test/C/src/federated/DistributedDoublePort.lf @@ -14,7 +14,7 @@ target C { import Count from "../lib/Count.lf" reactor CountMicrostep { - state count: int(1) + state count: int = 1 output out: int logical action act: int timer t(0, 1 sec) diff --git a/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf b/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf index 078b15ed02..acbbaf4035 100644 --- a/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf +++ b/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf @@ -11,8 +11,8 @@ import TestCount from "../lib/TestCount.lf" reactor WithLogicalAction { output out: int - state thread_id: lf_thread_t(0) - state counter: int(1) + state thread_id: lf_thread_t = 0 + state counter: int = 1 logical action act(0): int reaction(startup, act) -> act, out {= diff --git a/test/C/src/federated/DistributedLoopedAction.lf b/test/C/src/federated/DistributedLoopedAction.lf index 1a70412696..8218e73646 100644 --- a/test/C/src/federated/DistributedLoopedAction.lf +++ b/test/C/src/federated/DistributedLoopedAction.lf @@ -10,11 +10,14 @@ target C { import Sender from "../lib/LoopedActionSender.lf" -reactor Receiver(take_a_break_after: int(10), break_interval: time(400 msec)) { +reactor Receiver( + take_a_break_after: int = 10, + break_interval: time = 400 msec +) { input in: int - state received_messages: int(0) - state total_received_messages: int(0) - state breaks: int(0) + state received_messages: int = 0 + state total_received_messages: int = 0 + state breaks: int = 0 timer t(0, 1 msec) // This will impact the performance // but forces the logical time to advance Comment this line for a more diff --git a/test/C/src/federated/DistributedLoopedActionDecentralized.lf b/test/C/src/federated/DistributedLoopedActionDecentralized.lf index f1addfb63c..17842809fa 100644 --- a/test/C/src/federated/DistributedLoopedActionDecentralized.lf +++ b/test/C/src/federated/DistributedLoopedActionDecentralized.lf @@ -20,11 +20,14 @@ target C { import Sender from "../lib/LoopedActionSender.lf" -reactor Receiver(take_a_break_after: int(10), break_interval: time(400 msec)) { +reactor Receiver( + take_a_break_after: int = 10, + break_interval: time = 400 msec +) { input in: int - state received_messages: int(0) - state total_received_messages: int(0) - state breaks: int(0) + state received_messages: int = 0 + state total_received_messages: int = 0 + state breaks: int = 0 reaction(in) {= tag_t current_tag = lf_tag(); @@ -77,12 +80,12 @@ reactor Receiver(take_a_break_after: int(10), break_interval: time(400 msec)) { } reactor STPReceiver( - take_a_break_after: int(10), - break_interval: time(400 msec), - stp_offset: time(0) + take_a_break_after: int = 10, + break_interval: time = 400 msec, + stp_offset: time = 0 ) { input in: int - state last_time_updated_stp: time(0) + state last_time_updated_stp: time = 0 receiver = new Receiver(take_a_break_after = 10, break_interval = 400 msec) timer t(0, 1 msec) // Force advancement of logical time diff --git a/test/C/src/federated/DistributedLoopedPhysicalAction.lf b/test/C/src/federated/DistributedLoopedPhysicalAction.lf index 7b42628683..454577ff35 100644 --- a/test/C/src/federated/DistributedLoopedPhysicalAction.lf +++ b/test/C/src/federated/DistributedLoopedPhysicalAction.lf @@ -16,10 +16,10 @@ target C { } } -reactor Sender(take_a_break_after: int(10), break_interval: time(550 msec)) { +reactor Sender(take_a_break_after: int = 10, break_interval: time = 550 msec) { output out: int physical action act - state sent_messages: int(0) + state sent_messages: int = 0 reaction(startup, act) -> act, out {= // Send a message on out @@ -35,11 +35,14 @@ reactor Sender(take_a_break_after: int(10), break_interval: time(550 msec)) { =} } -reactor Receiver(take_a_break_after: int(10), break_interval: time(550 msec)) { +reactor Receiver( + take_a_break_after: int = 10, + break_interval: time = 550 msec +) { input in: int - state received_messages: int(0) - state total_received_messages: int(0) - state breaks: int(0) + state received_messages: int = 0 + state total_received_messages: int = 0 + state breaks: int = 0 timer t(0, 1 msec) // This will impact the performance // but forces the logical time to advance Comment this line for a more diff --git a/test/C/src/federated/DistributedMultiport.lf b/test/C/src/federated/DistributedMultiport.lf index 28156d4b70..3853629731 100644 --- a/test/C/src/federated/DistributedMultiport.lf +++ b/test/C/src/federated/DistributedMultiport.lf @@ -7,7 +7,7 @@ target C { reactor Source { output[4] out: int timer t(0, 100 msec) - state count: int(0) + state count: int = 0 reaction(t) -> out {= for (int i = 0; i < out_width; i++) { @@ -18,7 +18,7 @@ reactor Source { reactor Destination { input[4] in: int - state count: int(0) + state count: int = 0 reaction(in) {= for (int i = 0; i < in_width; i++) { diff --git a/test/C/src/federated/DistributedMultiportToBank.lf b/test/C/src/federated/DistributedMultiportToBank.lf index 503dec692e..0accaf8c60 100644 --- a/test/C/src/federated/DistributedMultiportToBank.lf +++ b/test/C/src/federated/DistributedMultiportToBank.lf @@ -6,7 +6,7 @@ target C { reactor Source { output[2] out: int timer t(0, 100 msec) - state count: int(0) + state count: int = 0 reaction(t) -> out {= for (int i = 0; i < out_width; i++) { @@ -18,7 +18,7 @@ reactor Source { reactor Destination { input in: int - state count: int(0) + state count: int = 0 reaction(in) {= lf_print("Received %d.", in->value); diff --git a/test/C/src/federated/DistributedMultiportToken.lf b/test/C/src/federated/DistributedMultiportToken.lf index 065f480e11..dfb791d861 100644 --- a/test/C/src/federated/DistributedMultiportToken.lf +++ b/test/C/src/federated/DistributedMultiportToken.lf @@ -8,7 +8,7 @@ target C { reactor Source { output[4] out: char* timer t(0, 200 msec) - state count: int(0) + state count: int = 0 reaction(t) -> out {= for (int i = 0; i < out_width; i++) { diff --git a/test/C/src/federated/DistributedNetworkOrder.lf b/test/C/src/federated/DistributedNetworkOrder.lf index 0b9ca0cd17..e18ea4ac04 100644 --- a/test/C/src/federated/DistributedNetworkOrder.lf +++ b/test/C/src/federated/DistributedNetworkOrder.lf @@ -32,7 +32,7 @@ reactor Sender { reactor Receiver { input in: int - state success: int(0) + state success: int = 0 reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/federated/DistributedPhysicalActionUpstream.lf b/test/C/src/federated/DistributedPhysicalActionUpstream.lf index 2cc6735884..47280a24b8 100644 --- a/test/C/src/federated/DistributedPhysicalActionUpstream.lf +++ b/test/C/src/federated/DistributedPhysicalActionUpstream.lf @@ -30,7 +30,7 @@ preamble {= reactor WithPhysicalAction { output out: int - state thread_id: lf_thread_t(0) + state thread_id: lf_thread_t = 0 physical action act(0): int reaction(startup) -> act {= diff --git a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf index 0a6ee31e62..66bbb964e1 100644 --- a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf +++ b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf @@ -30,7 +30,7 @@ preamble {= reactor WithPhysicalAction { output out: int - state thread_id: lf_thread_t(0) + state thread_id: lf_thread_t = 0 physical action act(0): int reaction(startup) -> act {= diff --git a/test/C/src/federated/DistributedStop.lf b/test/C/src/federated/DistributedStop.lf index cf103d8beb..2d4a616664 100644 --- a/test/C/src/federated/DistributedStop.lf +++ b/test/C/src/federated/DistributedStop.lf @@ -10,7 +10,7 @@ reactor Sender { output out: int timer t(0, 1 usec) logical action act - state reaction_invoked_correctly: bool(false) + state reaction_invoked_correctly: bool = false reaction(t, act) -> out, act {= lf_print("Sending 42 at (%lld, %u).", @@ -63,10 +63,10 @@ reactor Sender { } reactor Receiver( - stp_offset: time(10 msec) // Used in the decentralized variant of the test + stp_offset: time = 10 msec // Used in the decentralized variant of the test ) { input in: int - state reaction_invoked_correctly: bool(false) + state reaction_invoked_correctly: bool = false reaction(in) {= lf_print("Received %d at (%lld, %u).", diff --git a/test/C/src/federated/DistributedToken.lf b/test/C/src/federated/DistributedToken.lf index c408af6ad2..1068bd5f85 100644 --- a/test/C/src/federated/DistributedToken.lf +++ b/test/C/src/federated/DistributedToken.lf @@ -26,11 +26,11 @@ target C { * @param root The root string. * @output message The message. */ -reactor MessageGenerator(root: string("")) { +reactor MessageGenerator(root: string = "") { // Output type char* instead of string is used for dynamically allocated // character arrays (as opposed to static constant strings). output message: char* - state count: int(1) + state count: int = 1 // Send first message after 1 sec so that the startup reactions do not // factor into the transport time measurement on the first message. timer t(1 sec, 1 sec) @@ -57,7 +57,7 @@ reactor MessageGenerator(root: string("")) { */ reactor PrintMessage { input message: char* - state count: int(0) + state count: int = 0 reaction(message) {= printf("PrintMessage: At (elapsed) logical time %lld, receiver receives: %s\n", diff --git a/test/C/src/federated/FeedbackDelay.lf b/test/C/src/federated/FeedbackDelay.lf index 6495fde8db..997a4ad012 100644 --- a/test/C/src/federated/FeedbackDelay.lf +++ b/test/C/src/federated/FeedbackDelay.lf @@ -7,8 +7,8 @@ reactor PhysicalPlant { input control: double output sensor: double timer t(0, 33 msec) - state last_sensor_time: time(0) - state previous_sensor_time: time(0) + state last_sensor_time: time = 0 + state previous_sensor_time: time = 0 reaction(t) -> sensor {= lf_set(sensor, 42); @@ -27,8 +27,8 @@ reactor Controller { input sensor: double output control: double - state latest_control: double(0.0) - state first: bool(true) + state latest_control: double = 0.0 + state first: bool = true output request_for_planning: double input planning: double diff --git a/test/C/src/federated/FeedbackDelaySimple.lf b/test/C/src/federated/FeedbackDelaySimple.lf index 2f00000e03..0c9b147cda 100644 --- a/test/C/src/federated/FeedbackDelaySimple.lf +++ b/test/C/src/federated/FeedbackDelaySimple.lf @@ -6,7 +6,7 @@ reactor Loop { input in: int output out: int timer t(0, 100 msec) - state count: int(1) + state count: int = 1 reaction(in) {= lf_print("Received %d.", in->value); diff --git a/test/C/src/federated/HelloDistributed.lf b/test/C/src/federated/HelloDistributed.lf index 14b446f26d..6841bf91bc 100644 --- a/test/C/src/federated/HelloDistributed.lf +++ b/test/C/src/federated/HelloDistributed.lf @@ -19,7 +19,7 @@ reactor Source { reactor Destination { input in: string - state received: bool(false) + state received: bool = false reaction(startup) {= lf_print("Destination started."); =} diff --git a/test/C/src/federated/LoopDistributedCentralized.lf b/test/C/src/federated/LoopDistributedCentralized.lf index 9643d3f1f3..7fdc972b5a 100644 --- a/test/C/src/federated/LoopDistributedCentralized.lf +++ b/test/C/src/federated/LoopDistributedCentralized.lf @@ -14,11 +14,11 @@ target C { logging: DEBUG } -reactor Looper(incr: int(1), delay: time(0 msec)) { +reactor Looper(incr: int = 1, delay: time = 0 msec) { input in: int output out: int physical action a(delay) - state count: int(0) + state count: int = 0 timer t(0, 1 sec) @@ -42,7 +42,7 @@ reactor Looper(incr: int(1), delay: time(0 msec)) { =} } -federated reactor LoopDistributedCentralized(delay: time(0)) { +federated reactor LoopDistributedCentralized(delay: time = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.in diff --git a/test/C/src/federated/LoopDistributedCentralized2.lf b/test/C/src/federated/LoopDistributedCentralized2.lf index c81a0f012e..82412e16fe 100644 --- a/test/C/src/federated/LoopDistributedCentralized2.lf +++ b/test/C/src/federated/LoopDistributedCentralized2.lf @@ -13,11 +13,11 @@ target C { timeout: 4 sec } -reactor Looper(incr: int(1), delay: time(0 msec)) { +reactor Looper(incr: int = 1, delay: time = 0 msec) { input in: int output out: int physical action a(delay) - state count: int(0) + state count: int = 0 timer t(0, 1 sec) @@ -41,11 +41,11 @@ reactor Looper(incr: int(1), delay: time(0 msec)) { =} } -reactor Looper2(incr: int(1), delay: time(0 msec)) { +reactor Looper2(incr: int = 1, delay: time = 0 msec) { input in: int output out: int physical action a(delay) - state count: int(0) + state count: int = 0 timer t(0, 1 sec) @@ -69,7 +69,7 @@ reactor Looper2(incr: int(1), delay: time(0 msec)) { =} } -federated reactor(delay: time(0)) { +federated reactor(delay: time = 0) { left = new Looper() right = new Looper2(incr = -1) left.out -> right.in diff --git a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf index e622e31979..44ce6e1681 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf @@ -27,11 +27,11 @@ preamble {= } =} -reactor Looper(incr: int(1), delay: time(0 msec)) { +reactor Looper(incr: int = 1, delay: time = 0 msec) { input in: int output out: int physical action a(delay) - state count: int(0) + state count: int = 0 reaction(startup) -> a {= // Start the thread that listens for Enter or Return. @@ -62,7 +62,7 @@ reactor Looper(incr: int(1), delay: time(0 msec)) { =} } -federated reactor(delay: time(0)) { +federated reactor(delay: time = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.in diff --git a/test/C/src/federated/LoopDistributedCentralizedPrecedence.lf b/test/C/src/federated/LoopDistributedCentralizedPrecedence.lf index a1e6e87b6e..f9b088b7d1 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPrecedence.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPrecedence.lf @@ -14,11 +14,11 @@ target C { timeout: 5 sec } -reactor Looper(incr: int(1), delay: time(0 msec)) { +reactor Looper(incr: int = 1, delay: time = 0 msec) { input in: int output out: int - state count: int(0) - state received_count: int(0) + state count: int = 0 + state received_count: int = 0 timer t(0, 1 sec) reaction(t) -> out {= @@ -48,7 +48,7 @@ reactor Looper(incr: int(1), delay: time(0 msec)) { =} } -federated reactor(delay: time(0)) { +federated reactor(delay: time = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.in diff --git a/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf b/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf index ef18cadfbf..17177aadb3 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf @@ -15,11 +15,11 @@ target C { timeout: 5 sec } -reactor Contained(incr: int(1)) { +reactor Contained(incr: int = 1) { timer t(0, 1 sec) input in: int - state count: int(0) - state received_count: int(0) + state count: int = 0 + state received_count: int = 0 reaction(t) {= self->count += self->incr; =} @@ -32,10 +32,10 @@ reactor Contained(incr: int(1)) { =} } -reactor Looper(incr: int(1), delay: time(0 msec)) { +reactor Looper(incr: int = 1, delay: time = 0 msec) { input in: int output out: int - state count: int(0) + state count: int = 0 timer t(0, 1 sec) c = new Contained(incr = incr) @@ -62,7 +62,7 @@ reactor Looper(incr: int(1), delay: time(0 msec)) { =} } -federated reactor(delay: time(0)) { +federated reactor(delay: time = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.in diff --git a/test/C/src/federated/LoopDistributedDecentralized.lf b/test/C/src/federated/LoopDistributedDecentralized.lf index c2d4bcb297..ae64e6e71a 100644 --- a/test/C/src/federated/LoopDistributedDecentralized.lf +++ b/test/C/src/federated/LoopDistributedDecentralized.lf @@ -23,11 +23,11 @@ preamble {= } =} -reactor Looper(incr: int(1), delay: time(0 msec), stp_offset: time(0)) { +reactor Looper(incr: int = 1, delay: time = 0 msec, stp_offset: time = 0) { input in: int output out: int physical action a(stp_offset) - state count: int(0) + state count: int = 0 reaction(startup) -> a {= // Start the thread that listens for Enter or Return. @@ -69,7 +69,7 @@ reactor Looper(incr: int(1), delay: time(0 msec), stp_offset: time(0)) { =} } -federated reactor LoopDistributedDecentralized(delay: time(0)) { +federated reactor LoopDistributedDecentralized(delay: time = 0) { left = new Looper(stp_offset = 900 usec) right = new Looper(incr = -1, stp_offset = 2400 usec) left.out -> right.in diff --git a/test/C/src/federated/LoopDistributedDouble.lf b/test/C/src/federated/LoopDistributedDouble.lf index bba7c7ffaa..31b7eb0f6e 100644 --- a/test/C/src/federated/LoopDistributedDouble.lf +++ b/test/C/src/federated/LoopDistributedDouble.lf @@ -27,13 +27,13 @@ preamble {= } =} -reactor Looper(incr: int(1), delay: time(0 msec)) { +reactor Looper(incr: int = 1, delay: time = 0 msec) { input in: int input in2: int output out: int output out2: int physical action a(delay) - state count: int(0) + state count: int = 0 timer t(0, 1 sec) reaction(startup) -> a {= @@ -82,7 +82,7 @@ reactor Looper(incr: int(1), delay: time(0 msec)) { =} } -federated reactor(delay: time(0)) { +federated reactor(delay: time = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.in diff --git a/test/C/src/federated/PhysicalSTP.lf b/test/C/src/federated/PhysicalSTP.lf index 1824d9ec93..492cd8875b 100644 --- a/test/C/src/federated/PhysicalSTP.lf +++ b/test/C/src/federated/PhysicalSTP.lf @@ -9,9 +9,9 @@ target C { import Count from "../lib/Count.lf" -reactor Print(STP_offset_param: time(0)) { +reactor Print(STP_offset_param: time = 0) { input in: int - state c: int(1) + state c: int = 1 reaction(in) {= interval_t elapsed_time = lf_time_logical_elapsed(); diff --git a/test/C/src/federated/PingPongDistributed.lf b/test/C/src/federated/PingPongDistributed.lf index a1d321cc6d..e16de50ed2 100644 --- a/test/C/src/federated/PingPongDistributed.lf +++ b/test/C/src/federated/PingPongDistributed.lf @@ -23,7 +23,7 @@ target C import Ping, Pong from "PingPongDistributedPhysical.lf" -federated reactor(count: int(10)) { +federated reactor(count: int = 10) { ping = new Ping(count = count) pong = new Pong(expected = count) ping.send -> pong.receive diff --git a/test/C/src/federated/PingPongDistributedPhysical.lf b/test/C/src/federated/PingPongDistributedPhysical.lf index 95427c2a29..d9ba517c59 100644 --- a/test/C/src/federated/PingPongDistributedPhysical.lf +++ b/test/C/src/federated/PingPongDistributedPhysical.lf @@ -26,10 +26,10 @@ */ target C -reactor Ping(count: int(10)) { +reactor Ping(count: int = 10) { input receive: int output send: int - state pingsLeft: int(count) + state pingsLeft: int = count logical action serve reaction(startup, serve) -> send {= @@ -46,10 +46,10 @@ reactor Ping(count: int(10)) { =} } -reactor Pong(expected: int(10)) { +reactor Pong(expected: int = 10) { input receive: int output send: int - state count: int(0) + state count: int = 0 reaction(receive) -> send {= self->count++; @@ -71,7 +71,7 @@ reactor Pong(expected: int(10)) { =} } -federated reactor(count: int(10)) { +federated reactor(count: int = 10) { ping = new Ping(count = count) pong = new Pong(expected = count) ping.send ~> pong.receive diff --git a/test/C/src/federated/TopLevelArtifacts.lf b/test/C/src/federated/TopLevelArtifacts.lf index 5fd4b0c376..05932b1937 100644 --- a/test/C/src/federated/TopLevelArtifacts.lf +++ b/test/C/src/federated/TopLevelArtifacts.lf @@ -15,7 +15,7 @@ import Count from "../lib/Count.lf" import TestCount from "../lib/TestCount.lf" federated reactor { - state successes: int(0) + state successes: int = 0 timer t(0, 1 sec) logical action act(0) diff --git a/test/C/src/federated/failing/ClockSync.lf b/test/C/src/federated/failing/ClockSync.lf index e843f0c549..1231ce9cbf 100644 --- a/test/C/src/federated/failing/ClockSync.lf +++ b/test/C/src/federated/failing/ClockSync.lf @@ -29,7 +29,7 @@ target C { } /** Reactor that outputs periodically. */ -reactor Ticker(period: time(1600 msec)) { +reactor Ticker(period: time = 1600 msec) { output out: int timer tick(0, period) diff --git a/test/C/src/federated/failing/DistributedDoublePortLooped.lf b/test/C/src/federated/failing/DistributedDoublePortLooped.lf index 4eca2cee76..67a3300c74 100644 --- a/test/C/src/federated/failing/DistributedDoublePortLooped.lf +++ b/test/C/src/federated/failing/DistributedDoublePortLooped.lf @@ -19,7 +19,7 @@ reactor Foo { } reactor Count { - state count: int(1) + state count: int = 1 input in1: int input in2: int input in3: int @@ -37,7 +37,7 @@ reactor Count { } reactor CountMicrostep { - state count: int(1) + state count: int = 1 output out: int logical action act: int timer t(0, 1 msec) diff --git a/test/C/src/federated/failing/DistributedDoublePortLooped2.lf b/test/C/src/federated/failing/DistributedDoublePortLooped2.lf index 04ea02c4e8..56a873e1bc 100644 --- a/test/C/src/federated/failing/DistributedDoublePortLooped2.lf +++ b/test/C/src/federated/failing/DistributedDoublePortLooped2.lf @@ -12,7 +12,7 @@ target C { } reactor Count { - state count: int(1) + state count: int = 1 input in: int output out: int timer t(0, 1 msec) @@ -26,7 +26,7 @@ reactor Count { } reactor CountMicrostep { - state count: int(1) + state count: int = 1 output out: int logical action act: int timer t(0, 1 msec) diff --git a/test/C/src/federated/failing/DistributedNetworkOrderDecentralized.lf b/test/C/src/federated/failing/DistributedNetworkOrderDecentralized.lf index 19779de3d2..97fa16b016 100644 --- a/test/C/src/federated/failing/DistributedNetworkOrderDecentralized.lf +++ b/test/C/src/federated/failing/DistributedNetworkOrderDecentralized.lf @@ -31,7 +31,7 @@ reactor Sender { reactor Receiver { input in: int - state success: int(0) + state success: int = 0 reaction(in) {= tag_t current_tag = lf_tag(); diff --git a/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedence.lf b/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedence.lf index ee9fc56d83..b991662a05 100644 --- a/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedence.lf +++ b/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedence.lf @@ -11,11 +11,11 @@ target C { timeout: 4900 msec } -reactor Looper(incr: int(1), delay: time(0 msec), stp_offset: time(0)) { +reactor Looper(incr: int = 1, delay: time = 0 msec, stp_offset: time = 0) { input in: int output out: int - state count: int(0) - state received_count: int(0) + state count: int = 0 + state received_count: int = 0 timer t(0, 1 sec) reaction(t) -> out {= @@ -57,7 +57,7 @@ reactor Looper(incr: int(1), delay: time(0 msec), stp_offset: time(0)) { =} } -federated reactor(delay: time(0)) { +federated reactor(delay: time = 0) { left = new Looper(stp_offset = 5 msec) right = new Looper(incr = -1, stp_offset = 5 msec) left.out -> right.in diff --git a/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedenceHierarchy.lf b/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedenceHierarchy.lf index 6cf9236901..357b7f2f2d 100644 --- a/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedenceHierarchy.lf +++ b/test/C/src/federated/failing/LoopDistributedDecentralizedPrecedenceHierarchy.lf @@ -1,6 +1,7 @@ /** - * This tests that the precedence order of reaction invocation is kept - * in the hierarchy of reactors when a feedback loop is present in decentralized coordination. + * This tests that the precedence order of reaction invocation is kept in the + * hierarchy of reactors when a feedback loop is present in decentralized + * coordination. * * @author Edward A. Lee * @author Soroush Bateni @@ -11,37 +12,39 @@ target C { timeout: 4900 msec } -import Contained from "../LoopDistributedCentralizedPrecedenceHierarchy.lf"; +import Contained from "../LoopDistributedCentralizedPrecedenceHierarchy.lf" -reactor Looper(incr:int(1), delay:time(0 msec), stp_offset:time(0)) { - input in:int; - output out:int; - state count:int(0); - timer t(0, 1 sec); +reactor Looper(incr: int = 1, delay: time = 0 msec, stp_offset: time = 0) { + input in: int + output out: int + state count: int = 0 + timer t(0, 1 sec) - c = new Contained(incr = incr); + c = new Contained(incr = incr) + in -> c.in reaction(t) -> out {= lf_set(out, self->count); self->count += self->incr; =} + reaction(in) {= instant_t time_lag = lf_time_physical() - lf_time_logical(); char time_buffer[28]; // 28 bytes is enough for the largest 64 bit number: 9,223,372,036,854,775,807 lf_readable_time(time_buffer, time_lag); lf_print("Received %d. Logical time is behind physical time by %s nsec.", in->value, time_buffer); - =} STP (stp_offset) {= + =} STP(stp_offset) {= instant_t time_lag = lf_time_physical() - lf_time_logical(); char time_buffer[28]; // 28 bytes is enough for the largest 64 bit number: 9,223,372,036,854,775,807 lf_readable_time(time_buffer, time_lag); lf_print("STP offset was violated. Received %d. Logical time is behind physical time by %s nsec.", in->value, time_buffer); - =} deadline (10 msec) {= + =} deadline(10 msec) {= instant_t time_lag = lf_time_physical() - lf_time_logical(); char time_buffer[28]; // 28 bytes is enough for the largest 64 bit number: 9,223,372,036,854,775,807 lf_readable_time(time_buffer, time_lag); lf_print("Deadline miss. Received %d. Logical time is behind physical time by %s nsec.", in->value, time_buffer); =} - in->c.in; + reaction(shutdown) {= lf_print("******* Shutdown invoked."); if (self->count != 5 * self->incr) { @@ -49,9 +52,10 @@ reactor Looper(incr:int(1), delay:time(0 msec), stp_offset:time(0)) { } =} } -federated reactor (delay:time(0)) { - left = new Looper(stp_offset = 5 msec); - right = new Looper(incr = -1, stp_offset = 5 msec); - left.out -> right.in; - right.out -> left.in; + +federated reactor(delay: time = 0) { + left = new Looper(stp_offset = 5 msec) + right = new Looper(incr = -1, stp_offset = 5 msec) + left.out -> right.in + right.out -> left.in } diff --git a/test/C/src/lib/Count.lf b/test/C/src/lib/Count.lf index fc95ead637..70291e783f 100644 --- a/test/C/src/lib/Count.lf +++ b/test/C/src/lib/Count.lf @@ -1,7 +1,7 @@ target C -reactor Count(offset: time(0), period: time(1 sec)) { - state count: int(1) +reactor Count(offset: time = 0, period: time = 1 sec) { + state count: int = 1 output out: int timer t(offset, period) diff --git a/test/C/src/lib/InternalDelay.lf b/test/C/src/lib/InternalDelay.lf index 013b916ecc..fbbf2f0cb6 100644 --- a/test/C/src/lib/InternalDelay.lf +++ b/test/C/src/lib/InternalDelay.lf @@ -1,6 +1,6 @@ target C -reactor InternalDelay(delay: int(10 msec)) { +reactor InternalDelay(delay: int = 10 msec) { input in: int output out: int logical action d: int diff --git a/test/C/src/lib/LoopedActionSender.lf b/test/C/src/lib/LoopedActionSender.lf index 2e24f1677f..b996fe6ac5 100644 --- a/test/C/src/lib/LoopedActionSender.lf +++ b/test/C/src/lib/LoopedActionSender.lf @@ -11,10 +11,10 @@ target C * @param break_interval: Determines how long the reactor should take a break * after sending take_a_break_after messages. */ -reactor Sender(take_a_break_after: int(10), break_interval: time(400 msec)) { +reactor Sender(take_a_break_after: int = 10, break_interval: time = 400 msec) { output out: int logical action act - state sent_messages: int(0) + state sent_messages: int = 0 reaction(startup, act) -> act, out {= // Send a message on out diff --git a/test/C/src/lib/Test.lf b/test/C/src/lib/Test.lf index ea341ae772..3ad7518d4e 100644 --- a/test/C/src/lib/Test.lf +++ b/test/C/src/lib/Test.lf @@ -1,8 +1,8 @@ target C -reactor TestDouble(expected: double[](1.0, 1.0, 1.0, 1.0)) { +reactor TestDouble(expected: double[] = {1.0, 1.0, 1.0, 1.0}) { input in: double - state count: int(0) + state count: int = 0 reaction(in) {= printf("Received: %f\n", in->value); diff --git a/test/C/src/lib/TestCount.lf b/test/C/src/lib/TestCount.lf index 3db3fbb2fc..d3f29aec6c 100644 --- a/test/C/src/lib/TestCount.lf +++ b/test/C/src/lib/TestCount.lf @@ -9,9 +9,9 @@ */ target C -reactor TestCount(start: int(1), stride: int(1), num_inputs: int(1)) { - state count: int(start) - state inputs_received: int(0) +reactor TestCount(start: int = 1, stride: int = 1, num_inputs: int = 1) { + state count: int = start + state inputs_received: int = 0 input in: int reaction(in) {= diff --git a/test/C/src/lib/TestCountMultiport.lf b/test/C/src/lib/TestCountMultiport.lf index 2a24922ef8..5a2db4b029 100644 --- a/test/C/src/lib/TestCountMultiport.lf +++ b/test/C/src/lib/TestCountMultiport.lf @@ -12,13 +12,13 @@ target C reactor TestCountMultiport( - start: int(1), - stride: int(1), - num_inputs: int(1), - width: int(2) + start: int = 1, + stride: int = 1, + num_inputs: int = 1, + width: int = 2 ) { - state count: int(start) - state inputs_received: int(0) + state count: int = start + state inputs_received: int = 0 input[width] in: int reaction(in) {= diff --git a/test/C/src/modal_models/ConvertCaseTest.lf b/test/C/src/modal_models/ConvertCaseTest.lf index 494a3c273c..5475edaa35 100644 --- a/test/C/src/modal_models/ConvertCaseTest.lf +++ b/test/C/src/modal_models/ConvertCaseTest.lf @@ -83,9 +83,9 @@ reactor Converter { } } -reactor InputFeeder(message: string("")) { +reactor InputFeeder(message: string = "") { output character: char - state idx: int(0) + state idx: int = 0 timer t(0, 250 msec) diff --git a/test/C/src/modal_models/Count3Modes.lf b/test/C/src/modal_models/Count3Modes.lf index 5ef26ccde0..862915abed 100644 --- a/test/C/src/modal_models/Count3Modes.lf +++ b/test/C/src/modal_models/Count3Modes.lf @@ -34,7 +34,7 @@ main reactor { timer stepper(0, 250 msec) counter = new CounterCycle() - state expected_value: int(1) + state expected_value: int = 1 // Trigger reaction(stepper) -> counter.next {= lf_set(counter.next, true); =} diff --git a/test/C/src/modal_models/MixedReactions.lf b/test/C/src/modal_models/MixedReactions.lf index e0c9c7f0f7..78ea39c95c 100644 --- a/test/C/src/modal_models/MixedReactions.lf +++ b/test/C/src/modal_models/MixedReactions.lf @@ -8,8 +8,8 @@ target C { } main reactor { - state x: int(0) - state first: bool(true) + state x: int = 0 + state first: bool = true timer t(0, 100 msec) diff --git a/test/C/src/modal_models/ModalAfter.lf b/test/C/src/modal_models/ModalAfter.lf index 038a368cc9..270fd88d05 100644 --- a/test/C/src/modal_models/ModalAfter.lf +++ b/test/C/src/modal_models/ModalAfter.lf @@ -45,7 +45,7 @@ reactor Modal { } } -reactor Producer(mode_id: int(0)) { +reactor Producer(mode_id: int = 0) { output product: int timer t(0, 750 msec) @@ -56,7 +56,7 @@ reactor Producer(mode_id: int(0)) { =} } -reactor Consumer(mode_id: int(0)) { +reactor Consumer(mode_id: int = 0) { input product: int output report: int diff --git a/test/C/src/modal_models/ModalCycleBreaker.lf b/test/C/src/modal_models/ModalCycleBreaker.lf index 680323a729..cee4fabfec 100644 --- a/test/C/src/modal_models/ModalCycleBreaker.lf +++ b/test/C/src/modal_models/ModalCycleBreaker.lf @@ -42,11 +42,11 @@ reactor Modal { } } -reactor Counter(period: time(1 sec)) { +reactor Counter(period: time = 1 sec) { output value: int timer t(0, period) - state curval: int(0) + state curval: int = 0 reaction(t) -> value {= lf_set(value, self->curval++); =} } diff --git a/test/C/src/modal_models/ModalStateReset.lf b/test/C/src/modal_models/ModalStateReset.lf index cd963cfd8d..61563162b6 100644 --- a/test/C/src/modal_models/ModalStateReset.lf +++ b/test/C/src/modal_models/ModalStateReset.lf @@ -14,7 +14,7 @@ reactor Modal { output count1: int output count2: int - state counter0: int(0) + state counter0: int = 0 reaction(next) -> count0 {= printf("Counter0: %d\n", self->counter0); @@ -22,7 +22,7 @@ reactor Modal { =} initial mode One { - state counter1: int(0) + state counter1: int = 0 timer T1(0 msec, 250 msec) reaction(reset) {= self->counter1 = 0; =} @@ -39,7 +39,7 @@ reactor Modal { } mode Two { - state counter2: int(-2) + state counter2: int = -2 timer T2(0 msec, 250 msec) reaction(reset) {= self->counter2 = -2; =} diff --git a/test/C/src/modal_models/ModalStateResetAuto.lf b/test/C/src/modal_models/ModalStateResetAuto.lf index 24cfe9b187..a2b6b105f0 100644 --- a/test/C/src/modal_models/ModalStateResetAuto.lf +++ b/test/C/src/modal_models/ModalStateResetAuto.lf @@ -14,7 +14,7 @@ reactor Modal { output count1: int output count2: int - state counter0: int(0) + state counter0: int = 0 reaction(next) -> count0 {= printf("Counter0: %d\n", self->counter0); @@ -22,7 +22,7 @@ reactor Modal { =} initial mode One { - state counter1: int(0) + state counter1: int = 0 timer T1(0 msec, 250 msec) reaction(T1) -> count1 {= printf("Counter1: %d\n", self->counter1); @@ -37,7 +37,7 @@ reactor Modal { } mode Two { - reset state counter2: int(-2) + reset state counter2: int = -2 timer T2(0 msec, 250 msec) reaction(T2) -> count2 {= printf("Counter2: %d\n", self->counter2); diff --git a/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf b/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf index 7e75afec13..60fa76dd1e 100644 --- a/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf +++ b/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf @@ -28,11 +28,11 @@ reactor Modal { } } -reactor Counter(period: time(1 sec)) { +reactor Counter(period: time = 1 sec) { output value: int timer t(0, period) - reset state curval: int(0) + reset state curval: int = 0 reaction(t) -> value {= lf_set(value, self->curval++); =} } diff --git a/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf b/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf index 48b335d2b3..dff82c602f 100644 --- a/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf +++ b/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf @@ -31,11 +31,11 @@ reactor Modal { } } -reactor Counter(period: time(1 sec)) { +reactor Counter(period: time = 1 sec) { output value: int timer t(0, period) - reset state curval: int(0) + reset state curval: int = 0 reaction(t) -> value {= lf_set(value, self->curval++); =} } diff --git a/test/C/src/modal_models/util/TraceTesting.lf b/test/C/src/modal_models/util/TraceTesting.lf index 7d5a335db2..247b3f1def 100644 --- a/test/C/src/modal_models/util/TraceTesting.lf +++ b/test/C/src/modal_models/util/TraceTesting.lf @@ -6,17 +6,17 @@ preamble {= =} reactor TraceTesting( - events_size: int(0), - trace_size: int(0), - trace: int[](0), - training: bool(false) + events_size: int = 0, + trace_size: int = 0, + trace: int[] = 0, + training: bool = false ) { input[events_size] events: int - state last_reaction_time: int(0) - state trace_idx: int(0) - state recorded_events: int*(0) - state recorded_events_next: int(0) + state last_reaction_time: int = 0 + state trace_idx: int = 0 + state recorded_events: int* = 0 + state recorded_events_next: int = 0 reaction(startup) {= self->last_reaction_time = lf_time_logical(); =} diff --git a/test/C/src/multiport/BankIndexInitializer.lf b/test/C/src/multiport/BankIndexInitializer.lf index b9b8803943..d8a2c5766e 100644 --- a/test/C/src/multiport/BankIndexInitializer.lf +++ b/test/C/src/multiport/BankIndexInitializer.lf @@ -3,15 +3,15 @@ target C preamble {= int table[] = {4, 3, 2, 1}; =} -reactor Source(bank_index: int(0), value: int(0)) { +reactor Source(bank_index: int = 0, value: int = 0) { output out: int reaction(startup) -> out {= lf_set(out, self->value); =} } -reactor Sink(width: int(4)) { +reactor Sink(width: int = 4) { input[width] in: int - state received: bool(false) + state received: bool = false reaction(in) {= for (int idx = 0; idx < in_width; idx++) { @@ -32,7 +32,7 @@ reactor Sink(width: int(4)) { =} } -main reactor(width: int(4)) { +main reactor(width: int = 4) { source = new[width] Source(value = {= table[bank_index] =}) sink = new Sink(width = width) source.out -> sink.in diff --git a/test/C/src/multiport/BankMultiportToReaction.lf b/test/C/src/multiport/BankMultiportToReaction.lf index c2eff34100..731cd6891b 100644 --- a/test/C/src/multiport/BankMultiportToReaction.lf +++ b/test/C/src/multiport/BankMultiportToReaction.lf @@ -13,8 +13,8 @@ reactor DoubleCount { } main reactor { - state count: int(1) - state received: bool(false) + state count: int = 1 + state received: bool = false s = new[2] DoubleCount() diff --git a/test/C/src/multiport/BankReactionsInContainer.lf b/test/C/src/multiport/BankReactionsInContainer.lf index 6533e7ed0b..98d7cf4e1f 100644 --- a/test/C/src/multiport/BankReactionsInContainer.lf +++ b/test/C/src/multiport/BankReactionsInContainer.lf @@ -5,10 +5,10 @@ target C { timeout: 1 sec } -reactor R(bank_index: int(0)) { +reactor R(bank_index: int = 0) { output[2] out: int input[2] in: int - state received: bool(false) + state received: bool = false reaction(startup) -> out {= for (int i = 0; i < out_width; i++) { @@ -42,7 +42,7 @@ reactor R(bank_index: int(0)) { main reactor { s = new[2] R() - state received: bool(false) + state received: bool = false reaction(startup) -> s.in {= int count = 0; diff --git a/test/C/src/multiport/BankSelfBroadcast.lf b/test/C/src/multiport/BankSelfBroadcast.lf index 587087d550..e1ffb4b2dd 100644 --- a/test/C/src/multiport/BankSelfBroadcast.lf +++ b/test/C/src/multiport/BankSelfBroadcast.lf @@ -7,10 +7,10 @@ */ target C -reactor A(bank_index: int(0)) { +reactor A(bank_index: int = 0) { input[4] in: int output out: int - state received: bool(false) + state received: bool = false reaction(startup) -> out {= lf_set(out, self->bank_index); =} diff --git a/test/C/src/multiport/BankToBank.lf b/test/C/src/multiport/BankToBank.lf index 19a784efd4..beb3a32fa8 100644 --- a/test/C/src/multiport/BankToBank.lf +++ b/test/C/src/multiport/BankToBank.lf @@ -4,10 +4,10 @@ target C { fast: true } -reactor Source(bank_index: int(0)) { +reactor Source(bank_index: int = 0) { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= lf_set(out, self->s); @@ -15,8 +15,8 @@ reactor Source(bank_index: int(0)) { =} } -reactor Destination(bank_index: int(0)) { - state s: int(0) +reactor Destination(bank_index: int = 0) { + state s: int = 0 input in: int reaction(in) {= @@ -37,7 +37,7 @@ reactor Destination(bank_index: int(0)) { =} } -main reactor BankToBank(width: int(4)) { +main reactor BankToBank(width: int = 4) { a = new[width] Source() b = new[width] Destination() a.out -> b.in diff --git a/test/C/src/multiport/BankToBankMultiport.lf b/test/C/src/multiport/BankToBankMultiport.lf index 22c5821cc6..681538f116 100644 --- a/test/C/src/multiport/BankToBankMultiport.lf +++ b/test/C/src/multiport/BankToBankMultiport.lf @@ -4,10 +4,10 @@ target C { fast: true } -reactor Source(width: int(1)) { +reactor Source(width: int = 1) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < out_width; i++) { @@ -16,8 +16,8 @@ reactor Source(width: int(1)) { =} } -reactor Destination(width: int(1)) { - state s: int(6) +reactor Destination(width: int = 1) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -42,7 +42,7 @@ reactor Destination(width: int(1)) { =} } -main reactor BankToBankMultiport(bank_width: int(4)) { +main reactor BankToBankMultiport(bank_width: int = 4) { a = new[bank_width] Source(width = 4) b = new[bank_width] Destination(width = 4) a.out -> b.in diff --git a/test/C/src/multiport/BankToBankMultiportAfter.lf b/test/C/src/multiport/BankToBankMultiportAfter.lf index 38daec6ccf..db0ff7771e 100644 --- a/test/C/src/multiport/BankToBankMultiportAfter.lf +++ b/test/C/src/multiport/BankToBankMultiportAfter.lf @@ -4,10 +4,10 @@ target C { fast: true } -reactor Source(width: int(1)) { +reactor Source(width: int = 1) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < out_width; i++) { @@ -16,8 +16,8 @@ reactor Source(width: int(1)) { =} } -reactor Destination(width: int(1)) { - state s: int(6) +reactor Destination(width: int = 1) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -42,7 +42,7 @@ reactor Destination(width: int(1)) { =} } -main reactor(bank_width: int(4)) { +main reactor(bank_width: int = 4) { a = new[bank_width] Source(width = 4) b = new[bank_width] Destination(width = 4) a.out -> b.in after 200 msec diff --git a/test/C/src/multiport/BankToMultiport.lf b/test/C/src/multiport/BankToMultiport.lf index ee0ad53dc3..f72c104df7 100644 --- a/test/C/src/multiport/BankToMultiport.lf +++ b/test/C/src/multiport/BankToMultiport.lf @@ -1,15 +1,15 @@ // Test bank of reactors to multiport input with id parameter in the bank. target C -reactor Source(bank_index: int(0)) { +reactor Source(bank_index: int = 0) { output out: int reaction(startup) -> out {= lf_set(out, self->bank_index); =} } -reactor Sink(width: int(4)) { +reactor Sink(width: int = 4) { input[width] in: int - state received: bool(false) + state received: bool = false reaction(in) {= for (int i = 0; i < in_width; i++) { @@ -32,7 +32,7 @@ reactor Sink(width: int(4)) { =} } -main reactor BankToMultiport(width: int(5)) { +main reactor BankToMultiport(width: int = 5) { source = new[width] Source() sink = new Sink(width = width) source.out -> sink.in diff --git a/test/C/src/multiport/BankToReaction.lf b/test/C/src/multiport/BankToReaction.lf index c5d68cfe41..4e59352bd5 100644 --- a/test/C/src/multiport/BankToReaction.lf +++ b/test/C/src/multiport/BankToReaction.lf @@ -6,7 +6,7 @@ target C { import Count from "../lib/Count.lf" main reactor { - state count: int(1) + state count: int = 1 s = new[2] Count() diff --git a/test/C/src/multiport/Broadcast.lf b/test/C/src/multiport/Broadcast.lf index dee0ad62ed..e3471da5f6 100644 --- a/test/C/src/multiport/Broadcast.lf +++ b/test/C/src/multiport/Broadcast.lf @@ -9,9 +9,9 @@ reactor Source { reaction(startup) -> out {= lf_set(out, 42); =} } -reactor Destination(bank_index: int(0)) { +reactor Destination(bank_index: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Destination %d received %d.\n", self->bank_index, in->value); diff --git a/test/C/src/multiport/BroadcastAfter.lf b/test/C/src/multiport/BroadcastAfter.lf index b66979e182..c1be635e55 100644 --- a/test/C/src/multiport/BroadcastAfter.lf +++ b/test/C/src/multiport/BroadcastAfter.lf @@ -9,9 +9,9 @@ reactor Source { reaction(startup) -> out {= lf_set(out, 42); =} } -reactor Destination(bank_index: int(0)) { +reactor Destination(bank_index: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Destination %d received %d.\n", self->bank_index, in->value); diff --git a/test/C/src/multiport/BroadcastMultipleAfter.lf b/test/C/src/multiport/BroadcastMultipleAfter.lf index b83540e3d4..97d853e4ce 100644 --- a/test/C/src/multiport/BroadcastMultipleAfter.lf +++ b/test/C/src/multiport/BroadcastMultipleAfter.lf @@ -3,15 +3,15 @@ target C { fast: true } -reactor Source(value: int(42)) { +reactor Source(value: int = 42) { output out: int reaction(startup) -> out {= lf_set(out, self->value); =} } -reactor Destination(bank_index: int(0)) { +reactor Destination(bank_index: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Destination %d received %d.\n", self->bank_index, in->value); diff --git a/test/C/src/multiport/DualBanks.lf b/test/C/src/multiport/DualBanks.lf index aa8faf4b75..3cb5d38887 100644 --- a/test/C/src/multiport/DualBanks.lf +++ b/test/C/src/multiport/DualBanks.lf @@ -1,8 +1,8 @@ target C -reactor Base(bank_index: int(0)) { +reactor Base(bank_index: int = 0) { input I: int - state received: bool(false) + state received: bool = false reaction(shutdown) {= if(!self->received) { diff --git a/test/C/src/multiport/DualBanksMultiport.lf b/test/C/src/multiport/DualBanksMultiport.lf index 42c4adb11e..a5c2120a79 100644 --- a/test/C/src/multiport/DualBanksMultiport.lf +++ b/test/C/src/multiport/DualBanksMultiport.lf @@ -1,8 +1,8 @@ target C -reactor Base(bank_index: int(0)) { +reactor Base(bank_index: int = 0) { input[2] I: int - state received: bool(false) + state received: bool = false reaction(shutdown) {= if(!self->received) { diff --git a/test/C/src/multiport/FullyConnected.lf b/test/C/src/multiport/FullyConnected.lf index 8a26fee83f..758ecf1580 100644 --- a/test/C/src/multiport/FullyConnected.lf +++ b/test/C/src/multiport/FullyConnected.lf @@ -1,10 +1,10 @@ target C -reactor Node(num_nodes: size_t(4), bank_index: int(0)) { +reactor Node(num_nodes: size_t = 4, bank_index: int = 0) { input[num_nodes] in: int output out: int - state received: bool(false) + state received: bool = false reaction(startup) -> out {= lf_print("Hello from node %d!", self->bank_index); @@ -35,7 +35,7 @@ reactor Node(num_nodes: size_t(4), bank_index: int(0)) { =} } -main reactor(num_nodes: size_t(4)) { +main reactor(num_nodes: size_t = 4) { nodes = new[num_nodes] Node(num_nodes = num_nodes) (nodes.out)+ -> nodes.in } diff --git a/test/C/src/multiport/FullyConnectedAddressable.lf b/test/C/src/multiport/FullyConnectedAddressable.lf index ae99d5397e..91b0f5caac 100644 --- a/test/C/src/multiport/FullyConnectedAddressable.lf +++ b/test/C/src/multiport/FullyConnectedAddressable.lf @@ -1,12 +1,12 @@ // In this pattern, each node can send direct messages to individual other nodes target C -reactor Node(num_nodes: size_t(4), bank_index: int(0)) { +reactor Node(num_nodes: size_t = 4, bank_index: int = 0) { input[num_nodes] in: int output[num_nodes] out: int - state received: int(0) - state triggered: bool(false) + state received: int = 0 + state triggered: bool = false reaction(startup) -> out {= int outChannel = (self->bank_index + 1) % self->num_nodes; @@ -45,7 +45,7 @@ reactor Node(num_nodes: size_t(4), bank_index: int(0)) { =} } -main reactor(num_nodes: size_t(4)) { +main reactor(num_nodes: size_t = 4) { nodes1 = new[num_nodes] Node(num_nodes = num_nodes) nodes1.out -> interleaved (nodes1.in) diff --git a/test/C/src/multiport/FullyConnectedAddressableAfter.lf b/test/C/src/multiport/FullyConnectedAddressableAfter.lf index cd6dc4cf85..1d30430deb 100644 --- a/test/C/src/multiport/FullyConnectedAddressableAfter.lf +++ b/test/C/src/multiport/FullyConnectedAddressableAfter.lf @@ -3,7 +3,7 @@ target C import Node from "FullyConnectedAddressable.lf" -main reactor(num_nodes: size_t(4)) { +main reactor(num_nodes: size_t = 4) { nodes1 = new[num_nodes] Node(num_nodes = num_nodes) nodes1.out -> interleaved (nodes1.in) after 200 msec diff --git a/test/C/src/multiport/MultiportFromBank.lf b/test/C/src/multiport/MultiportFromBank.lf index b8ade7a354..1b49786060 100644 --- a/test/C/src/multiport/MultiportFromBank.lf +++ b/test/C/src/multiport/MultiportFromBank.lf @@ -5,7 +5,7 @@ target C { fast: true } -reactor Source(check_override: int(0), bank_index: int(0)) { +reactor Source(check_override: int = 0, bank_index: int = 0) { output out: int reaction(startup) -> out {= @@ -13,9 +13,9 @@ reactor Source(check_override: int(0), bank_index: int(0)) { =} } -reactor Destination(port_width: int(3)) { +reactor Destination(port_width: int = 3) { input[port_width] in: int - state received: bool(false) + state received: bool = false reaction(in) {= for (int i = 0; i < in_width; i++) { @@ -37,7 +37,7 @@ reactor Destination(port_width: int(3)) { =} } -main reactor MultiportFromBank(width: int(4)) { +main reactor MultiportFromBank(width: int = 4) { a = new[width] Source(check_override = 1) b = new Destination(port_width = width) a.out -> b.in diff --git a/test/C/src/multiport/MultiportFromBankHierarchy.lf b/test/C/src/multiport/MultiportFromBankHierarchy.lf index c38046107c..e8ee6194b5 100644 --- a/test/C/src/multiport/MultiportFromBankHierarchy.lf +++ b/test/C/src/multiport/MultiportFromBankHierarchy.lf @@ -7,13 +7,13 @@ target C { import Source, Destination from "MultiportFromBank.lf" -reactor Container(port_width: int(3)) { +reactor Container(port_width: int = 3) { output[port_width] out: int s = new[port_width] Source(check_override = 1) s.out -> out } -main reactor(width: int(4)) { +main reactor(width: int = 4) { a = new Container(port_width = width) b = new Destination(port_width = width) a.out -> b.in diff --git a/test/C/src/multiport/MultiportFromHierarchy.lf b/test/C/src/multiport/MultiportFromHierarchy.lf index f38661345c..8323210946 100644 --- a/test/C/src/multiport/MultiportFromHierarchy.lf +++ b/test/C/src/multiport/MultiportFromHierarchy.lf @@ -5,10 +5,10 @@ target C { fast: true } -reactor Source(width: int(3)) { +reactor Source(width: int = 3) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < out_width; i++) { @@ -17,8 +17,8 @@ reactor Source(width: int(3)) { =} } -reactor Destination(width: int(3)) { - state s: int(6) +reactor Destination(width: int = 3) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -43,19 +43,19 @@ reactor Destination(width: int(3)) { =} } -reactor Container(width: int(3)) { +reactor Container(width: int = 3) { output[width] out: int src = new InsideContainer(width = width) src.out -> out } -reactor InsideContainer(width: int(3)) { +reactor InsideContainer(width: int = 3) { output[width] out: int src = new Source(width = width) src.out -> out } -main reactor MultiportFromHierarchy(width: int(4)) { +main reactor MultiportFromHierarchy(width: int = 4) { a = new Container(width = width) b = new Destination(width = width) a.out -> b.in diff --git a/test/C/src/multiport/MultiportFromReaction.lf b/test/C/src/multiport/MultiportFromReaction.lf index acaa59e519..33cbfffd83 100644 --- a/test/C/src/multiport/MultiportFromReaction.lf +++ b/test/C/src/multiport/MultiportFromReaction.lf @@ -4,8 +4,8 @@ target C { fast: true } -reactor Destination(width: int(1)) { - state s: int(6) +reactor Destination(width: int = 1) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -30,9 +30,9 @@ reactor Destination(width: int(1)) { =} } -main reactor MultiportFromReaction(width: int(4)) { +main reactor MultiportFromReaction(width: int = 4) { timer t(0, 200 msec) - state s: int(0) + state s: int = 0 b = new Destination(width = width) reaction(t) -> b.in {= diff --git a/test/C/src/multiport/MultiportIn.lf b/test/C/src/multiport/MultiportIn.lf index 80269896d8..b87a91a79b 100644 --- a/test/C/src/multiport/MultiportIn.lf +++ b/test/C/src/multiport/MultiportIn.lf @@ -8,7 +8,7 @@ target C { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= lf_set(out, self->s); @@ -24,7 +24,7 @@ reactor Computation { } reactor Destination { - state s: int(0) + state s: int = 0 input[4] in: int reaction(in) {= diff --git a/test/C/src/multiport/MultiportInParameterized.lf b/test/C/src/multiport/MultiportInParameterized.lf index cd9bb27ad2..bf7ec93f6e 100644 --- a/test/C/src/multiport/MultiportInParameterized.lf +++ b/test/C/src/multiport/MultiportInParameterized.lf @@ -8,7 +8,7 @@ target C { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= lf_set(out, self->s); @@ -23,8 +23,8 @@ reactor Computation { reaction(in) -> out {= lf_set(out, in->value); =} } -reactor Destination(width: int(1)) { - state s: int(0) +reactor Destination(width: int = 1) { + state s: int = 0 input[width] in: int reaction(in) {= diff --git a/test/C/src/multiport/MultiportMutableInput.lf b/test/C/src/multiport/MultiportMutableInput.lf index d1b0994012..e1f8d579c2 100644 --- a/test/C/src/multiport/MultiportMutableInput.lf +++ b/test/C/src/multiport/MultiportMutableInput.lf @@ -12,7 +12,7 @@ reactor Source { =} } -reactor Print(scale: int(1)) { // The scale parameter is just for testing. +reactor Print(scale: int = 1) { // The scale parameter is just for testing. input[2] in: int reaction(in) {= @@ -27,7 +27,7 @@ reactor Print(scale: int(1)) { // The scale parameter is just for testing. =} } -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { mutable input[2] in: int output[2] out: int diff --git a/test/C/src/multiport/MultiportMutableInputArray.lf b/test/C/src/multiport/MultiportMutableInputArray.lf index cd279bb6a2..90907e4f44 100644 --- a/test/C/src/multiport/MultiportMutableInputArray.lf +++ b/test/C/src/multiport/MultiportMutableInputArray.lf @@ -26,7 +26,7 @@ reactor Source { =} } -reactor Print(scale: int(1)) { // The scale parameter is just for testing. +reactor Print(scale: int = 1) { // The scale parameter is just for testing. input[2] in: int[] reaction(in) {= @@ -52,7 +52,7 @@ reactor Print(scale: int(1)) { // The scale parameter is just for testing. =} } -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { mutable input[2] in: int[] output[2] out: int[] diff --git a/test/C/src/multiport/MultiportOut.lf b/test/C/src/multiport/MultiportOut.lf index c2707d5d33..38365d217a 100644 --- a/test/C/src/multiport/MultiportOut.lf +++ b/test/C/src/multiport/MultiportOut.lf @@ -7,7 +7,7 @@ target C { reactor Source { timer t(0, 200 msec) output[4] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < 4; i++) { @@ -31,7 +31,7 @@ reactor Computation { } reactor Destination { - state s: int(0) + state s: int = 0 input[4] in: int reaction(in) {= diff --git a/test/C/src/multiport/MultiportToBank.lf b/test/C/src/multiport/MultiportToBank.lf index c1f19d3553..86146b9818 100644 --- a/test/C/src/multiport/MultiportToBank.lf +++ b/test/C/src/multiport/MultiportToBank.lf @@ -4,7 +4,7 @@ target C { fast: true } -reactor Source(width: int(2)) { +reactor Source(width: int = 2) { output[width] out: int // Connected to a bank of Destination reactors input dummy: int // Not connected to anything @@ -24,9 +24,9 @@ reactor Source(width: int(2)) { =} } -reactor Destination(bank_index: int(0)) { +reactor Destination(bank_index: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Destination %d received %d.\n", self->bank_index, in->value); @@ -46,7 +46,7 @@ reactor Destination(bank_index: int(0)) { =} } -main reactor MultiportToBank(width: int(3)) { +main reactor MultiportToBank(width: int = 3) { a = new Source(width = width) b = new[width] Destination() a.out -> b.in diff --git a/test/C/src/multiport/MultiportToBankAfter.lf b/test/C/src/multiport/MultiportToBankAfter.lf index 66e0fdf837..ba26142243 100644 --- a/test/C/src/multiport/MultiportToBankAfter.lf +++ b/test/C/src/multiport/MultiportToBankAfter.lf @@ -5,7 +5,7 @@ target C { fast: true } -reactor Source(width: int(2)) { +reactor Source(width: int = 2) { output[width] out: int reaction(startup) -> out {= @@ -15,9 +15,9 @@ reactor Source(width: int(2)) { =} } -reactor Destination(bank_index: int(0)) { +reactor Destination(bank_index: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Destination %d received %d.\n", self->bank_index, in->value); @@ -41,7 +41,7 @@ reactor Destination(bank_index: int(0)) { =} } -main reactor(width: int(3)) { +main reactor(width: int = 3) { a = new Source(width = width) b = new[width] Destination() a.out -> b.in after 1 sec // Width of the bank of delays will be inferred. diff --git a/test/C/src/multiport/MultiportToBankDouble.lf b/test/C/src/multiport/MultiportToBankDouble.lf index a116ef9c00..444f02d170 100644 --- a/test/C/src/multiport/MultiportToBankDouble.lf +++ b/test/C/src/multiport/MultiportToBankDouble.lf @@ -24,9 +24,9 @@ reactor Source { =} } -reactor Destination(bank_index: int(0)) { +reactor Destination(bank_index: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Destination %d received %d.\n", self->bank_index, in->value); diff --git a/test/C/src/multiport/MultiportToBankHierarchy.lf b/test/C/src/multiport/MultiportToBankHierarchy.lf index e4f231390f..fbdc531bf0 100644 --- a/test/C/src/multiport/MultiportToBankHierarchy.lf +++ b/test/C/src/multiport/MultiportToBankHierarchy.lf @@ -5,7 +5,7 @@ target C { fast: true } -reactor Source(width: int(2)) { +reactor Source(width: int = 2) { output[width] out: int reaction(startup) -> out {= @@ -15,9 +15,9 @@ reactor Source(width: int(2)) { =} } -reactor Destination(bank_index: int(0)) { +reactor Destination(bank_index: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Destination %d received %d.\n", self->bank_index, in->value); @@ -37,13 +37,13 @@ reactor Destination(bank_index: int(0)) { =} } -reactor Container(width: int(2)) { +reactor Container(width: int = 2) { input[width] in: int c = new[width] Destination() in -> c.in } -main reactor MultiportToBankHierarchy(width: int(3)) { +main reactor MultiportToBankHierarchy(width: int = 3) { a = new Source(width = width) b = new Container(width = width) a.out -> b.in diff --git a/test/C/src/multiport/MultiportToHierarchy.lf b/test/C/src/multiport/MultiportToHierarchy.lf index 24290b56a5..41ca505077 100644 --- a/test/C/src/multiport/MultiportToHierarchy.lf +++ b/test/C/src/multiport/MultiportToHierarchy.lf @@ -6,10 +6,10 @@ target C { fast: true } -reactor Source(width: int(2)) { +reactor Source(width: int = 2) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < 4; i++) { @@ -18,8 +18,8 @@ reactor Source(width: int(2)) { =} } -reactor Destination(width: int(4)) { - state s: int(6) +reactor Destination(width: int = 4) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -44,13 +44,13 @@ reactor Destination(width: int(4)) { =} } -reactor Container(width: int(4)) { +reactor Container(width: int = 4) { input[width] in: int dst = new Destination() in -> dst.in } -main reactor MultiportToHierarchy(width: int(4)) { +main reactor MultiportToHierarchy(width: int = 4) { a = new Source(width = width) b = new Container(width = width) a.out -> b.in diff --git a/test/C/src/multiport/MultiportToMultiport.lf b/test/C/src/multiport/MultiportToMultiport.lf index bf4bbefde4..375fa080e2 100644 --- a/test/C/src/multiport/MultiportToMultiport.lf +++ b/test/C/src/multiport/MultiportToMultiport.lf @@ -4,10 +4,10 @@ target C { fast: true } -reactor Source(width: int(1)) { +reactor Source(width: int = 1) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < out_width; i++) { @@ -19,8 +19,8 @@ reactor Source(width: int(1)) { =} } -reactor Destination(width: int(1)) { - state s: int(6) +reactor Destination(width: int = 1) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -45,7 +45,7 @@ reactor Destination(width: int(1)) { =} } -main reactor MultiportToMultiport(width: int(4)) { +main reactor MultiportToMultiport(width: int = 4) { a = new Source(width = width) b = new Destination(width = width) a.out -> b.in diff --git a/test/C/src/multiport/MultiportToMultiport2.lf b/test/C/src/multiport/MultiportToMultiport2.lf index 29a8632399..76858a2a86 100644 --- a/test/C/src/multiport/MultiportToMultiport2.lf +++ b/test/C/src/multiport/MultiportToMultiport2.lf @@ -1,7 +1,7 @@ // Test multiport to multiport connections. See also MultiportToMultiport. target C -reactor Source(width: int(2)) { +reactor Source(width: int = 2) { output[width] out: int reaction(startup) -> out {= @@ -11,7 +11,7 @@ reactor Source(width: int(2)) { =} } -reactor Destination(width: int(2)) { +reactor Destination(width: int = 2) { input[width] in: int reaction(in) {= @@ -30,9 +30,9 @@ reactor Destination(width: int(2)) { } main reactor MultiportToMultiport2( - width1: int(3), - width2: int(2), - width3: int(5) + width1: int = 3, + width2: int = 2, + width3: int = 5 ) { a1 = new Source(width = width1) a2 = new Source(width = width2) diff --git a/test/C/src/multiport/MultiportToMultiport2After.lf b/test/C/src/multiport/MultiportToMultiport2After.lf index b6f078a099..49585ea1b0 100644 --- a/test/C/src/multiport/MultiportToMultiport2After.lf +++ b/test/C/src/multiport/MultiportToMultiport2After.lf @@ -1,7 +1,7 @@ // Test multiport to multiport connections. See also MultiportToMultiport. target C -reactor Source(width: int(2)) { +reactor Source(width: int = 2) { output[width] out: int reaction(startup) -> out {= @@ -11,7 +11,7 @@ reactor Source(width: int(2)) { =} } -reactor Destination(width: int(2)) { +reactor Destination(width: int = 2) { input[width] in: int reaction(in) {= diff --git a/test/C/src/multiport/MultiportToMultiportArray.lf b/test/C/src/multiport/MultiportToMultiportArray.lf index 100a57c393..9d86a4a522 100644 --- a/test/C/src/multiport/MultiportToMultiportArray.lf +++ b/test/C/src/multiport/MultiportToMultiportArray.lf @@ -8,7 +8,7 @@ target C { reactor Source { timer t(0, 200 msec) output[2] out: int[] - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < 2; i++) { @@ -24,7 +24,7 @@ reactor Source { } reactor Destination { - state s: int(15) + state s: int = 15 input[2] in: int[] reaction(in) {= diff --git a/test/C/src/multiport/MultiportToMultiportParameter.lf b/test/C/src/multiport/MultiportToMultiportParameter.lf index 9dc7288a52..d1c96e5ddd 100644 --- a/test/C/src/multiport/MultiportToMultiportParameter.lf +++ b/test/C/src/multiport/MultiportToMultiportParameter.lf @@ -4,10 +4,10 @@ target C { fast: true } -reactor Source(width: int(1)) { +reactor Source(width: int = 1) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < out_width; i++) { @@ -19,8 +19,8 @@ reactor Source(width: int(1)) { =} } -reactor Destination(width: int(1)) { - state s: int(6) +reactor Destination(width: int = 1) { + state s: int = 6 input[width] in: int // Width is one larger than that of the source. reaction(in) {= @@ -45,7 +45,7 @@ reactor Destination(width: int(1)) { =} } -main reactor(width: int(4)) { +main reactor(width: int = 4) { a = new Source(width = width) b = new Destination(width = width) a.out -> b.in diff --git a/test/C/src/multiport/MultiportToPort.lf b/test/C/src/multiport/MultiportToPort.lf index ccee46566c..1784f2503c 100644 --- a/test/C/src/multiport/MultiportToPort.lf +++ b/test/C/src/multiport/MultiportToPort.lf @@ -16,9 +16,9 @@ reactor Source { =} } -reactor Destination(expected: int(0)) { +reactor Destination(expected: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= printf("Received: %d.\n", in->value); diff --git a/test/C/src/multiport/MultiportToReaction.lf b/test/C/src/multiport/MultiportToReaction.lf index 62e2c302f2..9281633079 100644 --- a/test/C/src/multiport/MultiportToReaction.lf +++ b/test/C/src/multiport/MultiportToReaction.lf @@ -4,9 +4,9 @@ target C { fast: true } -reactor Source(width: int(1)) { +reactor Source(width: int = 1) { timer t(0, 200 msec) - state s: int(0) + state s: int = 0 output[width] out: int reaction(t) -> out {= @@ -18,7 +18,7 @@ reactor Source(width: int(1)) { } main reactor MultiportToReaction { - state s: int(6) + state s: int = 6 b = new Source(width = 4) reaction(b.out) {= diff --git a/test/C/src/multiport/NestedBanks.lf b/test/C/src/multiport/NestedBanks.lf index b80ff2e606..bef08727f3 100644 --- a/test/C/src/multiport/NestedBanks.lf +++ b/test/C/src/multiport/NestedBanks.lf @@ -13,13 +13,13 @@ main reactor { (a.x)+ -> c.z, d.u, e.t } -reactor A(bank_index: int(0)) { +reactor A(bank_index: int = 0) { output[4] x: int b = new[2] B(a_bank_index = bank_index) b.y -> x } -reactor B(a_bank_index: int(0), bank_index: int(0)) { +reactor B(a_bank_index: int = 0, bank_index: int = 0) { output[2] y: int reaction(startup) -> y {= @@ -29,7 +29,7 @@ reactor B(a_bank_index: int(0), bank_index: int(0)) { =} } -reactor C(bank_index: int(0)) { +reactor C(bank_index: int = 0) { input[2] z: int f = new F(c_bank_index = bank_index) g = new G(c_bank_index = bank_index) @@ -59,7 +59,7 @@ reactor E { =} } -reactor F(c_bank_index: int(0)) { +reactor F(c_bank_index: int = 0) { input w: int reaction(w) {= @@ -70,7 +70,7 @@ reactor F(c_bank_index: int(0)) { =} } -reactor G(c_bank_index: int(0)) { +reactor G(c_bank_index: int = 0) { input s: int reaction(s) {= diff --git a/test/C/src/multiport/NestedInterleavedBanks.lf b/test/C/src/multiport/NestedInterleavedBanks.lf index 9f03313972..8e3056bd88 100644 --- a/test/C/src/multiport/NestedInterleavedBanks.lf +++ b/test/C/src/multiport/NestedInterleavedBanks.lf @@ -4,7 +4,7 @@ */ target C -reactor A(bank_index: int(0), outer_bank_index: int(0)) { +reactor A(bank_index: int = 0, outer_bank_index: int = 0) { output[2] p: int reaction(startup) -> p {= @@ -15,7 +15,7 @@ reactor A(bank_index: int(0), outer_bank_index: int(0)) { =} } -reactor B(bank_index: int(0)) { +reactor B(bank_index: int = 0) { output[4] q: int a = new[2] A(outer_bank_index = bank_index) interleaved (a.p) -> q diff --git a/test/C/src/multiport/ReactionToContainedBank.lf b/test/C/src/multiport/ReactionToContainedBank.lf index 3e0afa17d5..30f3f5564c 100644 --- a/test/C/src/multiport/ReactionToContainedBank.lf +++ b/test/C/src/multiport/ReactionToContainedBank.lf @@ -6,9 +6,9 @@ target C { import TestCount from "../lib/TestCount.lf" -main reactor ReactionToContainedBank(width: int(2)) { +main reactor ReactionToContainedBank(width: int = 2) { timer t(0, 100 msec) - state count: int(1) + state count: int = 1 test = new[width] TestCount(num_inputs = 11) diff --git a/test/C/src/multiport/ReactionToContainedBank2.lf b/test/C/src/multiport/ReactionToContainedBank2.lf index b7c7d1cf92..e03e884558 100644 --- a/test/C/src/multiport/ReactionToContainedBank2.lf +++ b/test/C/src/multiport/ReactionToContainedBank2.lf @@ -6,9 +6,9 @@ target C { import TestCount from "../lib/TestCount.lf" -reactor ReactionToContainedBank(width: int(2)) { +reactor ReactionToContainedBank(width: int = 2) { timer t(0, 100 msec) - state count: int(1) + state count: int = 1 test = new[width] TestCount(num_inputs = 11) @@ -20,7 +20,7 @@ reactor ReactionToContainedBank(width: int(2)) { =} } -main reactor(width: int(2)) { +main reactor(width: int = 2) { a = new ReactionToContainedBank(width = width) b = new ReactionToContainedBank(width = 4) } diff --git a/test/C/src/multiport/ReactionToContainedBankMultiport.lf b/test/C/src/multiport/ReactionToContainedBankMultiport.lf index 6a9e067ca1..d84b21c191 100644 --- a/test/C/src/multiport/ReactionToContainedBankMultiport.lf +++ b/test/C/src/multiport/ReactionToContainedBankMultiport.lf @@ -7,9 +7,9 @@ target C { import TestCountMultiport from "../lib/TestCountMultiport.lf" -main reactor(width: int(2)) { +main reactor(width: int = 2) { timer t(0, 100 msec) - state count: int(1) + state count: int = 1 test = new[width] TestCountMultiport(num_inputs = 11, width = width) diff --git a/test/C/src/multiport/ReactionsToNested.lf b/test/C/src/multiport/ReactionsToNested.lf index 102df10f8b..4270f55c2d 100644 --- a/test/C/src/multiport/ReactionsToNested.lf +++ b/test/C/src/multiport/ReactionsToNested.lf @@ -4,9 +4,9 @@ target C { timeout: 1 sec } -reactor T(expected: int(0)) { +reactor T(expected: int = 0) { input z: int - state received: bool(false) + state received: bool = false reaction(z) {= lf_print("T received %d", z->value); diff --git a/test/C/src/multiport/Sparse.lf b/test/C/src/multiport/Sparse.lf index eaa75782cb..537daabb4b 100644 --- a/test/C/src/multiport/Sparse.lf +++ b/test/C/src/multiport/Sparse.lf @@ -3,9 +3,9 @@ target C { timeout: 20 ms } -reactor SparseSource(width: int(20)) { +reactor SparseSource(width: int = 20) { output[width] out: int - state count: int(0) + state count: int = 0 timer t(0, 1 msec) reaction(t) -> out {= @@ -19,7 +19,7 @@ reactor SparseSource(width: int(20)) { =} } -reactor SparseSink(width: int(20)) { +reactor SparseSink(width: int = 20) { input[width] in: int reaction(in) {= @@ -43,7 +43,7 @@ reactor SparseSink(width: int(20)) { =} } -main reactor(width: int(20)) { +main reactor(width: int = 20) { s = new SparseSource(width = width) d = new SparseSink(width = width) s.out -> d.in diff --git a/test/C/src/multiport/SparseFallback.lf b/test/C/src/multiport/SparseFallback.lf index 195dca1f19..a7788858d8 100644 --- a/test/C/src/multiport/SparseFallback.lf +++ b/test/C/src/multiport/SparseFallback.lf @@ -6,9 +6,9 @@ target C { timeout: 20 ms } -reactor SparseSource(width: int(20)) { +reactor SparseSource(width: int = 20) { output[width] out: int - state count: int(0) + state count: int = 0 timer t(0, 1 msec) reaction(t) -> out {= @@ -22,7 +22,7 @@ reactor SparseSource(width: int(20)) { =} } -reactor SparseSink(width: int(20)) { +reactor SparseSink(width: int = 20) { input[width] in: int reaction(in) {= @@ -46,7 +46,7 @@ reactor SparseSink(width: int(20)) { =} } -main reactor(width: int(10)) { +main reactor(width: int = 10) { s = new SparseSource(width = width) d = new SparseSink(width = width) s.out -> d.in diff --git a/test/C/src/multiport/TriggerDownstreamOnlyIfPresent.lf b/test/C/src/multiport/TriggerDownstreamOnlyIfPresent.lf index c696261db7..917982af39 100644 --- a/test/C/src/multiport/TriggerDownstreamOnlyIfPresent.lf +++ b/test/C/src/multiport/TriggerDownstreamOnlyIfPresent.lf @@ -10,7 +10,7 @@ target C { reactor Source { output a: int output b: int - state count: int(0) + state count: int = 0 timer t(0, 200 msec) reaction(t) -> a, b {= diff --git a/test/C/src/serialization/ROSBuiltInSerialization.lf b/test/C/src/serialization/ROSBuiltInSerialization.lf index d573b23cce..095aadacd2 100644 --- a/test/C/src/serialization/ROSBuiltInSerialization.lf +++ b/test/C/src/serialization/ROSBuiltInSerialization.lf @@ -32,7 +32,7 @@ reactor Sender { output out: std_msgs::msg::Int32 // state serialized_msg_pcl:rclcpp::SerializedMessage({=0u=}); - state count: int(0) + state count: int = 0 timer t(0, 1 sec) @@ -45,7 +45,7 @@ reactor Sender { reactor Receiver { input in: std_msgs::msg::Int32 - state count: int(0) + state count: int = 0 reaction(in) {= // Print the ROS2 message data diff --git a/test/C/src/serialization/ROSBuiltInSerializationSharedPtr.lf b/test/C/src/serialization/ROSBuiltInSerializationSharedPtr.lf index 4726809c57..97b5a12554 100644 --- a/test/C/src/serialization/ROSBuiltInSerializationSharedPtr.lf +++ b/test/C/src/serialization/ROSBuiltInSerializationSharedPtr.lf @@ -31,7 +31,7 @@ preamble {= reactor Sender { output out: std::shared_ptr - state count: int(0) + state count: int = 0 timer t(0, 1 sec) @@ -44,7 +44,7 @@ reactor Sender { reactor Receiver { input in: std::shared_ptr - state count: int(0) + state count: int = 0 reaction(in) {= // Print the ROS2 message data diff --git a/test/C/src/target/HelloWorldCCPP.lf b/test/C/src/target/HelloWorldCCPP.lf index 0b0e54ed52..91d1cd61d1 100644 --- a/test/C/src/target/HelloWorldCCPP.lf +++ b/test/C/src/target/HelloWorldCCPP.lf @@ -11,7 +11,7 @@ reactor HelloWorld { preamble {= #include =} - state success: bool(false) + state success: bool = false reaction(startup) {= std::cout << "Hello World." << std::endl; diff --git a/test/C/src/token/TokenContainedPrint.lf b/test/C/src/token/TokenContainedPrint.lf index 0d4b19379d..29e6005c4c 100644 --- a/test/C/src/token/TokenContainedPrint.lf +++ b/test/C/src/token/TokenContainedPrint.lf @@ -9,7 +9,7 @@ target C { import TokenPrint from "lib/Token.lf" main reactor { - state count: int(0) + state count: int = 0 timer t(0, 1 ms) p = new TokenPrint() diff --git a/test/C/src/token/TokenContainedPrintBank.lf b/test/C/src/token/TokenContainedPrintBank.lf index b23eb1c109..991a94b5a7 100644 --- a/test/C/src/token/TokenContainedPrintBank.lf +++ b/test/C/src/token/TokenContainedPrintBank.lf @@ -10,7 +10,7 @@ target C { import TokenPrint from "lib/Token.lf" main reactor { - state count: int(0) + state count: int = 0 timer t(0, 1 ms) p = new[2] TokenPrint() diff --git a/test/C/src/token/TokenContainedSource.lf b/test/C/src/token/TokenContainedSource.lf index 9bf517bc18..66095b8a0b 100644 --- a/test/C/src/token/TokenContainedSource.lf +++ b/test/C/src/token/TokenContainedSource.lf @@ -9,9 +9,9 @@ target C { import TokenSource from "lib/Token.lf" -main reactor(scale: int(1)) { - state count: int(0) - state input_received: bool(false) +main reactor(scale: int = 1) { + state count: int = 0 + state input_received: bool = false s = new TokenSource() diff --git a/test/C/src/token/TokenContainedSourceBank.lf b/test/C/src/token/TokenContainedSourceBank.lf index f1faee87dd..615695890b 100644 --- a/test/C/src/token/TokenContainedSourceBank.lf +++ b/test/C/src/token/TokenContainedSourceBank.lf @@ -10,9 +10,9 @@ target C { import TokenSource from "lib/Token.lf" -main reactor(scale: int(1)) { - state count: int(0) - state input_received: bool(false) +main reactor(scale: int = 1) { + state count: int = 0 + state input_received: bool = false s = new[2] TokenSource() diff --git a/test/C/src/token/lib/Token.lf b/test/C/src/token/lib/Token.lf index 4e49cd0006..7b8d2a83ee 100644 --- a/test/C/src/token/lib/Token.lf +++ b/test/C/src/token/lib/Token.lf @@ -20,7 +20,7 @@ preamble {= */ reactor TokenSource { output out: int_array_t* - state count: int(0) + state count: int = 0 timer t(0, 1 ms) reaction(startup) -> out {= @@ -42,10 +42,10 @@ reactor TokenSource { * value is a scaled version of the sequence of values produced by the * TokenSource, where the scaling factor is given by the scale parameter. */ -reactor TokenPrint(scale: int(1)) { +reactor TokenPrint(scale: int = 1) { input in: int_array_t* - state count: int(0) - state input_received: bool(false) + state count: int = 0 + state input_received: bool = false reaction(in) {= self->input_received = true; @@ -79,7 +79,7 @@ reactor TokenPrint(scale: int(1)) { * input is declared mutable so, if possible, the input token will be modified * rather than allocating a new token. */ -reactor TokenScale(scale: int(2)) { +reactor TokenScale(scale: int = 2) { mutable input in: int_array_t* output out: int_array_t* diff --git a/test/Cpp/src/ActionDelay.lf b/test/Cpp/src/ActionDelay.lf index 5bb1402e05..14210e1009 100644 --- a/test/Cpp/src/ActionDelay.lf +++ b/test/Cpp/src/ActionDelay.lf @@ -4,7 +4,7 @@ target Cpp reactor GeneratedDelay { input y_in: int output y_out: int - state y_state: int{0} + state y_state: int = 0 logical action act(100 msec): void reaction(y_in) -> act {= diff --git a/test/Cpp/src/ActionIsPresent.lf b/test/Cpp/src/ActionIsPresent.lf index 7b8267d606..e98c2a2a32 100644 --- a/test/Cpp/src/ActionIsPresent.lf +++ b/test/Cpp/src/ActionIsPresent.lf @@ -1,10 +1,10 @@ // Tests the is_present variable for actions in cpp target Cpp -main reactor ActionIsPresent(offset: time(1 nsec), period: time(500 msec)) { +main reactor ActionIsPresent(offset: time = 1 nsec, period: time(500 msec)) { logical action a - state success: bool(false) - state zero: time(0 nsec) + state success: bool = false + state zero: time = 0 nsec reaction(startup, a) -> a {= if (!a.is_present()) { diff --git a/test/Cpp/src/ActionValues.lf b/test/Cpp/src/ActionValues.lf index 18d06b7b3c..8d115c60fa 100644 --- a/test/Cpp/src/ActionValues.lf +++ b/test/Cpp/src/ActionValues.lf @@ -2,8 +2,8 @@ target Cpp main reactor ActionValues { - state r1done: bool(false) - state r2done: bool(false) + state r1done: bool = false + state r2done: bool = false logical action act(100 msec): int reaction(startup) -> act {= diff --git a/test/Cpp/src/After.lf b/test/Cpp/src/After.lf index 177ebfbe01..bbcc287b78 100644 --- a/test/Cpp/src/After.lf +++ b/test/Cpp/src/After.lf @@ -13,8 +13,8 @@ reactor foo { } reactor print { - state expected_time: time(10 msec) - state i: int(0) + state expected_time: time = 10 msec + state i: int = 0 input x: int reaction(x) {= diff --git a/test/Cpp/src/AfterOverlapped.lf b/test/Cpp/src/AfterOverlapped.lf index 926c4609c2..6a85eaf5bc 100644 --- a/test/Cpp/src/AfterOverlapped.lf +++ b/test/Cpp/src/AfterOverlapped.lf @@ -8,7 +8,7 @@ import Count from "lib/Count.lf" reactor Test { input c: int - state i: int(0) + state i: int = 0 reaction(c) {= std::cout << "Received " << *c.get() << '\n'; diff --git a/test/Cpp/src/AfterZero.lf b/test/Cpp/src/AfterZero.lf index 8baa157e99..152b87cfc2 100644 --- a/test/Cpp/src/AfterZero.lf +++ b/test/Cpp/src/AfterZero.lf @@ -13,8 +13,8 @@ reactor foo { } reactor print { - state expected_time: time(0) - state i: int(0) + state expected_time: time = 0 + state i: int = 0 input x: int reaction(x) {= diff --git a/test/Cpp/src/Alignment.lf b/test/Cpp/src/Alignment.lf index 45e52cb573..1bd3562f41 100644 --- a/test/Cpp/src/Alignment.lf +++ b/test/Cpp/src/Alignment.lf @@ -8,7 +8,7 @@ target Cpp { reactor Source { output out: int - state count: int(1) + state count: int = 1 timer t(0, 100 msec) reaction(t) -> out {= diff --git a/test/Cpp/src/ArrayAsParameter.lf b/test/Cpp/src/ArrayAsParameter.lf index 77795deb56..85cd0630bf 100644 --- a/test/Cpp/src/ArrayAsParameter.lf +++ b/test/Cpp/src/ArrayAsParameter.lf @@ -2,9 +2,9 @@ // passes to Print. target Cpp -reactor Source(sequence: int[]{0, 1, 2}) { +reactor Source(sequence: std::vector = {0, 1, 2}) { output out: size_t - state count: size_t{0} + state count: size_t = 0 logical action next: void reaction(startup, next) -> out, next {= @@ -18,7 +18,7 @@ reactor Source(sequence: int[]{0, 1, 2}) { reactor Print { input in: size_t - state count: size_t{1} + state count: size_t = 1 reaction(in) {= std::cout << "Received: " << *in.get() << '\n'; @@ -38,7 +38,7 @@ reactor Print { } main reactor ArrayAsParameter { - s = new Source(sequence = {= {1, 2, 3, 4} =}) + s = new Source(sequence = {1, 2, 3, 4}) p = new Print() s.out -> p.in } diff --git a/test/Cpp/src/ArrayPrint.lf b/test/Cpp/src/ArrayPrint.lf index 420f7902db..3bdf494107 100644 --- a/test/Cpp/src/ArrayPrint.lf +++ b/test/Cpp/src/ArrayPrint.lf @@ -18,7 +18,7 @@ reactor Source { =} } -reactor Print(scale: int(1)) { +reactor Print(scale: int = 1) { input in: int[3] reaction(in) {= diff --git a/test/Cpp/src/ArrayScale.lf b/test/Cpp/src/ArrayScale.lf index f5b2e56b30..b52e7305b7 100644 --- a/test/Cpp/src/ArrayScale.lf +++ b/test/Cpp/src/ArrayScale.lf @@ -5,7 +5,7 @@ target Cpp import Source, Print from "ArrayPrint.lf" -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { input in: int[3] output out: int[3] diff --git a/test/Cpp/src/CharLiteralInitializer.lf b/test/Cpp/src/CharLiteralInitializer.lf index 2d0edb71b8..e01e5973a6 100644 --- a/test/Cpp/src/CharLiteralInitializer.lf +++ b/test/Cpp/src/CharLiteralInitializer.lf @@ -2,7 +2,7 @@ target Cpp main reactor CharLiteralInitializer { - state c: char('x') + state c: char = 'x' reaction(startup) {= if (c != 'x') { diff --git a/test/Cpp/src/Composition.lf b/test/Cpp/src/Composition.lf index 6ac7364a51..0a341d5e4f 100644 --- a/test/Cpp/src/Composition.lf +++ b/test/Cpp/src/Composition.lf @@ -5,10 +5,10 @@ target Cpp { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= count++; @@ -18,7 +18,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= count++; diff --git a/test/Cpp/src/CompositionAfter.lf b/test/Cpp/src/CompositionAfter.lf index 06f71a6fbb..15d68e13ea 100644 --- a/test/Cpp/src/CompositionAfter.lf +++ b/test/Cpp/src/CompositionAfter.lf @@ -5,10 +5,10 @@ target Cpp { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= count++; @@ -18,7 +18,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= count++; @@ -38,7 +38,7 @@ reactor Test { =} } -main reactor(delay: time(5 sec)) { +main reactor(delay: time = 5 sec) { s = new Source() d = new Test() s.y -> d.x after delay diff --git a/test/Cpp/src/CountTest.lf b/test/Cpp/src/CountTest.lf index 63a494bc61..838d84a998 100644 --- a/test/Cpp/src/CountTest.lf +++ b/test/Cpp/src/CountTest.lf @@ -7,7 +7,7 @@ import Count from "lib/Count.lf" reactor Test { input c: int - state i: int(0) + state i: int = 0 reaction(c) {= i++; diff --git a/test/Cpp/src/Deadline.lf b/test/Cpp/src/Deadline.lf index 81eab8a004..19a41ff057 100644 --- a/test/Cpp/src/Deadline.lf +++ b/test/Cpp/src/Deadline.lf @@ -5,13 +5,13 @@ target Cpp { timeout: 4 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { private preamble {= #include =} output y: int timer t(0, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= if (count % 2 == 1) { @@ -25,9 +25,9 @@ reactor Source(period: time(2 sec)) { =} } -reactor Destination(timeout: time(1 sec)) { +reactor Destination(timeout: time = 1 sec) { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= std::cout << "Destination receives: " << *x.get() << std::endl; diff --git a/test/Cpp/src/DeadlineHandledAbove.lf b/test/Cpp/src/DeadlineHandledAbove.lf index 76a790fcbf..b2a6c7658a 100644 --- a/test/Cpp/src/DeadlineHandledAbove.lf +++ b/test/Cpp/src/DeadlineHandledAbove.lf @@ -2,7 +2,7 @@ // container reacts to that output. target Cpp -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { input x: int output deadline_violation: bool @@ -16,7 +16,7 @@ reactor Deadline(threshold: time(100 msec)) { } main reactor DeadlineHandledAbove { - state violation_detected: bool({= false =}) + state violation_detected: bool = {= false =} d = new Deadline(threshold = 10 msec) reaction(startup) -> d.x {= diff --git a/test/Cpp/src/DelayInt.lf b/test/Cpp/src/DelayInt.lf index 379e22a3ec..f58e5ef45b 100644 --- a/test/Cpp/src/DelayInt.lf +++ b/test/Cpp/src/DelayInt.lf @@ -1,7 +1,7 @@ // This tests actions with payloads by delaying an input by a fixed amount. target Cpp -reactor Delay(delay: time(100 msec)) { +reactor Delay(delay: time = 100 msec) { input in: int output out: int logical action d: int diff --git a/test/Cpp/src/DelayedAction.lf b/test/Cpp/src/DelayedAction.lf index 0b2b748909..1ce1aea63f 100644 --- a/test/Cpp/src/DelayedAction.lf +++ b/test/Cpp/src/DelayedAction.lf @@ -6,7 +6,7 @@ target Cpp { main reactor DelayedAction { timer t(0, 1 sec) logical action a: void - state count: int(0) + state count: int = 0 reaction(t) -> a {= a.schedule(100ms); =} diff --git a/test/Cpp/src/DoubleInvocation.lf b/test/Cpp/src/DoubleInvocation.lf index 3315fafae4..956b54bb17 100644 --- a/test/Cpp/src/DoubleInvocation.lf +++ b/test/Cpp/src/DoubleInvocation.lf @@ -14,7 +14,7 @@ target Cpp { reactor Ball { output position: int output velocity: int - state p: int(200) + state p: int = 200 timer trigger(0, 1 sec) reaction(trigger) -> position, velocity {= @@ -27,7 +27,7 @@ reactor Ball { reactor Print { input velocity: int input position: int - state previous: int(-1) + state previous: int = -1 reaction(startup) {= reactor::log::Info() << "####### Print startup"; diff --git a/test/Cpp/src/DoublePort.lf b/test/Cpp/src/DoublePort.lf index 00950b1331..faaa474db5 100644 --- a/test/Cpp/src/DoublePort.lf +++ b/test/Cpp/src/DoublePort.lf @@ -13,7 +13,7 @@ target Cpp { import Count from "lib/Count.lf" reactor CountMicrostep { - state count: int(1) + state count: int = 1 output out: int logical action act: int timer t(0, 1 sec) diff --git a/test/Cpp/src/DoubleReaction.lf b/test/Cpp/src/DoubleReaction.lf index d5e8964089..1128d63f97 100644 --- a/test/Cpp/src/DoubleReaction.lf +++ b/test/Cpp/src/DoubleReaction.lf @@ -5,10 +5,10 @@ target Cpp { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= count++; @@ -19,7 +19,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int input w: int - state s: int(2) + state s: int = 2 reaction(x, w) {= int sum = 0; diff --git a/test/Cpp/src/DoubleTrigger.lf b/test/Cpp/src/DoubleTrigger.lf index c714d83e9c..5db4818048 100644 --- a/test/Cpp/src/DoubleTrigger.lf +++ b/test/Cpp/src/DoubleTrigger.lf @@ -5,7 +5,7 @@ target Cpp main reactor DoubleTrigger { timer t1 timer t2 - state s: int(0) + state s: int = 0 reaction(t1, t2) {= s++; diff --git a/test/Cpp/src/FloatLiteral.lf b/test/Cpp/src/FloatLiteral.lf index a65af798b4..6ad95f9e55 100644 --- a/test/Cpp/src/FloatLiteral.lf +++ b/test/Cpp/src/FloatLiteral.lf @@ -2,10 +2,10 @@ target Cpp // This test verifies that floating-point literals are handled correctly. main reactor { - state N: double(6.0221409e+23) - state charge: double(-1.6021766E-19) - state minus_epsilon: double(-.01e0) - state expected: double(.964853323188E5) + state N: double = 6.0221409e+23 + state charge: double = -1.6021766E-19 + state minus_epsilon: double = -.01e0 + state expected: double = .964853323188E5 reaction(startup) {= auto F = - N * charge; diff --git a/test/Cpp/src/Gain.lf b/test/Cpp/src/Gain.lf index 4a62a62a8e..4b123cc992 100644 --- a/test/Cpp/src/Gain.lf +++ b/test/Cpp/src/Gain.lf @@ -1,7 +1,7 @@ // Example in the Wiki. target Cpp -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { input x: int output y: int diff --git a/test/Cpp/src/GetMicroStep.lf b/test/Cpp/src/GetMicroStep.lf index c8eb89a4fa..8370a21403 100644 --- a/test/Cpp/src/GetMicroStep.lf +++ b/test/Cpp/src/GetMicroStep.lf @@ -2,7 +2,7 @@ target Cpp main reactor GetMicroStep { - state s: {= reactor::mstep_t =}(1) + state s: {= reactor::mstep_t =} = 1 logical action l diff --git a/test/Cpp/src/Hello.lf b/test/Cpp/src/Hello.lf index c63afbabe4..f74acf6688 100644 --- a/test/Cpp/src/Hello.lf +++ b/test/Cpp/src/Hello.lf @@ -8,8 +8,11 @@ target Cpp { fast: true } -reactor HelloCpp(period: time(2 sec), message: {= std::string =}("Hello C++")) { - state count: int(0) +reactor HelloCpp( + period: time = 2 sec, + message: {= std::string =} = "Hello C++" +) { + state count: int = 0 state previous_time: {= reactor::TimePoint =} timer t(1 sec, period) logical action a: void @@ -37,8 +40,8 @@ reactor HelloCpp(period: time(2 sec), message: {= std::string =}("Hello C++")) { } reactor Inside( - period: time(1 sec), - message: std::string("Composite default message.") + period: time = 1 sec, + message: std::string = "Composite default message." ) { third_instance = new HelloCpp(period = period, message = message) } diff --git a/test/Cpp/src/Hierarchy2.lf b/test/Cpp/src/Hierarchy2.lf index 4e78e3406e..db81bf4971 100644 --- a/test/Cpp/src/Hierarchy2.lf +++ b/test/Cpp/src/Hierarchy2.lf @@ -14,7 +14,7 @@ reactor Source { reactor Count { output out: int timer t(0, 1 sec) - state i: int(0) + state i: int = 0 reaction(t) -> out {= i++; @@ -37,7 +37,7 @@ reactor Add { reactor Print { input in: int - state expected: int(2) + state expected: int = 2 reaction(in) {= auto value = *in.get(); diff --git a/test/Cpp/src/ImportComposition.lf b/test/Cpp/src/ImportComposition.lf index 4b8649a243..0559edacd1 100644 --- a/test/Cpp/src/ImportComposition.lf +++ b/test/Cpp/src/ImportComposition.lf @@ -16,7 +16,7 @@ main reactor ImportComposition { =} imp_comp = new ImportedComposition() - state received: bool(false) + state received: bool = false reaction(startup) -> imp_comp.x {= imp_comp.x.set(42); =} diff --git a/test/Cpp/src/ManualDelayedReaction.lf b/test/Cpp/src/ManualDelayedReaction.lf index 2d426bffb7..3c55bdcaf1 100644 --- a/test/Cpp/src/ManualDelayedReaction.lf +++ b/test/Cpp/src/ManualDelayedReaction.lf @@ -4,7 +4,7 @@ target Cpp reactor GeneratedDelay { input y_in: int output y_out: int - state y_state: int(0) + state y_state: int = 0 logical action act(100 msec): int diff --git a/test/Cpp/src/Methods.lf b/test/Cpp/src/Methods.lf index fc4decbcbd..ff94460b62 100644 --- a/test/Cpp/src/Methods.lf +++ b/test/Cpp/src/Methods.lf @@ -1,7 +1,7 @@ target Cpp main reactor { - state foo: int(2) + state foo: int = 2 const method getFoo(): int {= return foo; =} diff --git a/test/Cpp/src/MovingAverage.lf b/test/Cpp/src/MovingAverage.lf index 160688c2ed..af202ca0a0 100644 --- a/test/Cpp/src/MovingAverage.lf +++ b/test/Cpp/src/MovingAverage.lf @@ -8,7 +8,7 @@ target Cpp { reactor Source { output out: double - state count: int{0} + state count: int = 0 timer clock(0, 200 msec) reaction(clock) -> out {= @@ -19,7 +19,7 @@ reactor Source { reactor MovingAverageImpl { state delay_line: double[3]{0.0, 0.0, 0.0} - state index: int{0} + state index: int = 0 input in: double output out: double @@ -44,7 +44,7 @@ reactor MovingAverageImpl { reactor Print { input in: double - state count: int{0} + state count: int = 0 reaction(in) {= std::cout << "Received: " << *in.get() << '\n'; diff --git a/test/Cpp/src/NativeListsAndTimes.lf b/test/Cpp/src/NativeListsAndTimes.lf index 5ec46d2930..5bf67c8fb4 100644 --- a/test/Cpp/src/NativeListsAndTimes.lf +++ b/test/Cpp/src/NativeListsAndTimes.lf @@ -2,27 +2,29 @@ target Cpp // This test passes if it is successfully compiled into valid target code. reactor Foo( - x: int(0), - y: time(0), // Units are missing but not required - z(1 msec), // Type is missing but not required - p: int[]{1, 2, 3, 4}, // List of integers + x: int = 0, + y: time = 0, // Units are missing but not required + z = 1 msec, // Type is missing but not required + p: int[]{1, 2, 3, 4}, // List of integers q: {= // list of time values std::vector =}{1 msec, 2 msec, 3 msec}, - g: time[]{1 msec, 2 msec} // List of time values + g: time[]{1 msec, 2 msec}, // List of time values + g2: int[] = {} ) { - state s: time(y) // Reference to explicitly typed time parameter - state t: time(z) // Reference to implicitly typed time parameter + state s: time = y // Reference to explicitly typed time parameter + state t: time = z // Reference to implicitly typed time parameter state v: bool // Uninitialized boolean state variable state w: time // Uninitialized time state variable timer tick(0) // Units missing but not required timer tock(1 sec) // Implicit type time timer toe(z) // Implicit type time - state baz(p) // Implicit type int[] - state period(z) // Implicit type time + state baz = p // Implicit type int[] + state period = z // Implicit type time state times: std::vector< // a list of lists std::vector<{= reactor::Duration =}> >{q, g} + state empty_list: int[] = {} reaction(tick) {= // Target code diff --git a/test/Cpp/src/NestedTriggeredReactions.lf b/test/Cpp/src/NestedTriggeredReactions.lf index dcaa64b179..783dcefc7e 100644 --- a/test/Cpp/src/NestedTriggeredReactions.lf +++ b/test/Cpp/src/NestedTriggeredReactions.lf @@ -3,7 +3,7 @@ target Cpp reactor Container { input in: void - state triggered: bool{false} + state triggered: bool = false contained = new Contained() @@ -22,7 +22,7 @@ reactor Container { reactor Contained { input in: void - state triggered: bool{false} + state triggered: bool = false reaction(in) {= triggered = true; =} diff --git a/test/Cpp/src/ParameterHierarchy.lf b/test/Cpp/src/ParameterHierarchy.lf index acd051a5ce..c183cbca03 100644 --- a/test/Cpp/src/ParameterHierarchy.lf +++ b/test/Cpp/src/ParameterHierarchy.lf @@ -7,7 +7,7 @@ */ target Cpp -reactor Deep(p: int(0)) { +reactor Deep(p: int = 0) { reaction(startup) {= if(p != 42) { reactor::log::Error() << "Parameter value is: " << p << ". Should have been 42."; @@ -18,11 +18,11 @@ reactor Deep(p: int(0)) { =} } -reactor Intermediate(p: int(10)) { +reactor Intermediate(p: int = 10) { a = new Deep(p = p) } -reactor Another(p: int(20)) { +reactor Another(p: int = 20) { // also test forwarding parameters via target code blocks a = new Intermediate(p = {= p =}) } diff --git a/test/Cpp/src/ParameterizedState.lf b/test/Cpp/src/ParameterizedState.lf index be03a52b9a..1c39043a75 100644 --- a/test/Cpp/src/ParameterizedState.lf +++ b/test/Cpp/src/ParameterizedState.lf @@ -1,7 +1,7 @@ target Cpp -reactor Foo(bar: int(4)) { - state baz(bar) +reactor Foo(bar: int = 4) { + state baz = bar reaction(startup) {= std::cout << "Baz: " << baz << std::endl; diff --git a/test/Cpp/src/ParametersOutOfOrder.lf b/test/Cpp/src/ParametersOutOfOrder.lf index 64dcbc6ef3..7ef28b914a 100644 --- a/test/Cpp/src/ParametersOutOfOrder.lf +++ b/test/Cpp/src/ParametersOutOfOrder.lf @@ -2,7 +2,7 @@ // definition order. Compilation without errors is success. target Cpp -reactor Foo(a: int(0), b: std::string(""), c: float(0.0)) { +reactor Foo(a: int = 0, b: std::string = "", c: float = 0.0) { reaction(startup) {= if (a != 42 || b != "bar" || c < 3.1) { reactor::log::Error() << "received an unexpected parameter!"; diff --git a/test/Cpp/src/PeriodicDesugared.lf b/test/Cpp/src/PeriodicDesugared.lf index 2b6e92c750..2823835687 100644 --- a/test/Cpp/src/PeriodicDesugared.lf +++ b/test/Cpp/src/PeriodicDesugared.lf @@ -3,10 +3,10 @@ target Cpp { timeout: 5 secs } -main reactor(offset: time(50 msec), period: time(500 msec)) { +main reactor(offset: time = 50 msec, period: time = 500 msec) { logical action init(offset): void logical action recur(period): void - state expected(offset) + state expected = offset reaction(startup) -> init {= std::cout << "Hello from Periodic!\n"; diff --git a/test/Cpp/src/Pipeline.lf b/test/Cpp/src/Pipeline.lf index c4300f13c9..cc86ef67a0 100644 --- a/test/Cpp/src/Pipeline.lf +++ b/test/Cpp/src/Pipeline.lf @@ -6,7 +6,7 @@ import Computation from "concurrent/Threaded.lf" reactor Print { input in: int - state count: int(0) + state count: int = 0 reaction(in) {= std::cout << "Received: " << *in.get() << '\n'; @@ -27,7 +27,7 @@ reactor Print { main reactor Pipeline { timer t(0, 200 msec) - state count: int(0) + state count: int = 0 c1 = new Computation() c2 = new Computation() diff --git a/test/Cpp/src/ReadOutputOfContainedReactor.lf b/test/Cpp/src/ReadOutputOfContainedReactor.lf index 6cba642f04..c7e85083b4 100644 --- a/test/Cpp/src/ReadOutputOfContainedReactor.lf +++ b/test/Cpp/src/ReadOutputOfContainedReactor.lf @@ -10,7 +10,7 @@ reactor Contained { main reactor ReadOutputOfContainedReactor { c = new Contained() - state count: int(0) + state count: int = 0 reaction(startup) c.out {= std::cout << "Startup reaction reading output of contained " diff --git a/test/Cpp/src/ScheduleLogicalAction.lf b/test/Cpp/src/ScheduleLogicalAction.lf index c8eaea1a30..96fd5382b7 100644 --- a/test/Cpp/src/ScheduleLogicalAction.lf +++ b/test/Cpp/src/ScheduleLogicalAction.lf @@ -26,7 +26,7 @@ reactor foo { } reactor print { - state expected_time: time(0) + state expected_time: time = 0 input x: int reaction(x) {= diff --git a/test/Cpp/src/SelfLoop.lf b/test/Cpp/src/SelfLoop.lf index be08cc771b..2be47e3d4e 100644 --- a/test/Cpp/src/SelfLoop.lf +++ b/test/Cpp/src/SelfLoop.lf @@ -12,7 +12,7 @@ reactor Self { input x: int output y: int logical action a: int - state expected: int(43) + state expected: int = 43 reaction(a) -> y {= reactor::log::Info() << "a = " << *a.get(); diff --git a/test/Cpp/src/SendingInside.lf b/test/Cpp/src/SendingInside.lf index 2d947f686b..2f73122bc8 100644 --- a/test/Cpp/src/SendingInside.lf +++ b/test/Cpp/src/SendingInside.lf @@ -7,7 +7,7 @@ target Cpp { reactor Printer { input x: int - state count: int(1) + state count: int = 1 reaction(x) {= std::cout << "Inside reactor received: " << *x.get() << std::endl; @@ -20,7 +20,7 @@ reactor Printer { } main reactor SendingInside { - state count: int(0) + state count: int = 0 timer t(0, 1 sec) p = new Printer() diff --git a/test/Cpp/src/SimpleDeadline.lf b/test/Cpp/src/SimpleDeadline.lf index 17c354108a..be10b55d43 100644 --- a/test/Cpp/src/SimpleDeadline.lf +++ b/test/Cpp/src/SimpleDeadline.lf @@ -3,7 +3,7 @@ // violation. target Cpp -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { private preamble {= #include =} diff --git a/test/Cpp/src/SlowingClock.lf b/test/Cpp/src/SlowingClock.lf index 41dda03dba..adbe93e418 100644 --- a/test/Cpp/src/SlowingClock.lf +++ b/test/Cpp/src/SlowingClock.lf @@ -15,8 +15,8 @@ target Cpp { main reactor SlowingClock { logical action a(100 msec) - state interval: time(100 msec) - state expected_time: time(100 msec) + state interval: time = 100 msec + state expected_time: time = 100 msec reaction(startup) -> a {= a.schedule(0ns); =} diff --git a/test/Cpp/src/SlowingClockPhysical.lf b/test/Cpp/src/SlowingClockPhysical.lf index 24f77cf2be..919f9d6cdf 100644 --- a/test/Cpp/src/SlowingClockPhysical.lf +++ b/test/Cpp/src/SlowingClockPhysical.lf @@ -15,8 +15,8 @@ target Cpp { main reactor SlowingClockPhysical { physical action a - state interval: time(100 msec) - state expected_time: time(100 msec) + state interval: time = 100 msec + state expected_time: time = 100 msec reaction(startup) -> a {= expected_time=100ms; diff --git a/test/Cpp/src/Stride.lf b/test/Cpp/src/Stride.lf index 8adeaa69b0..5f469e4c70 100644 --- a/test/Cpp/src/Stride.lf +++ b/test/Cpp/src/Stride.lf @@ -5,8 +5,8 @@ target Cpp { fast: true } -reactor Count(stride: int(1)) { - state count: int(0) +reactor Count(stride: int = 1) { + state count: int = 0 output y: int timer t(0, 100 msec) diff --git a/test/Cpp/src/StructPrint.lf b/test/Cpp/src/StructPrint.lf index 9e0bc35d45..4ee404b8bf 100644 --- a/test/Cpp/src/StructPrint.lf +++ b/test/Cpp/src/StructPrint.lf @@ -19,8 +19,8 @@ reactor Source { } reactor Print( - expected_value: int(42), - expected_name: {= std::string =}("Earth") + expected_value: int = 42, + expected_name: {= std::string =} = "Earth" ) { input in: Hello diff --git a/test/Cpp/src/StructScale.lf b/test/Cpp/src/StructScale.lf index cf818f710f..776dad14b1 100644 --- a/test/Cpp/src/StructScale.lf +++ b/test/Cpp/src/StructScale.lf @@ -9,7 +9,7 @@ public preamble {= #include "include/hello.h" =} -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { input in: Hello output out: Hello diff --git a/test/Cpp/src/TimeLimit.lf b/test/Cpp/src/TimeLimit.lf index ec8d43af42..8b33dcd967 100644 --- a/test/Cpp/src/TimeLimit.lf +++ b/test/Cpp/src/TimeLimit.lf @@ -5,10 +5,10 @@ target Cpp { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= count++; @@ -19,7 +19,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int - state s: int(1) + state s: int = 1 reaction(x) {= //std::cout << "Received " << *x.get() << '\n'; @@ -39,7 +39,7 @@ reactor Destination { =} } -main reactor TimeLimit(period: time(1 sec)) { +main reactor TimeLimit(period: time = 1 sec) { timer stop(10 sec) c = new Clock(period = period) d = new Destination() diff --git a/test/Cpp/src/TimeState.lf b/test/Cpp/src/TimeState.lf index 28228c11ec..aefac79b1c 100644 --- a/test/Cpp/src/TimeState.lf +++ b/test/Cpp/src/TimeState.lf @@ -1,7 +1,7 @@ target Cpp -reactor Foo(bar: time(42 msec)) { - state baz(bar) +reactor Foo(bar: time = 42 msec) { + state baz = bar reaction(startup) {= std::cout << "Baz: " << baz << std::endl; =} } diff --git a/test/Cpp/src/Timeout_Test.lf b/test/Cpp/src/Timeout_Test.lf index 53ff8480dd..c3fe1ffbf9 100644 --- a/test/Cpp/src/Timeout_Test.lf +++ b/test/Cpp/src/Timeout_Test.lf @@ -13,7 +13,7 @@ import Sender from "lib/LoopedActionSender.lf" reactor Consumer { input in: int - state success: bool(false) + state success: bool = false reaction(in) {= auto current{get_elapsed_logical_time()}; diff --git a/test/Cpp/src/ToReactionNested.lf b/test/Cpp/src/ToReactionNested.lf index ff391229a0..5b048bc0a7 100644 --- a/test/Cpp/src/ToReactionNested.lf +++ b/test/Cpp/src/ToReactionNested.lf @@ -12,8 +12,8 @@ reactor CountContainer { } main reactor { - state count: int(1) - state received: bool(false) + state count: int = 1 + state received: bool = false s = new CountContainer() diff --git a/test/Cpp/src/TriggerDownstreamOnlyIfPresent2.lf b/test/Cpp/src/TriggerDownstreamOnlyIfPresent2.lf index d46507fce4..b8c304b164 100644 --- a/test/Cpp/src/TriggerDownstreamOnlyIfPresent2.lf +++ b/test/Cpp/src/TriggerDownstreamOnlyIfPresent2.lf @@ -13,7 +13,7 @@ target Cpp { reactor Source { output[2] out: int - state count: int(0) + state count: int = 0 timer t(0, 200 msec) reaction(t) -> out {= diff --git a/test/Cpp/src/concurrent/AsyncCallback.lf b/test/Cpp/src/concurrent/AsyncCallback.lf index 984132f133..1258f69ce5 100644 --- a/test/Cpp/src/concurrent/AsyncCallback.lf +++ b/test/Cpp/src/concurrent/AsyncCallback.lf @@ -11,11 +11,11 @@ main reactor AsyncCallback { timer t(0, 200 msec) state thread: {= std::thread =} - state expected_time: time(100 msec) - state toggle: bool(false) + state expected_time: time = 100 msec + state toggle: bool = false physical action a: int - state i: int(0) + state i: int = 0 reaction(t) -> a {= // make sure to join the old thread first diff --git a/test/Cpp/src/concurrent/AsyncCallback2.lf b/test/Cpp/src/concurrent/AsyncCallback2.lf index 322413617b..c89b8ab712 100644 --- a/test/Cpp/src/concurrent/AsyncCallback2.lf +++ b/test/Cpp/src/concurrent/AsyncCallback2.lf @@ -10,10 +10,10 @@ main reactor AsyncCallback2 { =} timer t(0, 200 msec) - state expected_time: time(0) + state expected_time: time = 0 logical action a: int - state i: int(0) + state i: int = 0 reaction(t) -> a {= // start new thread diff --git a/test/Cpp/src/concurrent/CompositionThreaded.lf b/test/Cpp/src/concurrent/CompositionThreaded.lf index 393b08e060..42817e8b1d 100644 --- a/test/Cpp/src/concurrent/CompositionThreaded.lf +++ b/test/Cpp/src/concurrent/CompositionThreaded.lf @@ -5,10 +5,10 @@ target Cpp { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: int timer t(1 sec, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= count++; @@ -18,7 +18,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= count++; diff --git a/test/Cpp/src/concurrent/DeadlineHandledAboveThreaded.lf b/test/Cpp/src/concurrent/DeadlineHandledAboveThreaded.lf index eb88c997eb..bb380fe14c 100644 --- a/test/Cpp/src/concurrent/DeadlineHandledAboveThreaded.lf +++ b/test/Cpp/src/concurrent/DeadlineHandledAboveThreaded.lf @@ -2,7 +2,7 @@ // container reacts to that output. target Cpp -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { input x: int output deadline_violation: bool @@ -16,7 +16,7 @@ reactor Deadline(threshold: time(100 msec)) { } main reactor { - state violation_detected: bool({= false =}) + state violation_detected: bool = {= false =} d = new Deadline(threshold = 10 msec) reaction(startup) -> d.x {= diff --git a/test/Cpp/src/concurrent/DeadlineThreaded.lf b/test/Cpp/src/concurrent/DeadlineThreaded.lf index ad87c5a052..cb794f5050 100644 --- a/test/Cpp/src/concurrent/DeadlineThreaded.lf +++ b/test/Cpp/src/concurrent/DeadlineThreaded.lf @@ -5,13 +5,13 @@ target Cpp { timeout: 4 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { private preamble {= #include =} output y: int timer t(0, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= if (count % 2 == 1) { @@ -25,9 +25,9 @@ reactor Source(period: time(2 sec)) { =} } -reactor Destination(timeout: time(1 sec)) { +reactor Destination(timeout: time = 1 sec) { input x: int - state count: int(0) + state count: int = 0 reaction(x) {= std::cout << "Destination receives: " << *x.get() << std::endl; diff --git a/test/Cpp/src/concurrent/DelayIntThreaded.lf b/test/Cpp/src/concurrent/DelayIntThreaded.lf index d935ae12e5..319cae8b76 100644 --- a/test/Cpp/src/concurrent/DelayIntThreaded.lf +++ b/test/Cpp/src/concurrent/DelayIntThreaded.lf @@ -1,7 +1,7 @@ // This tests actions with payloads by delaying an input by a fixed amount. target Cpp -reactor Delay(delay: time(100 msec)) { +reactor Delay(delay: time = 100 msec) { input in: int output out: int logical action d: int diff --git a/test/Cpp/src/concurrent/DoubleReactionThreaded.lf b/test/Cpp/src/concurrent/DoubleReactionThreaded.lf index 1c9c3041b6..d68f2e8b60 100644 --- a/test/Cpp/src/concurrent/DoubleReactionThreaded.lf +++ b/test/Cpp/src/concurrent/DoubleReactionThreaded.lf @@ -5,10 +5,10 @@ target Cpp { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= count++; @@ -19,7 +19,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int input w: int - state s: int(2) + state s: int = 2 reaction(x, w) {= int sum = 0; diff --git a/test/Cpp/src/concurrent/GainThreaded.lf b/test/Cpp/src/concurrent/GainThreaded.lf index 8e96f44ed1..e88801f5a1 100644 --- a/test/Cpp/src/concurrent/GainThreaded.lf +++ b/test/Cpp/src/concurrent/GainThreaded.lf @@ -1,7 +1,7 @@ // Example in the Wiki. target Cpp -reactor Scale(scale: int(2)) { +reactor Scale(scale: int = 2) { input x: int output y: int diff --git a/test/Cpp/src/concurrent/HelloThreaded.lf b/test/Cpp/src/concurrent/HelloThreaded.lf index 7515d76d38..2c849b94dc 100644 --- a/test/Cpp/src/concurrent/HelloThreaded.lf +++ b/test/Cpp/src/concurrent/HelloThreaded.lf @@ -8,8 +8,11 @@ target Cpp { fast: true } -reactor HelloCpp(period: time(2 sec), message: {= std::string =}("Hello C++")) { - state count: int(0) +reactor HelloCpp( + period: time = 2 sec, + message: {= std::string =} = "Hello C++" +) { + state count: int = 0 state previous_time: {= reactor::TimePoint =} timer t(1 sec, period) logical action a: void @@ -37,8 +40,8 @@ reactor HelloCpp(period: time(2 sec), message: {= std::string =}("Hello C++")) { } reactor Inside( - period: time(1 sec), - message: {= std::string =}("Composite default message.") + period: time = 1 sec, + message: {= std::string =} = "Composite default message." ) { third_instance = new HelloCpp(period = period, message = message) } diff --git a/test/Cpp/src/concurrent/SendingInsideThreaded.lf b/test/Cpp/src/concurrent/SendingInsideThreaded.lf index a0e0fcc250..51ca511e26 100644 --- a/test/Cpp/src/concurrent/SendingInsideThreaded.lf +++ b/test/Cpp/src/concurrent/SendingInsideThreaded.lf @@ -7,7 +7,7 @@ target Cpp { reactor Printer { input x: int - state count: int(1) + state count: int = 1 reaction(x) {= std::cout << "Inside reactor received: " << *x.get() << std::endl; @@ -20,7 +20,7 @@ reactor Printer { } main reactor { - state count: int(0) + state count: int = 0 timer t(0, 1 sec) p = new Printer() diff --git a/test/Cpp/src/concurrent/Threaded.lf b/test/Cpp/src/concurrent/Threaded.lf index 0a0d857030..9ad4fcbe56 100644 --- a/test/Cpp/src/concurrent/Threaded.lf +++ b/test/Cpp/src/concurrent/Threaded.lf @@ -13,7 +13,7 @@ target Cpp { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= out.set(s); @@ -35,7 +35,7 @@ reactor Computation { } reactor Destination { - state s: int(0) + state s: int = 0 input in1: int input in2: int input in3: int diff --git a/test/Cpp/src/concurrent/ThreadedThreaded.lf b/test/Cpp/src/concurrent/ThreadedThreaded.lf index 2a6680273b..f8b21a6121 100644 --- a/test/Cpp/src/concurrent/ThreadedThreaded.lf +++ b/test/Cpp/src/concurrent/ThreadedThreaded.lf @@ -13,7 +13,7 @@ target Cpp { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= out.set(s); @@ -35,7 +35,7 @@ reactor Computation { } reactor Destination { - state s: int(0) + state s: int = 0 input[4] in: int reaction(in) {= diff --git a/test/Cpp/src/concurrent/TimeLimitThreaded.lf b/test/Cpp/src/concurrent/TimeLimitThreaded.lf index 5afcf05299..47371f72d7 100644 --- a/test/Cpp/src/concurrent/TimeLimitThreaded.lf +++ b/test/Cpp/src/concurrent/TimeLimitThreaded.lf @@ -6,10 +6,10 @@ target Cpp { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: int timer t(offset, period) - state count: int(0) + state count: int = 0 reaction(t) -> y {= count++; @@ -20,7 +20,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: int - state s: int(1) + state s: int = 1 reaction(x) {= //std::cout << "Received " << *x.get() << '\n'; @@ -40,7 +40,7 @@ reactor Destination { =} } -main reactor(period: time(1 sec)) { +main reactor(period: time = 1 sec) { timer stop(10 sec) c = new Clock(period = period) d = new Destination() diff --git a/test/Cpp/src/enclave/EnclaveBank.lf b/test/Cpp/src/enclave/EnclaveBank.lf index b155bd9f57..8894526927 100644 --- a/test/Cpp/src/enclave/EnclaveBank.lf +++ b/test/Cpp/src/enclave/EnclaveBank.lf @@ -4,10 +4,10 @@ target Cpp { } reactor Node( - bank_index: size_t(0), - id: std::string({= "node" + std::to_string(bank_index) =}), - period: time(500 msec), - duration: time(10 msec) + bank_index: size_t = 0, + id: std::string = {= "node" + std::to_string(bank_index) =}, + period: time = 500 msec, + duration: time = 10 msec ) { logical action a: void diff --git a/test/Cpp/src/enclave/EnclaveBankEach.lf b/test/Cpp/src/enclave/EnclaveBankEach.lf index c632d2ae79..c6cdeac564 100644 --- a/test/Cpp/src/enclave/EnclaveBankEach.lf +++ b/test/Cpp/src/enclave/EnclaveBankEach.lf @@ -5,10 +5,10 @@ target Cpp { } reactor Node( - bank_index: size_t(0), - id: std::string({= "node" + std::to_string(bank_index) =}), - period: {= reactor::Duration =}({= 100ms * (bank_index+1) =}), - duration: {= reactor::Duration =}({= 50ms + 100ms * bank_index =}) + bank_index: size_t = 0, + id: std::string = {= "node" + std::to_string(bank_index) =}, + period: {= reactor::Duration =} = {= 100ms * (bank_index+1) =}, + duration: {= reactor::Duration =} = {= 50ms + 100ms * bank_index =} ) { logical action a: void diff --git a/test/Cpp/src/enclave/EnclaveHelloWorld.lf b/test/Cpp/src/enclave/EnclaveHelloWorld.lf index ab28317cf9..7ca051533b 100644 --- a/test/Cpp/src/enclave/EnclaveHelloWorld.lf +++ b/test/Cpp/src/enclave/EnclaveHelloWorld.lf @@ -1,10 +1,10 @@ target Cpp -reactor Hello(msg: std::string("World")) { +reactor Hello(msg: std::string = "World") { reaction(startup) {= reactor::log::Info() << "Hello " << msg << '!'; =} } -main reactor(msg1: std::string("World"), msg2: std::string("Enclave")) { +main reactor(msg1: std::string = "World", msg2: std::string = "Enclave") { hello1 = new Hello(msg = msg1) @enclave diff --git a/test/Cpp/src/enclave/EnclaveHierarchy.lf b/test/Cpp/src/enclave/EnclaveHierarchy.lf index 87c811f859..9063942575 100644 --- a/test/Cpp/src/enclave/EnclaveHierarchy.lf +++ b/test/Cpp/src/enclave/EnclaveHierarchy.lf @@ -4,9 +4,9 @@ target Cpp { } reactor Node( - id: std::string("node"), - period: time(100 msec), - duration: time(50 msec) + id: std::string = "node", + period: time = 100 msec, + duration: time = 50 msec ) { timer t(0, period) diff --git a/test/Cpp/src/enclave/EnclaveShutdown.lf b/test/Cpp/src/enclave/EnclaveShutdown.lf index 7d25a53643..831adcde9c 100644 --- a/test/Cpp/src/enclave/EnclaveShutdown.lf +++ b/test/Cpp/src/enclave/EnclaveShutdown.lf @@ -1,9 +1,9 @@ target Cpp reactor Node( - message: std::string("Hello"), - period: time(1 sec), - stop: time(3 sec) + message: std::string = "Hello", + period: time = 1 sec, + stop: time = 3 sec ) { timer t(0, period) timer s(stop) diff --git a/test/Cpp/src/enclave/EnclaveTimeout.lf b/test/Cpp/src/enclave/EnclaveTimeout.lf index c9dd51498f..dc87acbd26 100644 --- a/test/Cpp/src/enclave/EnclaveTimeout.lf +++ b/test/Cpp/src/enclave/EnclaveTimeout.lf @@ -2,7 +2,7 @@ target Cpp { timeout: 1 sec } -reactor Node(message: std::string("Hello"), period: time(1 sec)) { +reactor Node(message: std::string = "Hello", period: time = 1 sec) { timer t(0, period) reaction(t) {= reactor::log::Info() << message; =} diff --git a/test/Cpp/src/lib/Count.lf b/test/Cpp/src/lib/Count.lf index aaf689b720..7b56233019 100644 --- a/test/Cpp/src/lib/Count.lf +++ b/test/Cpp/src/lib/Count.lf @@ -3,7 +3,7 @@ target Cpp reactor Count { output c: int timer t(0, 1 sec) - state i: int(0) + state i: int = 0 reaction(t) -> c {= i++; diff --git a/test/Cpp/src/lib/LoopedActionSender.lf b/test/Cpp/src/lib/LoopedActionSender.lf index db35b7a606..3c1cc13631 100644 --- a/test/Cpp/src/lib/LoopedActionSender.lf +++ b/test/Cpp/src/lib/LoopedActionSender.lf @@ -13,10 +13,10 @@ target Cpp * @param break_interval: Determines how long the reactor should take a break * after sending take_a_break_after messages. */ -reactor Sender(take_a_break_after: int(10), break_interval: time(400 msec)) { +reactor Sender(take_a_break_after: int = 10, break_interval: time = 400 msec) { output out: int logical action act - state sent_messages: int(0) + state sent_messages: int = 0 reaction(startup, act) -> act, out {= out.set(sent_messages); diff --git a/test/Cpp/src/multiport/BankSelfBroadcast.lf b/test/Cpp/src/multiport/BankSelfBroadcast.lf index a32e3a8eac..07da948506 100644 --- a/test/Cpp/src/multiport/BankSelfBroadcast.lf +++ b/test/Cpp/src/multiport/BankSelfBroadcast.lf @@ -8,10 +8,10 @@ */ target Cpp -reactor A(bank_index: size_t(0)) { +reactor A(bank_index: size_t = 0) { input[4] in: size_t output out: size_t - state received: bool(false) + state received: bool = false reaction(startup) -> out {= out.set(bank_index); =} diff --git a/test/Cpp/src/multiport/BankToBank.lf b/test/Cpp/src/multiport/BankToBank.lf index 84fc29ea09..f70cfd83e4 100644 --- a/test/Cpp/src/multiport/BankToBank.lf +++ b/test/Cpp/src/multiport/BankToBank.lf @@ -4,10 +4,10 @@ target Cpp { fast: true } -reactor Source(bank_index: size_t(0)) { +reactor Source(bank_index: size_t = 0) { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= out.set(s); @@ -15,8 +15,8 @@ reactor Source(bank_index: size_t(0)) { =} } -reactor Destination(bank_index: size_t(0)) { - state s: int(0) +reactor Destination(bank_index: size_t = 0) { + state s: int = 0 input in: int reaction(in) {= @@ -37,7 +37,7 @@ reactor Destination(bank_index: size_t(0)) { =} } -main reactor BankToBank(width: int(4)) { +main reactor BankToBank(width: int = 4) { // FIXME: Should set the width to "width" rather than "4". a = new[4] Source() b = new[4] Destination() diff --git a/test/Cpp/src/multiport/BankToBankMultiport.lf b/test/Cpp/src/multiport/BankToBankMultiport.lf index e5c26a4d2b..bdb3e9a880 100644 --- a/test/Cpp/src/multiport/BankToBankMultiport.lf +++ b/test/Cpp/src/multiport/BankToBankMultiport.lf @@ -4,10 +4,10 @@ target Cpp { fast: true } -reactor Source(width: size_t(1)) { +reactor Source(width: size_t = 1) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(size_t i = 0; i < out.size(); i++) { @@ -16,8 +16,8 @@ reactor Source(width: size_t(1)) { =} } -reactor Destination(width: size_t(1)) { - state s: int(6) +reactor Destination(width: size_t = 1) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -44,7 +44,7 @@ reactor Destination(width: size_t(1)) { =} } -main reactor(bank_width: size_t(4)) { +main reactor(bank_width: size_t = 4) { a = new[bank_width] Source(width = 4) b = new[bank_width] Destination(width = 4) a.out -> b.in diff --git a/test/Cpp/src/multiport/BankToBankMultiportAfter.lf b/test/Cpp/src/multiport/BankToBankMultiportAfter.lf index 436a647f94..78d832a5ea 100644 --- a/test/Cpp/src/multiport/BankToBankMultiportAfter.lf +++ b/test/Cpp/src/multiport/BankToBankMultiportAfter.lf @@ -4,10 +4,10 @@ target Cpp { fast: true } -reactor Source(width: size_t(1)) { +reactor Source(width: size_t = 1) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(size_t i = 0; i < out.size(); i++) { @@ -16,9 +16,9 @@ reactor Source(width: size_t(1)) { =} } -reactor Destination(width: size_t(1)) { - state s: int(6) - state iterations: unsigned(0) +reactor Destination(width: size_t = 1) { + state s: int = 6 + state iterations: unsigned = 0 input[width] in: int reaction(in) {= @@ -52,7 +52,7 @@ reactor Destination(width: size_t(1)) { =} } -main reactor(bank_width: size_t(4)) { +main reactor(bank_width: size_t = 4) { a = new[bank_width] Source(width = 4) b = new[bank_width] Destination(width = 4) a.out -> b.in after 200 msec diff --git a/test/Cpp/src/multiport/BankToMultiport.lf b/test/Cpp/src/multiport/BankToMultiport.lf index d8132b2630..6b1acf7591 100644 --- a/test/Cpp/src/multiport/BankToMultiport.lf +++ b/test/Cpp/src/multiport/BankToMultiport.lf @@ -1,7 +1,7 @@ // Test bank of reactors to multiport input with id parameter in the bank. target Cpp -reactor Source(bank_index: size_t(0)) { +reactor Source(bank_index: size_t = 0) { output out: unsigned reaction(startup) -> out {= out.set(bank_index); =} @@ -9,7 +9,7 @@ reactor Source(bank_index: size_t(0)) { reactor Sink { input[4] in: unsigned - state received: bool(false) + state received: bool = false reaction(in) {= for (unsigned i = 0; i < in.size(); i++) { diff --git a/test/Cpp/src/multiport/Broadcast.lf b/test/Cpp/src/multiport/Broadcast.lf index 471bc92172..077ca75247 100644 --- a/test/Cpp/src/multiport/Broadcast.lf +++ b/test/Cpp/src/multiport/Broadcast.lf @@ -6,7 +6,7 @@ reactor Source { reaction(startup) -> out {= out.set(42); =} } -reactor Sink(bank_index: size_t(0)) { +reactor Sink(bank_index: size_t = 0) { input in: unsigned reaction(in) {= diff --git a/test/Cpp/src/multiport/BroadcastAfter.lf b/test/Cpp/src/multiport/BroadcastAfter.lf index 7f0ee66712..6cb7f84f4d 100644 --- a/test/Cpp/src/multiport/BroadcastAfter.lf +++ b/test/Cpp/src/multiport/BroadcastAfter.lf @@ -8,9 +8,9 @@ reactor Source { reaction(startup) -> out {= out.set(42); =} } -reactor Sink(bank_index: size_t(0)) { +reactor Sink(bank_index: size_t = 0) { input in: unsigned - state received: bool{false} + state received: bool = false reaction(in) {= std::cout << bank_index << " received " << *in.get() << '\n'; diff --git a/test/Cpp/src/multiport/BroadcastMultipleAfter.lf b/test/Cpp/src/multiport/BroadcastMultipleAfter.lf index 84051887ca..065e9bf403 100644 --- a/test/Cpp/src/multiport/BroadcastMultipleAfter.lf +++ b/test/Cpp/src/multiport/BroadcastMultipleAfter.lf @@ -2,15 +2,15 @@ target Cpp { fast: true } -reactor Source(value: unsigned(42)) { +reactor Source(value: unsigned = 42) { output out: unsigned reaction(startup) -> out {= out.set(value); =} } -reactor Sink(bank_index: size_t(0)) { +reactor Sink(bank_index: size_t = 0) { input in: unsigned - state received: bool{false} + state received: bool = false reaction(in) {= std::cout << bank_index << " received " << *in.get() << '\n'; diff --git a/test/Cpp/src/multiport/FullyConnected.lf b/test/Cpp/src/multiport/FullyConnected.lf index c925460402..0b0a66405e 100644 --- a/test/Cpp/src/multiport/FullyConnected.lf +++ b/test/Cpp/src/multiport/FullyConnected.lf @@ -1,10 +1,10 @@ target Cpp -reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { +reactor Node(bank_index: size_t = 0, num_nodes: size_t = 4) { input[num_nodes] in: size_t output out: size_t - state received: bool{false} + state received: bool = false reaction(startup) -> out {= std::cout << "Hello from node " << bank_index << "!\n"; @@ -36,7 +36,7 @@ reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { =} } -main reactor(num_nodes: size_t(4)) { +main reactor(num_nodes: size_t = 4) { nodes = new[num_nodes] Node(num_nodes = num_nodes) (nodes.out)+ -> nodes.in } diff --git a/test/Cpp/src/multiport/FullyConnectedAddressable.lf b/test/Cpp/src/multiport/FullyConnectedAddressable.lf index 2a756c328f..5385c44222 100644 --- a/test/Cpp/src/multiport/FullyConnectedAddressable.lf +++ b/test/Cpp/src/multiport/FullyConnectedAddressable.lf @@ -1,11 +1,11 @@ // In this pattern, each node can send direct messages to individual other nodes target Cpp -reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { +reactor Node(bank_index: size_t = 0, num_nodes: size_t = 4) { input[num_nodes] in: size_t output[num_nodes] out: size_t - state received: bool{false} + state received: bool = false reaction(startup) -> out {= std::cout << "Hello from node " << bank_index << "!\n"; @@ -41,7 +41,7 @@ reactor Node(bank_index: size_t(0), num_nodes: size_t(4)) { =} } -main reactor(num_nodes: size_t(4)) { +main reactor(num_nodes: size_t = 4) { nodes1 = new[num_nodes] Node(num_nodes = num_nodes) nodes1.out -> interleaved (nodes1.in) diff --git a/test/Cpp/src/multiport/FullyConnectedAddressableAfter.lf b/test/Cpp/src/multiport/FullyConnectedAddressableAfter.lf index e331494b1b..c6fcb4c9e6 100644 --- a/test/Cpp/src/multiport/FullyConnectedAddressableAfter.lf +++ b/test/Cpp/src/multiport/FullyConnectedAddressableAfter.lf @@ -3,7 +3,7 @@ target Cpp import Node from "FullyConnectedAddressable.lf" -main reactor(num_nodes: size_t(4)) { +main reactor(num_nodes: size_t = 4) { nodes1 = new[num_nodes] Node(num_nodes = num_nodes) nodes1.out -> interleaved (nodes1.in) after 200 msec diff --git a/test/Cpp/src/multiport/IndexIntoMultiportOutput.lf b/test/Cpp/src/multiport/IndexIntoMultiportOutput.lf index bf00c15081..964e2e7095 100644 --- a/test/Cpp/src/multiport/IndexIntoMultiportOutput.lf +++ b/test/Cpp/src/multiport/IndexIntoMultiportOutput.lf @@ -8,7 +8,7 @@ */ target Cpp -reactor ReactorWithMultiport(width: size_t(3)) { +reactor ReactorWithMultiport(width: size_t = 3) { output[width] out: int reaction(startup) -> out {= @@ -18,7 +18,7 @@ reactor ReactorWithMultiport(width: size_t(3)) { =} } -reactor MultiportSplitter(width: size_t(3)) { +reactor MultiportSplitter(width: size_t = 3) { input[width] in: int output out0: int @@ -34,9 +34,9 @@ main reactor IndexIntoMultiportOutput { source.out -> splitter.in - state received0: bool{false} - state received1: bool{false} - state received2: bool{false} + state received0: bool = false + state received1: bool = false + state received2: bool = false reaction(splitter.out0) {= received0 = true; diff --git a/test/Cpp/src/multiport/Multiport.lf b/test/Cpp/src/multiport/Multiport.lf index 946282566f..a63924c67c 100644 --- a/test/Cpp/src/multiport/Multiport.lf +++ b/test/Cpp/src/multiport/Multiport.lf @@ -21,7 +21,7 @@ reactor Test { main reactor Multiport { test = new Test() - state received: bool(false) + state received: bool = false reaction(startup) -> test.sink {= for (auto i = 0; i < 30; i++) { diff --git a/test/Cpp/src/multiport/MultiportFromBank.lf b/test/Cpp/src/multiport/MultiportFromBank.lf index 61d33e2a4c..b7339a9184 100644 --- a/test/Cpp/src/multiport/MultiportFromBank.lf +++ b/test/Cpp/src/multiport/MultiportFromBank.lf @@ -5,15 +5,15 @@ target Cpp { fast: true } -reactor Source(bank_index: size_t(0)) { +reactor Source(bank_index: size_t = 0) { output out: unsigned reaction(startup) -> out {= out.set(bank_index); =} } -reactor Destination(port_width: size_t(2)) { +reactor Destination(port_width: size_t = 2) { input[port_width] in: unsigned - state received: bool(false) + state received: bool = false reaction(in) {= for (size_t i = 0; i < in.size(); i++) { @@ -35,7 +35,7 @@ reactor Destination(port_width: size_t(2)) { =} } -main reactor(width: size_t(4)) { +main reactor(width: size_t = 4) { a = new[width] Source() b = new Destination(port_width = width) a.out -> b.in diff --git a/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf b/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf index ec75a59be4..673ef6889a 100644 --- a/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf +++ b/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf @@ -5,7 +5,7 @@ target Cpp { fast: true } -reactor Source(bank_index: size_t(0)) { +reactor Source(bank_index: size_t = 0) { output out: unsigned reaction(startup) -> out {= out.set(bank_index); =} @@ -19,7 +19,7 @@ reactor Container { reactor Destination { input[3] in: unsigned - state received: bool(false) + state received: bool = false reaction(in) {= for (size_t i = 0; i < in.size(); i++) { diff --git a/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf b/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf index fd0cb4c693..fe27c74f35 100644 --- a/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf +++ b/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf @@ -5,7 +5,7 @@ target Cpp { fast: true } -reactor Source(bank_index: size_t(0)) { +reactor Source(bank_index: size_t = 0) { output out: int reaction(startup) -> out {= out.set(bank_index); =} @@ -19,7 +19,7 @@ reactor Container { reactor Destination { input[3] in: int - state received: bool(false) + state received: bool = false reaction(in) {= for (int i = 0; i < in.size(); i++) { diff --git a/test/Cpp/src/multiport/MultiportFromHierarchy.lf b/test/Cpp/src/multiport/MultiportFromHierarchy.lf index c63285af13..fae914b609 100644 --- a/test/Cpp/src/multiport/MultiportFromHierarchy.lf +++ b/test/Cpp/src/multiport/MultiportFromHierarchy.lf @@ -8,7 +8,7 @@ target Cpp { reactor Source { timer t(0, 200 msec) output[4] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < 4; i++) { @@ -18,7 +18,7 @@ reactor Source { } reactor Destination { - state s: int(6) + state s: int = 6 input[4] in: int reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportIn.lf b/test/Cpp/src/multiport/MultiportIn.lf index 31f06bd956..39fce6f8ff 100644 --- a/test/Cpp/src/multiport/MultiportIn.lf +++ b/test/Cpp/src/multiport/MultiportIn.lf @@ -8,7 +8,7 @@ target Cpp { reactor Source { timer t(0, 200 msec) output out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= out.set(s++); =} } @@ -21,7 +21,7 @@ reactor Computation { } reactor Destination { - state s: int(0) + state s: int = 0 input[4] in: int reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportOut.lf b/test/Cpp/src/multiport/MultiportOut.lf index b4d5e00425..53f2a5bb23 100644 --- a/test/Cpp/src/multiport/MultiportOut.lf +++ b/test/Cpp/src/multiport/MultiportOut.lf @@ -7,7 +7,7 @@ target Cpp { reactor Source { timer t(0, 200 msec) output[4] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < 4; i++) { @@ -31,7 +31,7 @@ reactor Computation { } reactor Destination { - state s: int(0) + state s: int = 0 input[4] in: int reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportToBank.lf b/test/Cpp/src/multiport/MultiportToBank.lf index 898ccf7b97..fa2f79419f 100644 --- a/test/Cpp/src/multiport/MultiportToBank.lf +++ b/test/Cpp/src/multiport/MultiportToBank.lf @@ -10,7 +10,7 @@ reactor Source { =} } -reactor Sink(bank_index: size_t(0)) { +reactor Sink(bank_index: size_t = 0) { input in: unsigned reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportToBankAfter.lf b/test/Cpp/src/multiport/MultiportToBankAfter.lf index 546e569e24..c05730680f 100644 --- a/test/Cpp/src/multiport/MultiportToBankAfter.lf +++ b/test/Cpp/src/multiport/MultiportToBankAfter.lf @@ -10,7 +10,7 @@ reactor Source { =} } -reactor Sink(bank_index: size_t(0)) { +reactor Sink(bank_index: size_t = 0) { input in: unsigned reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportToBankHierarchy.lf b/test/Cpp/src/multiport/MultiportToBankHierarchy.lf index 11671f98bb..f9cef6a676 100644 --- a/test/Cpp/src/multiport/MultiportToBankHierarchy.lf +++ b/test/Cpp/src/multiport/MultiportToBankHierarchy.lf @@ -15,9 +15,9 @@ reactor Source { =} } -reactor Destination(bank_index: size_t(0)) { +reactor Destination(bank_index: size_t = 0) { input in: unsigned - state received: bool(false) + state received: bool = false reaction(in) {= std::cout << "Destination " << bank_index << " received " << *in.get() << ".\n"; diff --git a/test/Cpp/src/multiport/MultiportToHierarchy.lf b/test/Cpp/src/multiport/MultiportToHierarchy.lf index e0daa5a4c0..573a0e06da 100644 --- a/test/Cpp/src/multiport/MultiportToHierarchy.lf +++ b/test/Cpp/src/multiport/MultiportToHierarchy.lf @@ -6,10 +6,10 @@ target Cpp { fast: true } -reactor Source(width: size_t(4)) { +reactor Source(width: size_t = 4) { timer t(0, 200 msec) output[width] out: int - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(size_t i = 0; i < 4; i++) { @@ -18,8 +18,8 @@ reactor Source(width: size_t(4)) { =} } -reactor Destination(width: size_t(4)) { - state s: int(6) +reactor Destination(width: size_t = 4) { + state s: int = 6 input[width] in: int reaction(in) {= @@ -45,13 +45,13 @@ reactor Destination(width: size_t(4)) { =} } -reactor Container(width: size_t(4)) { +reactor Container(width: size_t = 4) { input[width] in: int dst = new Destination() in -> dst.in } -main reactor MultiportToHierarchy(width: size_t(4)) { +main reactor MultiportToHierarchy(width: size_t = 4) { a = new Source(width = width) b = new Container(width = width) a.out -> b.in diff --git a/test/Cpp/src/multiport/MultiportToMultiport.lf b/test/Cpp/src/multiport/MultiportToMultiport.lf index c7b673ea11..37a1ee5222 100644 --- a/test/Cpp/src/multiport/MultiportToMultiport.lf +++ b/test/Cpp/src/multiport/MultiportToMultiport.lf @@ -12,7 +12,7 @@ reactor Source { reactor Sink { input[4] in: unsigned - state received: bool(false) + state received: bool = false reaction(in) {= for (unsigned i = 0; i < in.size(); i++) { diff --git a/test/Cpp/src/multiport/MultiportToMultiport2.lf b/test/Cpp/src/multiport/MultiportToMultiport2.lf index ac0151731f..165f3201ce 100644 --- a/test/Cpp/src/multiport/MultiportToMultiport2.lf +++ b/test/Cpp/src/multiport/MultiportToMultiport2.lf @@ -1,7 +1,7 @@ // Test multiport to multiport connections. See also MultiportToMultiport. target Cpp -reactor Source(width: size_t(2)) { +reactor Source(width: size_t = 2) { output[width] out: size_t reaction(startup) -> out {= @@ -11,7 +11,7 @@ reactor Source(width: size_t(2)) { =} } -reactor Destination(width: size_t(2)) { +reactor Destination(width: size_t = 2) { input[width] in: size_t reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportToMultiport2After.lf b/test/Cpp/src/multiport/MultiportToMultiport2After.lf index b015bfe827..781275b2f4 100644 --- a/test/Cpp/src/multiport/MultiportToMultiport2After.lf +++ b/test/Cpp/src/multiport/MultiportToMultiport2After.lf @@ -1,7 +1,7 @@ // Test multiport to multiport connections. See also MultiportToMultiport. target Cpp -reactor Source(width: size_t(2)) { +reactor Source(width: size_t = 2) { output[width] out: size_t reaction(startup) -> out {= @@ -11,7 +11,7 @@ reactor Source(width: size_t(2)) { =} } -reactor Destination(width: size_t(2)) { +reactor Destination(width: size_t = 2) { input[width] in: size_t reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportToMultiportArray.lf b/test/Cpp/src/multiport/MultiportToMultiportArray.lf index 077aab0f9d..6c96d17150 100644 --- a/test/Cpp/src/multiport/MultiportToMultiportArray.lf +++ b/test/Cpp/src/multiport/MultiportToMultiportArray.lf @@ -8,7 +8,7 @@ target Cpp { reactor Source { timer t(0, 200 msec) output[2] out: int[3] - state s: int(0) + state s: int = 0 reaction(t) -> out {= for(int i = 0; i < 2; i++) { @@ -25,7 +25,7 @@ reactor Source { } reactor Destination { - state s: int(15) + state s: int = 15 input[2] in: int[3] reaction(in) {= diff --git a/test/Cpp/src/multiport/MultiportToPort.lf b/test/Cpp/src/multiport/MultiportToPort.lf index 107198c9cc..c2b0bd66ef 100644 --- a/test/Cpp/src/multiport/MultiportToPort.lf +++ b/test/Cpp/src/multiport/MultiportToPort.lf @@ -16,9 +16,9 @@ reactor Source { =} } -reactor Destination(expected: int(0)) { +reactor Destination(expected: int = 0) { input in: int - state received: bool(false) + state received: bool = false reaction(in) {= std::cout << "Received: " << *in.get() << ".\n"; diff --git a/test/Cpp/src/multiport/ReadMultiportOutputOfContainedBank.lf b/test/Cpp/src/multiport/ReadMultiportOutputOfContainedBank.lf index c6bbcf1242..6fa6dbb87b 100644 --- a/test/Cpp/src/multiport/ReadMultiportOutputOfContainedBank.lf +++ b/test/Cpp/src/multiport/ReadMultiportOutputOfContainedBank.lf @@ -2,7 +2,7 @@ // multiport target Cpp -reactor Contained(bank_index: size_t(0)) { +reactor Contained(bank_index: size_t = 0) { output[3] out: unsigned reaction(startup) -> out {= @@ -14,7 +14,7 @@ reactor Contained(bank_index: size_t(0)) { main reactor { c = new[3] Contained() - state count: int(0) + state count: int = 0 reaction(startup) c.out {= for (size_t i = 0; i < c.size(); i++) { diff --git a/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf b/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf index 81deaf91f2..18179b9086 100644 --- a/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf +++ b/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf @@ -2,7 +2,7 @@ // permutations. target Cpp -reactor Contained(bank_index: size_t(0)) { +reactor Contained(bank_index: size_t = 0) { output out: unsigned reaction(startup) -> out {= out.set(42 * bank_index); =} @@ -10,7 +10,7 @@ reactor Contained(bank_index: size_t(0)) { main reactor { c = new[4] Contained() - state count: int(0) + state count: int = 0 reaction(startup) c.out {= for (size_t i = 0; i < c.size(); i++) { diff --git a/test/Cpp/src/multiport/WidthGivenByCode.lf b/test/Cpp/src/multiport/WidthGivenByCode.lf index 52c2f84b36..10a2a59489 100644 --- a/test/Cpp/src/multiport/WidthGivenByCode.lf +++ b/test/Cpp/src/multiport/WidthGivenByCode.lf @@ -1,6 +1,6 @@ target Cpp -reactor Foo(a: size_t{8}, b: size_t{2}) { +reactor Foo(a: size_t = 8, b: size_t = 2) { input[{= a*b =}] in: size_t output[{= a/b =}] out: size_t diff --git a/test/Cpp/src/multiport/WriteInputOfContainedBank.lf b/test/Cpp/src/multiport/WriteInputOfContainedBank.lf index 3824664e35..b8f8d50958 100644 --- a/test/Cpp/src/multiport/WriteInputOfContainedBank.lf +++ b/test/Cpp/src/multiport/WriteInputOfContainedBank.lf @@ -1,9 +1,9 @@ // Test writing inputs to a contained reactor bank target Cpp -reactor Contained(bank_index: size_t(0)) { +reactor Contained(bank_index: size_t = 0) { input in: unsigned - state count: int(0) + state count: int = 0 reaction(in) {= unsigned result = *in.get(); diff --git a/test/Cpp/src/multiport/WriteMultiportInputOfContainedBank.lf b/test/Cpp/src/multiport/WriteMultiportInputOfContainedBank.lf index 49e0614842..5ffe567dfb 100644 --- a/test/Cpp/src/multiport/WriteMultiportInputOfContainedBank.lf +++ b/test/Cpp/src/multiport/WriteMultiportInputOfContainedBank.lf @@ -1,9 +1,9 @@ // Test writing multiport inputs to a contained reactor bank target Cpp -reactor Contained(bank_index: size_t(0)) { +reactor Contained(bank_index: size_t = 0) { input[4] in: unsigned - state count: int(0) + state count: int = 0 reaction(in) {= for (size_t i = 0; i < 3; i++) { diff --git a/test/Cpp/src/properties/Fast.lf b/test/Cpp/src/properties/Fast.lf index a12cbfaf01..1d4ba25d2f 100644 --- a/test/Cpp/src/properties/Fast.lf +++ b/test/Cpp/src/properties/Fast.lf @@ -5,7 +5,7 @@ target Cpp { main reactor { logical action a - state triggered: bool{false} + state triggered: bool = false reaction(startup) -> a {= a.schedule(2s); =} diff --git a/test/Cpp/src/properties/Timeout.lf b/test/Cpp/src/properties/Timeout.lf index 50f647349f..57c55ad24c 100644 --- a/test/Cpp/src/properties/Timeout.lf +++ b/test/Cpp/src/properties/Timeout.lf @@ -5,7 +5,7 @@ target Cpp { main reactor { timer t(1 sec, 1 sec) - state triggered: bool{false} + state triggered: bool = false reaction(t) {= triggered = true; diff --git a/test/Cpp/src/properties/TimeoutZero.lf b/test/Cpp/src/properties/TimeoutZero.lf index 96409f692e..9d04fa8048 100644 --- a/test/Cpp/src/properties/TimeoutZero.lf +++ b/test/Cpp/src/properties/TimeoutZero.lf @@ -5,7 +5,7 @@ target Cpp { main reactor { timer t(0, 1 sec) - state triggered: bool{false} + state triggered: bool = false reaction(t) {= triggered = true; diff --git a/test/Cpp/src/target/AfterVoid.lf b/test/Cpp/src/target/AfterVoid.lf index 0b9beadcff..555e024e6b 100644 --- a/test/Cpp/src/target/AfterVoid.lf +++ b/test/Cpp/src/target/AfterVoid.lf @@ -12,8 +12,8 @@ reactor foo { } reactor print { - state expected_time: time(10 msec) - state i: int(0) + state expected_time: time = 10 msec + state i: int = 0 input x: void reaction(x) {= diff --git a/test/Cpp/src/target/BraceAndParenInitialization.lf b/test/Cpp/src/target/BraceAndParenInitialization.lf index 8838187bc2..003b6d509d 100644 --- a/test/Cpp/src/target/BraceAndParenInitialization.lf +++ b/test/Cpp/src/target/BraceAndParenInitialization.lf @@ -26,8 +26,5 @@ reactor Foo( } main reactor { - foo = new Foo( - param_list_3 = {= std::vector(3, 5) =}, - param_list_4 = {= {3, 5} =} - ) + foo = new Foo(param_list_3 = {= std::vector(3, 5) =}, param_list_4 = {3, 5}) } diff --git a/test/Cpp/src/target/CliParserGenericArguments.lf b/test/Cpp/src/target/CliParserGenericArguments.lf index eb75ac2ef6..9a008ebcab 100644 --- a/test/Cpp/src/target/CliParserGenericArguments.lf +++ b/test/Cpp/src/target/CliParserGenericArguments.lf @@ -43,19 +43,19 @@ private preamble {= =} main reactor CliParserGenericArguments( - int_value: int(10), - signed_value: signed(-10), - unsigned_value: unsigned(11), - long_value: long(-100), - unsigned_long_value: {= unsigned_long =}(42), - long_long_value: {= long_long =}(-42), - ull_value: {= uns_long_long =}(42), - bool_value: bool(false), - char_value: char('T'), - double_value: double(4.2), - long_double_value: {= long_double =}(4.2), - float_value: float(10.5), - string_value: string("This is a testvalue"), + int_value: int = 10, + signed_value: signed = -10, + unsigned_value: unsigned = 11, + long_value: long = -100, + unsigned_long_value: {= unsigned_long =} = 42, + long_long_value: {= long_long =} = -42, + ull_value: {= uns_long_long =} = 42, + bool_value: bool = false, + char_value: char = 'T', + double_value: double = 4.2, + long_double_value: {= long_double =} = 4.2, + float_value: float = 10.5, + string_value: string = "This is a testvalue", custom_class_value: {= CustomClass =}("Peter") ) { reaction(startup) {= std::cout << "Hello World!\n"; =} diff --git a/test/Cpp/src/target/CombinedTypeNames.lf b/test/Cpp/src/target/CombinedTypeNames.lf index 28182b7bf7..d55c70a823 100644 --- a/test/Cpp/src/target/CombinedTypeNames.lf +++ b/test/Cpp/src/target/CombinedTypeNames.lf @@ -3,11 +3,11 @@ target Cpp reactor Foo( - bar: {= unsigned int =}(0), - baz: {= const unsigned int* =}({= nullptr =}) + bar: {= unsigned int =} = 0, + baz: {= const unsigned int* =} = {= nullptr =} ) { - state s_bar: {= unsigned int =}(bar) - state s_baz: {= const unsigned int* =}(baz) + state s_bar: {= unsigned int =} = bar + state s_baz: {= const unsigned int* =} = baz reaction(startup) {= if (bar != 42 || s_bar != 42 || *baz != 42 || *s_baz != 42) { @@ -17,6 +17,6 @@ reactor Foo( =} } -main reactor(bar: {= unsigned int =}(42)) { +main reactor(bar: {= unsigned int =} = 42) { foo = new Foo(bar = bar, baz = {= &bar =}) } diff --git a/test/Cpp/src/target/GenericDelay.lf b/test/Cpp/src/target/GenericDelay.lf index 135c04f417..5efa81b011 100644 --- a/test/Cpp/src/target/GenericDelay.lf +++ b/test/Cpp/src/target/GenericDelay.lf @@ -2,7 +2,7 @@ target Cpp import Test from "../DelayInt.lf" -reactor Delay(delay: time(0)) { +reactor Delay(delay: time = 0) { output out: T input in: T logical action a(delay): T diff --git a/test/Cpp/src/target/GenericParameterAndState.lf b/test/Cpp/src/target/GenericParameterAndState.lf index d259bf3b22..6803eb9509 100644 --- a/test/Cpp/src/target/GenericParameterAndState.lf +++ b/test/Cpp/src/target/GenericParameterAndState.lf @@ -1,7 +1,7 @@ target Cpp -reactor Foo(bar: T(0), expected: T(14542135)) { - state baz: T(bar) +reactor Foo(bar: T = 0, expected: T = 14542135) { + state baz: T = bar reaction(startup) {= if (bar != expected) { diff --git a/test/Cpp/src/target/InitializerSyntax.lf b/test/Cpp/src/target/InitializerSyntax.lf new file mode 100644 index 0000000000..54ae268457 --- /dev/null +++ b/test/Cpp/src/target/InitializerSyntax.lf @@ -0,0 +1,89 @@ +target Cpp + +public preamble {= + #include + struct TestType { + int x; + + // constructor #1 + TestType() : x(42) {} + // constructor #2 + TestType(int x) : x(x) {} + // constructor #3 + TestType(std::initializer_list l) : x(l.size()) {} + // constructor #4 + TestType(const TestType& t) : x(t.x + 10) { } + // constructor #5 + TestType(TestType&& t) : x(t.x + 20) { } + + TestType& operator=(const TestType& t) { + std::cout << "assign\n"; + this->x = t.x + 30; + return *this; + } + TestType& operator=(TestType&& t) { + this->x = t.x + 40; + return *this; + } + + ~TestType() = default; + }; +=} + +reactor TestReactor( + /** + * FIXME: should work without an explicit initialization, see + * https://github.com/lf-lang/lingua-franca/issues/623 + */ + // p_default: TestType, constructor #1 + p_default: TestType(), + p_empty: TestType(), // constructor #1 + p_value: TestType(24), // constructor #2 + p_init_empty: TestType{}, // constructor #1 + p_init_empty2: TestType({}), // constructor #1 + p_init_some: TestType{2, 6, 6, 3, 1}, // constructor #1 + p_assign_init_empty: TestType = {}, // constructor #1 + p_assign_init_some: TestType = {4, 2, 1} // constructor #3 +) { + state s_default: TestType // constructor #1 + state s_empty: TestType() // constructor #1 + state s_value: TestType(24) // constructor #2 + state s_init_empty: TestType{} // constructor #1 + state s_init_empty2: TestType({}) // constructor #3 + state s_init_some: TestType{3, 12, 40} // constructor #3 + state s_assign_init_empty: TestType = {} // constructor #3 + state s_assign_init_some: TestType = {4, 3, 2, 1} // constructor #3 + state s_copy1: TestType(p_default) // constructor #4 + state s_copy2: TestType{p_default} // constructor #4 + state s_copy3: TestType = p_default // constructor #4 + + reaction(startup) {= + reactor::validate(p_default.x == 62, "p_default should be default constructed and then moved"); + reactor::validate(p_empty.x == 62, "p_empty should be default constructed and then moved"); + reactor::validate(p_value.x == 44, "p_value should be constructed from 24 and then moved"); + reactor::validate(p_init_empty.x == 62, "p_init_empty should be default constructed and then moved"); + reactor::validate(p_init_empty2.x == 20, "p_init_empty2 should be constructed with 0 and then moved"); + reactor::validate(p_init_some.x == 25, "p_init_some should be constructed with 3 and then moved"); + reactor::validate(p_assign_init_empty.x == 62, "p_assign_init_empty should be default constructed and then moved"); + reactor::validate(p_assign_init_some.x == 23, "p_assign_init_some should be constructed with 4 and then moved"); + + reactor::validate(s_default.x == 42, "s_default should be default constructed"); + reactor::validate(s_empty.x == 42, "s_empty should be default constructed"); + reactor::validate(s_value.x == 24, "s_value should be constructed with 24"); + reactor::validate(s_init_empty.x == 42, "s_init_empty should be default constructed"); + reactor::validate(s_init_empty2.x == 0, "s_init_empty2 should be constructed with 0"); + reactor::validate(s_init_some.x == 3, "s_init_some should be constructed with 3"); + // NOTE: This is a strange corner case. Since the equal assignment will be translated to a () initializers (e.g. :foo(42)), + // the initialization here in LF behaves differently from what one might expect. When writing `Foo foo = {}`, + // the default constructor would be called instead of the initializer list constructor. + reactor::validate(s_assign_init_empty.x == 0, "s_assign_init_empty should be constructed with 0"); + reactor::validate(s_assign_init_some.x == 4, "s_assign_init_some should be constructed with 4"); + reactor::validate(s_copy1.x == 72, "s_copy1 should be copy constructed from p_default"); + reactor::validate(s_copy2.x == 72, "s_copy1 should be copy constructed from p_default"); + reactor::validate(s_copy3.x == 72, "s_copy1 should be copy constructed from p_default"); + =} +} + +main reactor { + test = new TestReactor() +} diff --git a/test/Cpp/src/target/PointerParameters.lf b/test/Cpp/src/target/PointerParameters.lf index cb8675d014..a769f20da3 100644 --- a/test/Cpp/src/target/PointerParameters.lf +++ b/test/Cpp/src/target/PointerParameters.lf @@ -2,7 +2,7 @@ // Compilation without errors is success. target Cpp -reactor Foo(ptr: int*{{= nullptr =}}) { +reactor Foo(ptr: int* = {= nullptr =}) { reaction(startup) {= if (ptr == nullptr || *ptr != 42) { reactor::log::Error() << "received an unexpected value!"; diff --git a/test/Python/src/ActionDelay.lf b/test/Python/src/ActionDelay.lf index 7c325bb2cb..60efa42f57 100644 --- a/test/Python/src/ActionDelay.lf +++ b/test/Python/src/ActionDelay.lf @@ -4,7 +4,7 @@ target Python reactor GeneratedDelay { input y_in output y_out - state y_state(0) + state y_state = 0 logical action act(100 msec) reaction(y_in) -> act {= diff --git a/test/Python/src/ActionIsPresent.lf b/test/Python/src/ActionIsPresent.lf index 259417110e..ceb52d10b8 100644 --- a/test/Python/src/ActionIsPresent.lf +++ b/test/Python/src/ActionIsPresent.lf @@ -1,10 +1,10 @@ # Tests the is_present variable for actions. target Python -main reactor ActionIsPresent(offset(1 nsec), period(500 msec)) { +main reactor ActionIsPresent(offset = 1 nsec, period = 500 msec) { logical action a - state first_time(True) - state success(False) + state first_time = True + state success = False reaction(startup, a) -> a {= # The is_present field should be initially False diff --git a/test/Python/src/After.lf b/test/Python/src/After.lf index 13b1970d5a..0c8b931543 100644 --- a/test/Python/src/After.lf +++ b/test/Python/src/After.lf @@ -13,8 +13,8 @@ reactor foo { } reactor print { - state expected_time(10 msec) - state received(0) + state expected_time = 10 msec + state received = 0 input x reaction(x) {= diff --git a/test/Python/src/AfterCycles.lf b/test/Python/src/AfterCycles.lf index ef1500af27..cd0f0dd98e 100644 --- a/test/Python/src/AfterCycles.lf +++ b/test/Python/src/AfterCycles.lf @@ -16,7 +16,7 @@ reactor Work { } main reactor AfterCycles { - state count(0) + state count = 0 s = new Source() w0 = new Work() w1 = new Work() diff --git a/test/Python/src/AfterOverlapped.lf b/test/Python/src/AfterOverlapped.lf index 2876538189..32657f1cdb 100644 --- a/test/Python/src/AfterOverlapped.lf +++ b/test/Python/src/AfterOverlapped.lf @@ -8,8 +8,8 @@ import Count from "lib/Count.lf" reactor Test { input c - state i(0) - state received(0) + state i = 0 + state received = 0 reaction(c) {= self.received += 1 diff --git a/test/Python/src/ArrayAsParameter.lf b/test/Python/src/ArrayAsParameter.lf index 67af462358..359e38b7a3 100644 --- a/test/Python/src/ArrayAsParameter.lf +++ b/test/Python/src/ArrayAsParameter.lf @@ -3,7 +3,7 @@ target Python reactor Source(sequence(0, 1, 2)) { output out - state count(0) + state count = 0 logical action next reaction(startup, next) -> out, next {= @@ -16,8 +16,8 @@ reactor Source(sequence(0, 1, 2)) { reactor Print { input _in - state count(1) - state received(0) + state count = 1 + state received = 0 reaction(_in) {= self.received+=1 @@ -36,7 +36,7 @@ reactor Print { } main reactor ArrayAsParameter { - s = new Source(sequence = (1, 2, 3, 4)) + s = new Source(sequence(1, 2, 3, 4)) p = new Print() s.out -> p._in } diff --git a/test/Python/src/ArrayAsType.lf b/test/Python/src/ArrayAsType.lf index 2025cecbd1..dc3b2f6e26 100644 --- a/test/Python/src/ArrayAsType.lf +++ b/test/Python/src/ArrayAsType.lf @@ -11,7 +11,7 @@ reactor Source { =} } -reactor Print(scale(1)) { # The scale parameter is just for testing. +reactor Print(scale = 1) { # The scale parameter is just for testing. input _in reaction(_in) {= diff --git a/test/Python/src/ArrayFree.lf b/test/Python/src/ArrayFree.lf index 893fbc4358..2ea2777c12 100644 --- a/test/Python/src/ArrayFree.lf +++ b/test/Python/src/ArrayFree.lf @@ -7,7 +7,7 @@ target Python import Source, Print from "ArrayPrint.lf" import Scale from "ArrayScale.lf" -reactor Free(scale(2)) { +reactor Free(scale = 2) { mutable input _in reaction(_in) {= diff --git a/test/Python/src/ArrayPrint.lf b/test/Python/src/ArrayPrint.lf index 039debac0c..4420311b93 100644 --- a/test/Python/src/ArrayPrint.lf +++ b/test/Python/src/ArrayPrint.lf @@ -11,7 +11,7 @@ reactor Source { =} } -reactor Print(scale(1)) { # The scale parameter is just for testing. +reactor Print(scale = 1) { # The scale parameter is just for testing. input _in reaction(_in) {= diff --git a/test/Python/src/ArrayScale.lf b/test/Python/src/ArrayScale.lf index 620b63e10f..2b69aebe77 100644 --- a/test/Python/src/ArrayScale.lf +++ b/test/Python/src/ArrayScale.lf @@ -6,7 +6,7 @@ target Python import Print, Source from "ArrayPrint.lf" -reactor Scale(scale(2)) { +reactor Scale(scale = 2) { mutable input _in output out diff --git a/test/Python/src/Composition.lf b/test/Python/src/Composition.lf index 3a737295bf..d1bef93ed2 100644 --- a/test/Python/src/Composition.lf +++ b/test/Python/src/Composition.lf @@ -5,10 +5,10 @@ target Python { timeout: 10 sec } -reactor Source(period(2 sec)) { +reactor Source(period = 2 sec) { output y timer t(1 sec, period) - state count(0) + state count = 0 reaction(t) -> y {= self.count += 1 @@ -20,7 +20,7 @@ reactor Source(period(2 sec)) { reactor Test { input x - state count(0) + state count = 0 reaction(x) {= self.count += 1 diff --git a/test/Python/src/CompositionAfter.lf b/test/Python/src/CompositionAfter.lf index f21b7ae75e..00b95e3ab9 100644 --- a/test/Python/src/CompositionAfter.lf +++ b/test/Python/src/CompositionAfter.lf @@ -5,10 +5,10 @@ target Python { timeout: 10 sec } -reactor Source(period(2 sec)) { +reactor Source(period = 2 sec) { output y timer t(1 sec, period) - state count(0) + state count = 0 reaction(t) -> y {= self.count += 1 @@ -18,7 +18,7 @@ reactor Source(period(2 sec)) { reactor Test { input x - state count(0) + state count = 0 reaction(x) {= self.count += 1 @@ -29,7 +29,7 @@ reactor Test { =} } -main reactor(delay(5 sec)) { +main reactor(delay = 5 sec) { s = new Source() d = new Test() s.y -> d.x after delay diff --git a/test/Python/src/CompositionInheritance.lf b/test/Python/src/CompositionInheritance.lf index deac45b05a..4b0e4a38c9 100644 --- a/test/Python/src/CompositionInheritance.lf +++ b/test/Python/src/CompositionInheritance.lf @@ -5,11 +5,11 @@ target Python { timeout: 10 sec } -reactor Source(period(2 sec)) { +reactor Source(period = 2 sec) { input foo output y timer t(1 sec, period) - state count(0) + state count = 0 reaction(t) -> y {= print("Hello World. My count is: ", self.count) @@ -30,7 +30,7 @@ reactor SourceExtended extends Source { reactor Test { input x - state count(0) + state count = 0 reaction(x) {= self.count += 1 diff --git a/test/Python/src/CountSelf.lf b/test/Python/src/CountSelf.lf index 40f4778c71..74219e6fe0 100644 --- a/test/Python/src/CountSelf.lf +++ b/test/Python/src/CountSelf.lf @@ -4,7 +4,7 @@ target Python { fast: true } -reactor CountSelf2(delay(100 msec)) { +reactor CountSelf2(delay = 100 msec) { output out logical action a @@ -21,7 +21,7 @@ reactor CountSelf2(delay(100 msec)) { reactor Test { input _in - state count(0) + state count = 0 reaction(_in) {= print("Received: {:d}".format(_in.value)) diff --git a/test/Python/src/CountTest.lf b/test/Python/src/CountTest.lf index 2761d22a5b..06c4a6fdb6 100644 --- a/test/Python/src/CountTest.lf +++ b/test/Python/src/CountTest.lf @@ -7,7 +7,7 @@ import Count from "lib/Count.lf" reactor Test { input c - state i(0) + state i = 0 reaction(c) {= print("Received ", c.value) diff --git a/test/Python/src/Deadline.lf b/test/Python/src/Deadline.lf index aeed5f0d5e..ceab2b28e2 100644 --- a/test/Python/src/Deadline.lf +++ b/test/Python/src/Deadline.lf @@ -7,10 +7,10 @@ target Python { preamble {= import time =} -reactor Source(period(3 sec)) { +reactor Source(period = 3 sec) { output y timer t(0, period) - state count(0) + state count = 0 reaction(t) -> y {= if self.count % 2 != 0: @@ -24,9 +24,9 @@ reactor Source(period(3 sec)) { =} } -reactor Destination(timeout(1 sec)) { +reactor Destination(timeout = 1 sec) { input x - state count(0) + state count = 0 reaction(x) {= print("Destination receives: ", x.value) diff --git a/test/Python/src/DeadlineHandledAbove.lf b/test/Python/src/DeadlineHandledAbove.lf index d3df675152..1259253cea 100644 --- a/test/Python/src/DeadlineHandledAbove.lf +++ b/test/Python/src/DeadlineHandledAbove.lf @@ -4,7 +4,7 @@ target Python preamble {= import time =} -reactor Deadline(threshold(100 msec)) { +reactor Deadline(threshold = 100 msec) { input x output deadline_violation @@ -18,7 +18,7 @@ reactor Deadline(threshold(100 msec)) { } main reactor DeadlineHandledAbove { - state violation_detected(false) + state violation_detected = False d = new Deadline(threshold = 10 msec) reaction(startup) -> d.x {= diff --git a/test/Python/src/DelayArray.lf b/test/Python/src/DelayArray.lf index abcf367d45..945b786cca 100644 --- a/test/Python/src/DelayArray.lf +++ b/test/Python/src/DelayArray.lf @@ -1,7 +1,7 @@ # This tests delaying an array type. target Python -reactor DelayPointer(delay(100 msec)) { +reactor DelayPointer(delay = 100 msec) { # The Python target does not require explicit type allocation for types # other than time mutable input _in @@ -25,7 +25,7 @@ reactor Source { =} } -reactor Print(scale(1)) { # The scale parameter is just for testing. +reactor Print(scale = 1) { # The scale parameter is just for testing. input _in reaction(_in) {= diff --git a/test/Python/src/DelayArrayWithAfter.lf b/test/Python/src/DelayArrayWithAfter.lf index e244690ec8..1e6ac1f317 100644 --- a/test/Python/src/DelayArrayWithAfter.lf +++ b/test/Python/src/DelayArrayWithAfter.lf @@ -7,7 +7,7 @@ target Python { reactor Source { output out - state iteration(1) + state iteration = 1 timer t(0, 1 sec) reaction(t) -> out {= @@ -18,10 +18,10 @@ reactor Source { =} } -reactor Print(scale(1)) { # The scale parameter is just for testing. +reactor Print(scale = 1) { # The scale parameter is just for testing. input _in - state iteration(1) - state inputs_received(0) + state iteration = 1 + state inputs_received = 0 reaction(_in) {= self.inputs_received += 1 diff --git a/test/Python/src/DelayInt.lf b/test/Python/src/DelayInt.lf index d12c8b968b..fae554bd30 100644 --- a/test/Python/src/DelayInt.lf +++ b/test/Python/src/DelayInt.lf @@ -1,7 +1,7 @@ # This tests actions with payloads by delaying an input by a fixed amount. target Python -reactor Delay(delay(100 msec)) { +reactor Delay(delay = 100 msec) { input _in output out logical action a @@ -16,8 +16,8 @@ reactor Delay(delay(100 msec)) { reactor Test { input _in - state start_time(0) - state received_value(false) + state start_time = 0 + state received_value = False reaction(startup) {= # Record the logical time at the start. diff --git a/test/Python/src/DelayString.lf b/test/Python/src/DelayString.lf index f315b0bec2..d1201dec02 100644 --- a/test/Python/src/DelayString.lf +++ b/test/Python/src/DelayString.lf @@ -2,7 +2,7 @@ # freed. target Python -reactor DelayString2(delay(100 msec)) { +reactor DelayString2(delay = 100 msec) { input _in output out logical action a @@ -14,7 +14,7 @@ reactor DelayString2(delay(100 msec)) { reactor Test { input _in - state start_time(0) + state start_time = 0 reaction(_in) {= print("Received: ", _in.value) diff --git a/test/Python/src/DelayStruct.lf b/test/Python/src/DelayStruct.lf index f16aedfe7c..e578a42699 100644 --- a/test/Python/src/DelayStruct.lf +++ b/test/Python/src/DelayStruct.lf @@ -5,7 +5,7 @@ target Python { preamble {= import hello =} -reactor DelayPointer(delay(100 msec)) { +reactor DelayPointer(delay = 100 msec) { input _in output out logical action a @@ -25,7 +25,7 @@ reactor Source { reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} } -reactor Print(expected(42)) { # expected parameter is for testing. +reactor Print(expected = 42) { # expected parameter is for testing. input _in reaction(_in) {= diff --git a/test/Python/src/DelayStructWithAfter.lf b/test/Python/src/DelayStructWithAfter.lf index 21dafab29e..5bb945cb3c 100644 --- a/test/Python/src/DelayStructWithAfter.lf +++ b/test/Python/src/DelayStructWithAfter.lf @@ -11,7 +11,7 @@ reactor Source { reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} } -reactor Print(expected(42)) { # expected parameter is for testing. +reactor Print(expected = 42) { # expected parameter is for testing. input _in reaction(_in) {= diff --git a/test/Python/src/DelayStructWithAfterOverlapped.lf b/test/Python/src/DelayStructWithAfterOverlapped.lf index f19c5cd1bd..072012a106 100644 --- a/test/Python/src/DelayStructWithAfterOverlapped.lf +++ b/test/Python/src/DelayStructWithAfterOverlapped.lf @@ -10,7 +10,7 @@ preamble {= import hello =} reactor Source { output out timer t(0, 1 sec) - state s(0) + state s = 0 reaction(t) -> out {= self.s += 1 @@ -20,7 +20,7 @@ reactor Source { reactor Print { # expected parameter is for testing. input _in - state s(0) + state s = 0 reaction(_in) {= self.s += 1 diff --git a/test/Python/src/DelayedAction.lf b/test/Python/src/DelayedAction.lf index 3f6fb70608..06c488b9c5 100644 --- a/test/Python/src/DelayedAction.lf +++ b/test/Python/src/DelayedAction.lf @@ -6,7 +6,7 @@ target Python { main reactor DelayedAction { timer t(0, 1 sec) logical action a - state count(0) + state count = 0 reaction(t) -> a {= a.schedule(MSEC(100)) =} diff --git a/test/Python/src/DoubleInvocation.lf b/test/Python/src/DoubleInvocation.lf index d14edd6332..aaecfa31cb 100644 --- a/test/Python/src/DoubleInvocation.lf +++ b/test/Python/src/DoubleInvocation.lf @@ -13,7 +13,7 @@ target Python { reactor Ball { output position output velocity - state p(200) + state p = 200 timer trigger(0, 1 sec) reaction(trigger) -> position, velocity {= @@ -26,7 +26,7 @@ reactor Ball { reactor Print { input velocity input position - state previous(-1) + state previous = -1 reaction(startup) {= print("####### Print startup\n") diff --git a/test/Python/src/DoubleReaction.lf b/test/Python/src/DoubleReaction.lf index 2061ab5a1c..c20a032a9a 100644 --- a/test/Python/src/DoubleReaction.lf +++ b/test/Python/src/DoubleReaction.lf @@ -5,10 +5,10 @@ target Python { fast: true } -reactor Clock(offset(0), period(1 sec)) { +reactor Clock(offset = 0, period = 1 sec) { output y timer t(offset, period) - state count(0) + state count = 0 reaction(t) -> y {= self.count += 1 @@ -19,7 +19,7 @@ reactor Clock(offset(0), period(1 sec)) { reactor Destination { input x input w - state s(2) + state s = 2 reaction(x, w) {= sm = 0 diff --git a/test/Python/src/FloatLiteral.lf b/test/Python/src/FloatLiteral.lf index 5ed6202429..b55953d135 100644 --- a/test/Python/src/FloatLiteral.lf +++ b/test/Python/src/FloatLiteral.lf @@ -2,10 +2,10 @@ target Python # This test verifies that floating-point literals are handled correctly. main reactor { - state N(6.0221409e+23) - state charge(-1.6021766E-19) - state minus_epsilon(-.01e0) - state expected(.964853323188E5) + state N = 6.0221409e+23 + state charge = -1.6021766E-19 + state minus_epsilon = -.01e0 + state expected = .964853323188E5 reaction(startup) {= F = - self.N * self.charge diff --git a/test/Python/src/Gain.lf b/test/Python/src/Gain.lf index 38459a9380..d69d4d2a65 100644 --- a/test/Python/src/Gain.lf +++ b/test/Python/src/Gain.lf @@ -1,7 +1,7 @@ # Example in the Wiki. target Python -reactor Scale(scale(2)) { +reactor Scale(scale = 2) { input x output y @@ -10,7 +10,7 @@ reactor Scale(scale(2)) { reactor Test { input x - state received_value(0) + state received_value = 0 reaction(x) {= print("Received " + str(x.value)) diff --git a/test/Python/src/GetMicroStep.lf b/test/Python/src/GetMicroStep.lf index 7b1da9bb40..124c692ef8 100644 --- a/test/Python/src/GetMicroStep.lf +++ b/test/Python/src/GetMicroStep.lf @@ -5,7 +5,7 @@ target Python { main reactor GetMicroStep { preamble {= import sys =} - state s(1) + state s = 1 logical action l # timer t(0, 1 msec); diff --git a/test/Python/src/Hello.lf b/test/Python/src/Hello.lf index 0da0d53f47..271ed180ca 100644 --- a/test/Python/src/Hello.lf +++ b/test/Python/src/Hello.lf @@ -8,9 +8,9 @@ target Python { fast: true } -reactor Reschedule(period(2 sec), message("Hello Python")) { - state count(0) - state previous_time(0) +reactor Reschedule(period = 2 sec, message = "Hello Python") { + state count = 0 + state previous_time = 0 timer t(1 sec, period) logical action a @@ -38,7 +38,7 @@ reactor Reschedule(period(2 sec), message("Hello Python")) { =} } -reactor Inside(period(1 sec), message("Composite default message.")) { +reactor Inside(period = 1 sec, message = "Composite default message.") { third_instance = new Reschedule(period = period, message = message) } diff --git a/test/Python/src/HelloWorld.lf b/test/Python/src/HelloWorld.lf index 725a73d0ac..523ff9ce2b 100644 --- a/test/Python/src/HelloWorld.lf +++ b/test/Python/src/HelloWorld.lf @@ -3,7 +3,7 @@ target Python { } reactor HelloWorld2 { - state success(False) + state success = False reaction(startup) {= print("Hello World.") diff --git a/test/Python/src/Hierarchy2.lf b/test/Python/src/Hierarchy2.lf index 07e67a765b..a1792712fa 100644 --- a/test/Python/src/Hierarchy2.lf +++ b/test/Python/src/Hierarchy2.lf @@ -14,7 +14,7 @@ reactor Source { reactor Count { output out timer t(0, 1 sec) - state i(0) + state i = 0 reaction(t) -> out {= self.i += 1 @@ -39,7 +39,7 @@ reactor Add { reactor Print { input _in - state expected(2) + state expected = 2 reaction(_in) {= print("Received: ", _in.value) diff --git a/test/Python/src/IdentifierLength.lf b/test/Python/src/IdentifierLength.lf index 66edf11abf..62b31141a0 100644 --- a/test/Python/src/IdentifierLength.lf +++ b/test/Python/src/IdentifierLength.lf @@ -6,11 +6,11 @@ target Python { } reactor A_Really_Long_Name_For_A_Source_But_Not_Quite_255_Characters_Which_Is_The_Maximum_For_The_Python_Target( - period(2 sec) + period = 2 sec ) { output y timer t(1 sec, period) - state count(0) + state count = 0 reaction(t) -> y {= self.count += 1 @@ -20,7 +20,7 @@ reactor A_Really_Long_Name_For_A_Source_But_Not_Quite_255_Characters_Which_Is_Th reactor Another_Really_Long_Name_For_A_Test_Class { input x - state count(0) + state count = 0 reaction(x) {= self.count += 1 diff --git a/test/Python/src/ImportComposition.lf b/test/Python/src/ImportComposition.lf index 4e57dc6423..369fbcab44 100644 --- a/test/Python/src/ImportComposition.lf +++ b/test/Python/src/ImportComposition.lf @@ -6,7 +6,7 @@ import ImportedComposition from "lib/ImportedComposition.lf" main reactor ImportComposition { a = new ImportedComposition() - state received(false) + state received = False reaction(startup) -> a.x {= a.x.set(42) =} diff --git a/test/Python/src/ManualDelayedReaction.lf b/test/Python/src/ManualDelayedReaction.lf index 0d142405cd..a9e39829eb 100644 --- a/test/Python/src/ManualDelayedReaction.lf +++ b/test/Python/src/ManualDelayedReaction.lf @@ -9,7 +9,7 @@ target Python { reactor GeneratedDelay { input y_in output y_out - state y_state(0) + state y_state = 0 physical action act(0 msec) # TODO: delay in act or the schedule call? diff --git a/test/Python/src/Methods.lf b/test/Python/src/Methods.lf index cc29079dad..5e0023fe8f 100644 --- a/test/Python/src/Methods.lf +++ b/test/Python/src/Methods.lf @@ -2,7 +2,7 @@ target Python main reactor { - state foo(2) + state foo = 2 method getFoo() {= return self.foo =} diff --git a/test/Python/src/MethodsRecursive.lf b/test/Python/src/MethodsRecursive.lf index ca3ea237e7..81afc86bea 100644 --- a/test/Python/src/MethodsRecursive.lf +++ b/test/Python/src/MethodsRecursive.lf @@ -2,7 +2,7 @@ target Python main reactor { - state foo(2) + state foo = 2 method fib(n) {= # Return the n-th Fibonacci number. if n <= 1: diff --git a/test/Python/src/MethodsSameName.lf b/test/Python/src/MethodsSameName.lf index c4d6cdbc4c..a80f521356 100644 --- a/test/Python/src/MethodsSameName.lf +++ b/test/Python/src/MethodsSameName.lf @@ -2,7 +2,7 @@ target Python reactor Foo { - state foo(2) + state foo = 2 method add(x) {= self.foo += x =} @@ -16,7 +16,7 @@ reactor Foo { } main reactor { - state foo(2) + state foo = 2 a = new Foo() diff --git a/test/Python/src/MovingAverage.lf b/test/Python/src/MovingAverage.lf index 505b6a043d..3dbd5636c3 100644 --- a/test/Python/src/MovingAverage.lf +++ b/test/Python/src/MovingAverage.lf @@ -10,7 +10,7 @@ import TestDouble from "lib/Test.lf" reactor MASource { output out - state count(0) + state count = 0 timer clock(0, 200 msec) reaction(clock) -> out {= @@ -21,7 +21,7 @@ reactor MASource { reactor MovingAverageImpl { state delay_line(0.0, 0.0, 0.0) - state index(0) + state index = 0 input m_in output out @@ -45,7 +45,7 @@ reactor MovingAverageImpl { main reactor MovingAverage { s = new MASource() m = new MovingAverageImpl() - p = new TestDouble(expected = (0.0, 0.25, 0.75, 1.5, 2.5, 3.5)) + p = new TestDouble(expected(0.0, 0.25, 0.75, 1.5, 2.5, 3.5)) s.out -> m.m_in m.out -> p.t_in } diff --git a/test/Python/src/MultipleContained.lf b/test/Python/src/MultipleContained.lf index 0b055951c5..27950ccd88 100644 --- a/test/Python/src/MultipleContained.lf +++ b/test/Python/src/MultipleContained.lf @@ -4,7 +4,7 @@ target Python reactor Contained { output trigger - state count(0) + state count = 0 input in1 input in2 diff --git a/test/Python/src/NativeListsAndTimes.lf b/test/Python/src/NativeListsAndTimes.lf index f8b60ed87d..b7b52b3d0a 100644 --- a/test/Python/src/NativeListsAndTimes.lf +++ b/test/Python/src/NativeListsAndTimes.lf @@ -2,22 +2,22 @@ target Python # This test passes if it is successfully compiled into valid target code. main reactor( - x(0), - y(0), # Units are missing but not required - z(1 msec), # Type is missing but not required + x = 0, + y = 0, # Units are missing but not required + z = 1 msec, # Type is missing but not required p(1, 2, 3, 4), # List of integers q(1 msec, 2 msec, 3 msec), # list of time values g(1 msec, 2 msec) # List of time values ) { - state s(y) # Reference to explicitly typed time parameter - state t(z) # Reference to implicitly typed time parameter + state s = y # Reference to explicitly typed time parameter + state t = z # Reference to implicitly typed time parameter state v # Uninitialized boolean state variable state w # Uninitialized time state variable timer tick(0) # Units missing but not required timer tock(1 sec) # Implicit type time timer toe(z) # Implicit type time - state baz(p) # Implicit type int[] - state period(z) # Implicit type time + state baz = p # Implicit type int[] + state period = z # Implicit type time state bar(1 msec, 2 msec, 3 msec) # list of time values state notype(1, 2, 3, 4) diff --git a/test/Python/src/ParameterizedState.lf b/test/Python/src/ParameterizedState.lf index 3511c8d2f5..e8805b027f 100644 --- a/test/Python/src/ParameterizedState.lf +++ b/test/Python/src/ParameterizedState.lf @@ -1,7 +1,7 @@ target Python -reactor Foo(bar(42)) { - state baz(bar) +reactor Foo(bar = 42) { + state baz = bar reaction(startup) {= print("Baz: ", self.baz) =} } diff --git a/test/Python/src/PeriodicDesugared.lf b/test/Python/src/PeriodicDesugared.lf index 5dc7f7bb8f..b003ffc9f8 100644 --- a/test/Python/src/PeriodicDesugared.lf +++ b/test/Python/src/PeriodicDesugared.lf @@ -3,7 +3,7 @@ target Python { timeout: 1 sec } -main reactor(offset(0), period(500 msec)) { +main reactor(offset = 0, period = 500 msec) { logical action init(offset) logical action recur(period) diff --git a/test/Python/src/PingPong.lf b/test/Python/src/PingPong.lf index 25ffdea3a8..f5f80511ae 100644 --- a/test/Python/src/PingPong.lf +++ b/test/Python/src/PingPong.lf @@ -24,10 +24,10 @@ target Python { fast: true } -reactor Ping(count(10)) { +reactor Ping(count = 10) { input receive output send - state pingsLeft(count) + state pingsLeft = count logical action serve reaction(startup, serve) -> send {= @@ -43,10 +43,10 @@ reactor Ping(count(10)) { =} } -reactor Pong(expected(10)) { +reactor Pong(expected = 10) { input receive output send - state count(0) + state count = 0 reaction(receive) -> send {= self.count += 1 diff --git a/test/Python/src/Pipeline.lf b/test/Python/src/Pipeline.lf index 014680e3ca..18436561b8 100644 --- a/test/Python/src/Pipeline.lf +++ b/test/Python/src/Pipeline.lf @@ -17,8 +17,8 @@ reactor TakeTime { reactor Print { input _in - state count(0) - state received(0) + state count = 0 + state received = 0 reaction(_in) {= self.received += 1 @@ -38,7 +38,7 @@ reactor Print { main reactor Pipeline { timer t(0, 200 msec) - state count(0) + state count = 0 c1 = new TakeTime() c2 = new TakeTime() diff --git a/test/Python/src/ReadOutputOfContainedReactor.lf b/test/Python/src/ReadOutputOfContainedReactor.lf index 25c8a1a368..1cb73667ce 100644 --- a/test/Python/src/ReadOutputOfContainedReactor.lf +++ b/test/Python/src/ReadOutputOfContainedReactor.lf @@ -10,7 +10,7 @@ reactor Contained { main reactor ReadOutputOfContainedReactor { c = new Contained() - state count(0) + state count = 0 reaction(startup) c.out {= print("Startup reaction reading output of contained reactor: ", c.out.value) diff --git a/test/Python/src/ScheduleLogicalAction.lf b/test/Python/src/ScheduleLogicalAction.lf index 10f8d4e3cd..3f99c65777 100644 --- a/test/Python/src/ScheduleLogicalAction.lf +++ b/test/Python/src/ScheduleLogicalAction.lf @@ -20,7 +20,7 @@ reactor foo { } reactor print { - state expected_time(0) + state expected_time = 0 input x reaction(x) {= diff --git a/test/Python/src/SelfLoop.lf b/test/Python/src/SelfLoop.lf index 1653b893e3..2b607b09c8 100644 --- a/test/Python/src/SelfLoop.lf +++ b/test/Python/src/SelfLoop.lf @@ -7,7 +7,7 @@ reactor Self { input x output y logical action a - state expected(43) + state expected = 43 reaction(a) -> y {= print("a = ", a.value) diff --git a/test/Python/src/SendingInside.lf b/test/Python/src/SendingInside.lf index 08d05b8ed9..8bcc66c005 100644 --- a/test/Python/src/SendingInside.lf +++ b/test/Python/src/SendingInside.lf @@ -7,7 +7,7 @@ target Python { reactor Printer { input x - state count(1) + state count = 1 reaction(x) {= print("Inside reactor received: ", x.value) @@ -19,7 +19,7 @@ reactor Printer { } main reactor SendingInside { - state count(0) + state count = 0 timer t(0, 1 sec) p = new Printer() diff --git a/test/Python/src/SetArray.lf b/test/Python/src/SetArray.lf index 356899ae8f..4766d529f7 100644 --- a/test/Python/src/SetArray.lf +++ b/test/Python/src/SetArray.lf @@ -9,7 +9,7 @@ reactor Source { reaction(startup) -> out {= out.set([0,1,2]) =} } -reactor Print(scale(1)) { # The scale parameter is just for testing. +reactor Print(scale = 1) { # The scale parameter is just for testing. input _in reaction(_in) {= diff --git a/test/Python/src/SimpleDeadline.lf b/test/Python/src/SimpleDeadline.lf index 0385f95f74..a714124129 100644 --- a/test/Python/src/SimpleDeadline.lf +++ b/test/Python/src/SimpleDeadline.lf @@ -3,7 +3,7 @@ # violation. target Python -reactor Deadline(threshold(100 msec)) { +reactor Deadline(threshold = 100 msec) { input x output deadlineViolation diff --git a/test/Python/src/SlowingClock.lf b/test/Python/src/SlowingClock.lf index b566781677..972421437a 100644 --- a/test/Python/src/SlowingClock.lf +++ b/test/Python/src/SlowingClock.lf @@ -10,8 +10,8 @@ target Python { main reactor SlowingClock { logical action a(100 msec) - state interval(100 msec) - state expected_time(100 msec) + state interval = 100 msec + state expected_time = 100 msec reaction(startup) -> a {= a.schedule(0) =} diff --git a/test/Python/src/SlowingClockPhysical.lf b/test/Python/src/SlowingClockPhysical.lf index b2533acf12..dbb9da705f 100644 --- a/test/Python/src/SlowingClockPhysical.lf +++ b/test/Python/src/SlowingClockPhysical.lf @@ -15,8 +15,8 @@ target Python { main reactor SlowingClockPhysical { # first offset and minimum interarrival time. physical action a(100 msec, 100 msec) - state interval(100 msec) - state expected_time(100 msec) + state interval = 100 msec + state expected_time = 100 msec reaction(startup) -> a {= self.expected_time = MSEC(100) diff --git a/test/Python/src/Stride.lf b/test/Python/src/Stride.lf index 95fe379ead..86c68bab5a 100644 --- a/test/Python/src/Stride.lf +++ b/test/Python/src/Stride.lf @@ -5,8 +5,8 @@ target Python { fast: true } -reactor Count(stride(1)) { - state count(1) +reactor Count(stride = 1) { + state count = 1 output y timer t(0, 100 msec) @@ -18,7 +18,7 @@ reactor Count(stride(1)) { reactor Display { input x - state expected(1) # for testing. + state expected = 1 # for testing. reaction(x) {= print("Received: ", x.value) diff --git a/test/Python/src/StructAsState.lf b/test/Python/src/StructAsState.lf index 4fec8dfc0e..b6f38f8589 100644 --- a/test/Python/src/StructAsState.lf +++ b/test/Python/src/StructAsState.lf @@ -9,7 +9,7 @@ main reactor StructAsState { self.name = name self.value = value =} - state s({= self.hello("Earth", 42) =}) + state s = {= self.hello("Earth", 42) =} reaction(startup) {= print("State s.name=\"{:s}\", value={:d}.".format(self.s.name, self.s.value)) diff --git a/test/Python/src/StructAsType.lf b/test/Python/src/StructAsType.lf index d8955d0064..46951ec86a 100644 --- a/test/Python/src/StructAsType.lf +++ b/test/Python/src/StructAsType.lf @@ -13,7 +13,7 @@ reactor Source { =} } -reactor Print(expected(42)) { # expected parameter is for testing. +reactor Print(expected = 42) { # expected parameter is for testing. input _in reaction(_in) {= diff --git a/test/Python/src/StructAsTypeDirect.lf b/test/Python/src/StructAsTypeDirect.lf index 50f4805a1f..921b7e4390 100644 --- a/test/Python/src/StructAsTypeDirect.lf +++ b/test/Python/src/StructAsTypeDirect.lf @@ -16,7 +16,7 @@ reactor Source { =} } -reactor Print(expected(42)) { # expected parameter is for testing. +reactor Print(expected = 42) { # expected parameter is for testing. input _in reaction(_in) {= diff --git a/test/Python/src/StructParallel.lf b/test/Python/src/StructParallel.lf index fb34a84981..bac79a6abe 100644 --- a/test/Python/src/StructParallel.lf +++ b/test/Python/src/StructParallel.lf @@ -8,7 +8,7 @@ import Source from "StructScale.lf" preamble {= import hello =} -reactor Check(expected(42)) { +reactor Check(expected = 42) { input _in reaction(_in) {= @@ -19,7 +19,7 @@ reactor Check(expected(42)) { =} } -reactor Print(scale(2)) { +reactor Print(scale = 2) { # Mutable keyword indicates that this reactor wants a writable copy of the # input. mutable input _in diff --git a/test/Python/src/StructPrint.lf b/test/Python/src/StructPrint.lf index f232c07a14..3c0138f937 100644 --- a/test/Python/src/StructPrint.lf +++ b/test/Python/src/StructPrint.lf @@ -12,7 +12,7 @@ reactor Print { reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} } -reactor Check(expected(42)) { # expected parameter is for testing. +reactor Check(expected = 42) { # expected parameter is for testing. input _in reaction(_in) {= diff --git a/test/Python/src/StructScale.lf b/test/Python/src/StructScale.lf index e9ed86ff5b..120c6fde65 100644 --- a/test/Python/src/StructScale.lf +++ b/test/Python/src/StructScale.lf @@ -12,7 +12,7 @@ reactor Source { reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} } -reactor TestInput(expected(42)) { # expected parameter is for testing. +reactor TestInput(expected = 42) { # expected parameter is for testing. input _in reaction(_in) {= @@ -23,7 +23,7 @@ reactor TestInput(expected(42)) { # expected parameter is for testing. =} } -reactor Print(scale(2)) { +reactor Print(scale = 2) { # Mutable keyword indicates that this reactor wants a writable copy of the # input. mutable input _in diff --git a/test/Python/src/SubclassesAndStartup.lf b/test/Python/src/SubclassesAndStartup.lf index 9dae1495be..a98e580851 100644 --- a/test/Python/src/SubclassesAndStartup.lf +++ b/test/Python/src/SubclassesAndStartup.lf @@ -1,7 +1,7 @@ target Python reactor Super { - state count(0) + state count = 0 reaction(startup) {= print("{:s}(Super) started".format(self.name)) @@ -15,7 +15,7 @@ reactor Super { =} } -reactor SubA(name("SubA")) extends Super { +reactor SubA(name = "SubA") extends Super { reaction(startup) {= print("{:s} started".format(self.name)) if self.count == 0: @@ -24,7 +24,7 @@ reactor SubA(name("SubA")) extends Super { =} } -reactor SubB(name("SubB")) extends Super { +reactor SubB(name = "SubB") extends Super { reaction(startup) {= print("{:s} started".format(self.name)) if self.count == 0: diff --git a/test/Python/src/TimeLimit.lf b/test/Python/src/TimeLimit.lf index 47577e9ed5..beb7602db9 100644 --- a/test/Python/src/TimeLimit.lf +++ b/test/Python/src/TimeLimit.lf @@ -5,10 +5,10 @@ target Python { fast: true } -reactor Clock(offset(0), period(1 sec)) { +reactor Clock(offset = 0, period = 1 sec) { output y timer t(offset, period) - state count(0) + state count = 0 reaction(t) -> y {= self.count += 1 @@ -19,7 +19,7 @@ reactor Clock(offset(0), period(1 sec)) { reactor Destination { input x - state s(1) + state s = 1 reaction(x) {= # print(x.value) @@ -37,7 +37,7 @@ reactor Destination { =} } -main reactor TimeLimit(period(1 sec)) { +main reactor TimeLimit(period = 1 sec) { timer stop(10 sec) c = new Clock(period = period) d = new Destination() diff --git a/test/Python/src/TimeState.lf b/test/Python/src/TimeState.lf index c12318400b..72ad9f0ebd 100644 --- a/test/Python/src/TimeState.lf +++ b/test/Python/src/TimeState.lf @@ -1,7 +1,7 @@ target Python -reactor Foo(bar(42)) { - state baz(500 msec) +reactor Foo(bar = 42) { + state baz = 500 msec reaction(startup) {= print("Baz: ", self.baz) =} } diff --git a/test/Python/src/Timers.lf b/test/Python/src/Timers.lf index ad59feca99..f6cbba747a 100644 --- a/test/Python/src/Timers.lf +++ b/test/Python/src/Timers.lf @@ -6,7 +6,7 @@ target Python { main reactor { timer t(0, 1 sec) timer t2(0, 2 sec) - state counter(0) + state counter = 0 reaction(t2) {= self.counter += 2 =} diff --git a/test/Python/src/TriggerDownstreamOnlyIfPresent.lf b/test/Python/src/TriggerDownstreamOnlyIfPresent.lf index c436a41cd9..b65f732652 100644 --- a/test/Python/src/TriggerDownstreamOnlyIfPresent.lf +++ b/test/Python/src/TriggerDownstreamOnlyIfPresent.lf @@ -10,7 +10,7 @@ target Python { reactor Source { output a output b - state count(0) + state count = 0 timer t(0, 200 msec) reaction(t) -> a, b {= diff --git a/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf b/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf index 310c1d1d02..b9a9184296 100644 --- a/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf +++ b/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf @@ -9,7 +9,7 @@ target Python { reactor Source { output[2] out - state count(0) + state count = 0 timer t(0, 200 msec) reaction(t) -> out {= diff --git a/test/Python/src/UnconnectedInput.lf b/test/Python/src/UnconnectedInput.lf index ee40844a99..66f459138b 100644 --- a/test/Python/src/UnconnectedInput.lf +++ b/test/Python/src/UnconnectedInput.lf @@ -7,7 +7,7 @@ target Python { reactor Source { output out timer t(0, 1 sec) - state s(1) + state s = 1 reaction(t) -> out {= out.set(self.s) @@ -32,7 +32,7 @@ reactor Add { reactor Print { input _in - state expected(1) + state expected = 1 reaction(_in) {= print("Received: ", _in.value) diff --git a/test/Python/src/concurrent/AsyncCallback.lf b/test/Python/src/concurrent/AsyncCallback.lf index 3769f09dc0..5ef4167bed 100644 --- a/test/Python/src/concurrent/AsyncCallback.lf +++ b/test/Python/src/concurrent/AsyncCallback.lf @@ -30,12 +30,12 @@ main reactor AsyncCallback { return None =} timer t(0, 200 msec) - state threads({= list() =}) - state expected_time(100 msec) - state toggle(false) + state threads = {= list() =} + state expected_time = 100 msec + state toggle = False physical action a(100 msec) - state i(0) + state i = 0 reaction(t) -> a {= # start new thread, provide callback diff --git a/test/Python/src/concurrent/AsyncCallbackNoTimer.lf b/test/Python/src/concurrent/AsyncCallbackNoTimer.lf index ff9a8613d6..4e481d1d34 100644 --- a/test/Python/src/concurrent/AsyncCallbackNoTimer.lf +++ b/test/Python/src/concurrent/AsyncCallbackNoTimer.lf @@ -34,12 +34,12 @@ main reactor { return None =} - state threads({= list() =}) - state expected_time(100 msec) - state toggle(false) + state threads = {= list() =} + state expected_time = 100 msec + state toggle = False physical action a(100 msec) - state i(0) + state i = 0 reaction(startup) -> a {= # start new thread, provide callback diff --git a/test/Python/src/docker/FilesPropertyContainerized.lf b/test/Python/src/docker/FilesPropertyContainerized.lf index 2b592921e8..c34736af48 100644 --- a/test/Python/src/docker/FilesPropertyContainerized.lf +++ b/test/Python/src/docker/FilesPropertyContainerized.lf @@ -17,7 +17,7 @@ main reactor { except: lf_request_stop() =} - state passed(false) + state passed = False timer t(1 msec) reaction(t) {= self.passed = True =} diff --git a/test/Python/src/docker/federated/DistributedCountContainerized.lf b/test/Python/src/docker/federated/DistributedCountContainerized.lf index 50f1f5cb12..5f8a348d24 100644 --- a/test/Python/src/docker/federated/DistributedCountContainerized.lf +++ b/test/Python/src/docker/federated/DistributedCountContainerized.lf @@ -15,7 +15,7 @@ target Python { import Count from "../../lib/Count.lf" import Print from "../../federated/DistributedCount.lf" -federated reactor DistributedCountContainerized(offset(200 msec)) at rti { +federated reactor DistributedCountContainerized(offset = 200 msec) at rti { c = new Count() p = new Print() c.out -> p.in_ after offset diff --git a/test/Python/src/federated/BroadcastFeedback.lf b/test/Python/src/federated/BroadcastFeedback.lf index e7c8dcb527..75d313e61d 100644 --- a/test/Python/src/federated/BroadcastFeedback.lf +++ b/test/Python/src/federated/BroadcastFeedback.lf @@ -8,7 +8,7 @@ target Python { reactor SenderAndReceiver { output out input[2] inp - state received(False) + state received = False reaction(startup) -> out {= out.set(42) =} diff --git a/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf b/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf index 63cd79ad1f..ad2a11c10c 100644 --- a/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf +++ b/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf @@ -8,7 +8,7 @@ target Python { reactor SenderAndReceiver { output out input[2] in_ - state received(False) + state received = False r = new Receiver() in_ -> r.in_ @@ -19,7 +19,7 @@ reactor SenderAndReceiver { reactor Receiver { preamble {= import sys =} input[2] in_ - state received(False) + state received = False reaction(in_) {= if in_[0].is_present and in_[1].is_present and in_[0].value == 42 and in_[1].value == 42: diff --git a/test/Python/src/federated/CycleDetection.lf b/test/Python/src/federated/CycleDetection.lf index faba30f7e1..cf9b6d6b54 100644 --- a/test/Python/src/federated/CycleDetection.lf +++ b/test/Python/src/federated/CycleDetection.lf @@ -10,7 +10,7 @@ reactor CAReplica { input remote_update input query - state balance(0) + state balance = 0 output response diff --git a/test/Python/src/federated/DecentralizedP2PComm.lf b/test/Python/src/federated/DecentralizedP2PComm.lf index 29aee88ad5..2725386915 100644 --- a/test/Python/src/federated/DecentralizedP2PComm.lf +++ b/test/Python/src/federated/DecentralizedP2PComm.lf @@ -5,13 +5,13 @@ target Python { coordination: decentralized } -reactor Platform(start(0), expected_start(0), stp_offset_param(0)) { +reactor Platform(start = 0, expected_start = 0, stp_offset_param = 0) { preamble {= import sys =} input in_ output out timer t(0, 100 msec) - state count(start) - state expected(expected_start) + state count = start + state expected = expected_start reaction(t) -> out {= out.set(self.count) diff --git a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf index 960a06c650..48ff7a4ca7 100644 --- a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf +++ b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf @@ -12,10 +12,10 @@ target Python { } # reason for failing: lf_tag() not supported by the python target -reactor Clock(offset(0), period(1 sec)) { +reactor Clock(offset = 0, period = 1 sec) { output y timer t(offset, period) - state count(0) + state count = 0 reaction(t) -> y {= self.count += 1 @@ -29,7 +29,7 @@ reactor Clock(offset(0), period(1 sec)) { reactor Destination { preamble {= import sys =} input x - state s(1) + state s = 1 state startup_logical_time reaction(startup) {= self.startup_logical_time = lf.time.logical() =} @@ -50,7 +50,7 @@ reactor Destination { =} } -federated reactor(period(10 usec)) { +federated reactor(period = 10 usec) { c = new Clock(period = period) d = new Destination() c.y -> d.x diff --git a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf index c9de7270c6..4eb881694f 100644 --- a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf +++ b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf @@ -12,10 +12,10 @@ target Python { coordination: decentralized } -reactor Clock(offset(0), period(1 sec)) { +reactor Clock(offset = 0, period = 1 sec) { output y timer t(offset, period) - state count(0) + state count = 0 reaction(t) -> y {= self.count += 1 @@ -28,7 +28,7 @@ reactor Clock(offset(0), period(1 sec)) { reactor Destination { preamble {= import sys =} input x - state s(1) + state s = 1 reaction(x) {= if x.value != self.s: @@ -43,7 +43,7 @@ reactor Destination { =} } -federated reactor(period(10 usec)) { +federated reactor(period = 10 usec) { c = new Clock(period = period) d = new Destination() c.y ~> d.x diff --git a/test/Python/src/federated/DistributedBank.lf b/test/Python/src/federated/DistributedBank.lf index ace6b6b53c..de9ea298e4 100644 --- a/test/Python/src/federated/DistributedBank.lf +++ b/test/Python/src/federated/DistributedBank.lf @@ -7,7 +7,7 @@ target Python { reactor Node { preamble {= import sys =} timer t(0, 100 msec) - state count(0) + state count = 0 reaction(t) {= print("Hello world {}.".format(self.count)) diff --git a/test/Python/src/federated/DistributedBankToMultiport.lf b/test/Python/src/federated/DistributedBankToMultiport.lf index f40a85fc65..493e73a3b1 100644 --- a/test/Python/src/federated/DistributedBankToMultiport.lf +++ b/test/Python/src/federated/DistributedBankToMultiport.lf @@ -8,7 +8,7 @@ import Count from "../lib/Count.lf" reactor Destination { preamble {= import sys =} input[2] in_ - state count(1) + state count = 1 reaction(in_) {= for i in range(len(in_)): diff --git a/test/Python/src/federated/DistributedCount.lf b/test/Python/src/federated/DistributedCount.lf index eb90b4d672..7713256520 100644 --- a/test/Python/src/federated/DistributedCount.lf +++ b/test/Python/src/federated/DistributedCount.lf @@ -15,7 +15,7 @@ import Count from "../lib/Count.lf" reactor Print { preamble {= import sys =} input in_ - state c(1) + state c = 1 reaction(in_) {= elapsed_time = lf.time.logical_elapsed() @@ -36,7 +36,7 @@ reactor Print { =} } -federated reactor DistributedCount(offset(200 msec)) { +federated reactor DistributedCount(offset = 200 msec) { c = new Count() p = new Print() c.out -> p.in_ after offset diff --git a/test/Python/src/federated/DistributedCountDecentralized.lf b/test/Python/src/federated/DistributedCountDecentralized.lf index 232c3ef830..e86adf9946 100644 --- a/test/Python/src/federated/DistributedCountDecentralized.lf +++ b/test/Python/src/federated/DistributedCountDecentralized.lf @@ -16,7 +16,7 @@ import Count from "../lib/Count.lf" reactor Print { preamble {= import sys =} input in_ - state c(1) + state c = 1 reaction(in_) {= print(f"At tag ({lf.time.logical_elapsed()}, {lf.tag().microstep}), received {in_.value}. " diff --git a/test/Python/src/federated/DistributedCountDecentralizedLate.lf b/test/Python/src/federated/DistributedCountDecentralizedLate.lf index 617673aa0e..e3a6899e78 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLate.lf @@ -18,12 +18,12 @@ reactor Print { # STP () input in_ # STP(in, 30 msec); - state success(0) - state success_stp_violation(0) + state success = 0 + state success_stp_violation = 0 # Force a timer to be invoke periodically timer t(0, 10 usec) # to ensure logical time will advance in the absence of incoming messages. - state c(0) + state c = 0 reaction(in_) {= current_tag = lf.tag() diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf index 18a55f7e6b..c1c641827f 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf @@ -28,12 +28,12 @@ import Count from "../lib/Count.lf" reactor ImportantActuator { input inp # Count messages that arrive without STP violation. - state success(0) - state success_stp_violation(0) + state success = 0 + state success_stp_violation = 0 # Force a timer to be invoked periodically timer t(0, 10 usec) # to ensure logical time will advance in the absence of incoming messages. - state c(0) + state c = 0 reaction(inp) {= current_tag = lf.tag() @@ -81,7 +81,7 @@ reactor Receiver { # Force a timer to be invoke periodically timer t(0, 10 msec) # to ensure logical time will advance in the absence of incoming messages. - state c(0) + state c = 0 p = new Print() a = new ImportantActuator() diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf index e9a1bec87e..d806ab5453 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf @@ -21,7 +21,7 @@ reactor Receiver { # Force a timer to be invoke periodically timer t(0, 10 msec) # to ensure logical time will advance in the absence of incoming messages. - state c(0) + state c = 0 p = new Print() a = new ImportantActuator() inp -> p.inp diff --git a/test/Python/src/federated/DistributedCountPhysical.lf b/test/Python/src/federated/DistributedCountPhysical.lf index b957e06172..69bc2faee9 100644 --- a/test/Python/src/federated/DistributedCountPhysical.lf +++ b/test/Python/src/federated/DistributedCountPhysical.lf @@ -13,7 +13,7 @@ target Python { reactor Count { timer t(200 msec, 1 sec) - state s(0) + state s = 0 output out reaction(t) -> out {= @@ -25,7 +25,7 @@ reactor Count { reactor Print { preamble {= import sys =} input in_ - state c(0) + state c = 0 reaction(in_) {= elapsed_time = lf.time.logical_elapsed() diff --git a/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf b/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf index 80c931184b..105413d0a0 100644 --- a/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf +++ b/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf @@ -13,7 +13,7 @@ target Python { reactor Count { timer t(200 msec, 1 sec) - state s(0) + state s = 0 output out reaction(t) -> out {= @@ -25,7 +25,7 @@ reactor Count { reactor Print { preamble {= import sys =} input in_ - state c(0) + state c = 0 reaction(in_) {= elapsed_time = lf.time.logical_elapsed() diff --git a/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf b/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf index 120bb6255c..0008479138 100644 --- a/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf +++ b/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf @@ -14,7 +14,7 @@ target Python { reactor Count { timer t(200 msec, 1 sec) - state s(0) + state s = 0 output out reaction(t) -> out {= @@ -26,7 +26,7 @@ reactor Count { reactor Print { preamble {= import sys =} input in_ - state c(0) + state c = 0 reaction(in_) {= elapsed_time = lf.time.logical_elapsed() diff --git a/test/Python/src/federated/DistributedDoublePort.lf b/test/Python/src/federated/DistributedDoublePort.lf index 03ad2c4d08..9eb24209e6 100644 --- a/test/Python/src/federated/DistributedDoublePort.lf +++ b/test/Python/src/federated/DistributedDoublePort.lf @@ -14,7 +14,7 @@ target Python { import Count from "../lib/Count.lf" reactor CountMicrostep { - state count(1) + state count = 1 output out logical action act timer t(0, 1 sec) diff --git a/test/Python/src/federated/DistributedLoopedAction.lf b/test/Python/src/federated/DistributedLoopedAction.lf index 83aa1de0a6..8f71d15363 100644 --- a/test/Python/src/federated/DistributedLoopedAction.lf +++ b/test/Python/src/federated/DistributedLoopedAction.lf @@ -10,12 +10,12 @@ target Python { import Sender from "../lib/LoopedActionSender.lf" -reactor Receiver(take_a_break_after(10), break_interval(400 msec)) { +reactor Receiver(take_a_break_after = 10, break_interval = 400 msec) { preamble {= import sys =} input in_ - state received_messages(0) - state total_received_messages(0) - state breaks(0) + state received_messages = 0 + state total_received_messages = 0 + state breaks = 0 timer t(0, 1 msec) # This will impact the performance # but forces the logical time to advance Comment this line for a more diff --git a/test/Python/src/federated/DistributedLoopedPhysicalAction.lf b/test/Python/src/federated/DistributedLoopedPhysicalAction.lf index a2cce3742a..d992e3d841 100644 --- a/test/Python/src/federated/DistributedLoopedPhysicalAction.lf +++ b/test/Python/src/federated/DistributedLoopedPhysicalAction.lf @@ -14,10 +14,10 @@ target Python { keepalive: true } -reactor Sender(take_a_break_after(10), break_interval(550 msec)) { +reactor Sender(take_a_break_after = 10, break_interval = 550 msec) { output out physical action act - state sent_messages(0) + state sent_messages = 0 reaction(startup, act) -> act, out {= # Send a message on out @@ -32,12 +32,12 @@ reactor Sender(take_a_break_after(10), break_interval(550 msec)) { =} } -reactor Receiver(take_a_break_after(10), break_interval(550 msec)) { +reactor Receiver(take_a_break_after = 10, break_interval = 550 msec) { preamble {= import sys =} input in_ - state received_messages(0) - state total_received_messages(0) - state breaks(0) + state received_messages = 0 + state total_received_messages = 0 + state breaks = 0 timer t(0, 1 msec) # This will impact the performance # but forces the logical time to advance Comment this line for a more # sensible log output. diff --git a/test/Python/src/federated/DistributedMultiport.lf b/test/Python/src/federated/DistributedMultiport.lf index 729f8697d8..7250f838ef 100644 --- a/test/Python/src/federated/DistributedMultiport.lf +++ b/test/Python/src/federated/DistributedMultiport.lf @@ -7,7 +7,7 @@ target Python { reactor Source { output[4] out timer t(0, 100 msec) - state count(0) + state count = 0 reaction(t) -> out {= for i in range(len(out)): @@ -19,7 +19,7 @@ reactor Source { reactor Destination { preamble {= import sys =} input[4] in_ - state count(0) + state count = 0 reaction(in_) {= for i in range(len(in_)): diff --git a/test/Python/src/federated/DistributedMultiportToBank.lf b/test/Python/src/federated/DistributedMultiportToBank.lf index cbd500394e..69c87b7f0c 100644 --- a/test/Python/src/federated/DistributedMultiportToBank.lf +++ b/test/Python/src/federated/DistributedMultiportToBank.lf @@ -6,7 +6,7 @@ target Python { reactor Source { output[2] out timer t(0, 100 msec) - state count(0) + state count = 0 reaction(t) -> out {= for i in range(len(out)): @@ -18,7 +18,7 @@ reactor Source { reactor Destination { preamble {= import sys =} input in_ - state count(0) + state count = 0 reaction(in_) {= print("Received {}.".format(in_.value)) diff --git a/test/Python/src/federated/DistributedMultiportToken.lf b/test/Python/src/federated/DistributedMultiportToken.lf index fdecd67c32..284e1c21a3 100644 --- a/test/Python/src/federated/DistributedMultiportToken.lf +++ b/test/Python/src/federated/DistributedMultiportToken.lf @@ -8,7 +8,7 @@ target Python { reactor Source { output[4] out timer t(0, 200 msec) - state count(0) + state count = 0 reaction(t) -> out {= for i in range(len(out)): diff --git a/test/Python/src/federated/DistributedStop.lf b/test/Python/src/federated/DistributedStop.lf index b4a84b8bef..06dfe96c36 100644 --- a/test/Python/src/federated/DistributedStop.lf +++ b/test/Python/src/federated/DistributedStop.lf @@ -12,7 +12,7 @@ reactor Sender { output out timer t(0, 1 usec) logical action act - state reaction_invoked_correctly(False) + state reaction_invoked_correctly = False reaction(t, act) -> out, act {= tag = lf.tag() @@ -62,10 +62,10 @@ reactor Sender { } reactor Receiver( - stp_offset(10 msec) # Used in the decentralized variant of the test + stp_offset = 10 msec # Used in the decentralized variant of the test ) { input in_ - state reaction_invoked_correctly(False) + state reaction_invoked_correctly = False reaction(in_) {= tag = lf.tag() diff --git a/test/Python/src/federated/HelloDistributed.lf b/test/Python/src/federated/HelloDistributed.lf index 5b8a2a55e4..ef99c3f793 100644 --- a/test/Python/src/federated/HelloDistributed.lf +++ b/test/Python/src/federated/HelloDistributed.lf @@ -19,7 +19,7 @@ reactor Source { reactor Destination { input _in - state received(false) + state received = False reaction(startup) {= print("Destination started.") =} diff --git a/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf b/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf index 9dc86b3c60..2719fd57ba 100644 --- a/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf +++ b/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf @@ -14,11 +14,11 @@ target Python { timeout: 5 sec } -reactor Contained(incr(1)) { +reactor Contained(incr = 1) { timer t(0, 1 sec) input inp - state count(0) - state received_count(0) + state count = 0 + state received_count = 0 reaction(t) {= self.count += self.incr =} @@ -31,10 +31,10 @@ reactor Contained(incr(1)) { =} } -reactor Looper(incr(1), delay(0 msec)) { +reactor Looper(incr = 1, delay = 0 msec) { input inp output out - state count(0) + state count = 0 timer t(0, 1 sec) c = new Contained(incr = incr) @@ -59,7 +59,7 @@ reactor Looper(incr(1), delay(0 msec)) { =} } -federated reactor(delay(0)) { +federated reactor(delay = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.inp diff --git a/test/Python/src/federated/PhysicalSTP.lf b/test/Python/src/federated/PhysicalSTP.lf index 75e5071bca..5edc61df34 100644 --- a/test/Python/src/federated/PhysicalSTP.lf +++ b/test/Python/src/federated/PhysicalSTP.lf @@ -9,10 +9,10 @@ target Python { import Count from "../lib/Count.lf" -reactor Print(STP_offset(0)) { +reactor Print(STP_offset = 0) { preamble {= import sys =} input in_ - state c(1) + state c = 1 reaction(in_) {= elapsed_time = lf.time.logical_elapsed() diff --git a/test/Python/src/federated/PingPongDistributed.lf b/test/Python/src/federated/PingPongDistributed.lf index a157baa22a..0f58cbf7db 100644 --- a/test/Python/src/federated/PingPongDistributed.lf +++ b/test/Python/src/federated/PingPongDistributed.lf @@ -21,10 +21,10 @@ */ target Python -reactor Ping(count(10)) { +reactor Ping(count = 10) { input receive output send - state pingsLeft(count) + state pingsLeft = count logical action serve reaction(startup, serve) -> send {= @@ -41,12 +41,12 @@ reactor Ping(count(10)) { =} } -reactor Pong(expected(10)) { +reactor Pong(expected = 10) { preamble {= import sys =} input receive output send - state count(0) + state count = 0 reaction(receive) -> send {= self.count += 1 @@ -64,7 +64,7 @@ reactor Pong(expected(10)) { =} } -federated reactor(count(10)) { +federated reactor(count = 10) { ping = new Ping(count = count) pong = new Pong(expected = count) ping.send ~> pong.receive diff --git a/test/Python/src/federated/failing/LoopDistributedCentralizedPrecedence.lf b/test/Python/src/federated/failing/LoopDistributedCentralizedPrecedence.lf index 9068ebfae8..12367332f4 100644 --- a/test/Python/src/federated/failing/LoopDistributedCentralizedPrecedence.lf +++ b/test/Python/src/federated/failing/LoopDistributedCentralizedPrecedence.lf @@ -16,11 +16,11 @@ target Python { timeout: 5 sec } -reactor Looper(incr(1), delay(0 msec)) { +reactor Looper(incr = 1, delay = 0 msec) { input in_ output out - state count(0) - state received_count(0) + state count = 0 + state received_count = 0 timer t(0, 1 sec) reaction(t) -> out {= @@ -50,7 +50,7 @@ reactor Looper(incr(1), delay(0 msec)) { =} } -federated reactor(delay(0)) { +federated reactor(delay = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.in_ diff --git a/test/Python/src/federated/failing/LoopDistributedDouble.lf b/test/Python/src/federated/failing/LoopDistributedDouble.lf index 8788d09288..4ccee4acd6 100644 --- a/test/Python/src/federated/failing/LoopDistributedDouble.lf +++ b/test/Python/src/federated/failing/LoopDistributedDouble.lf @@ -24,13 +24,13 @@ preamble {= } =} -reactor Looper(incr(1), delay(0 msec)) { +reactor Looper(incr = 1, delay = 0 msec) { input in_ input in2 output out output out2 physical action a(delay) - state count(0) + state count = 0 timer t(0, 1 sec) reaction(startup) -> a {= @@ -79,7 +79,7 @@ reactor Looper(incr(1), delay(0 msec)) { =} } -federated reactor(delay(0)) { +federated reactor(delay = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.in_ diff --git a/test/Python/src/lib/Count.lf b/test/Python/src/lib/Count.lf index bcfae967b6..26b53ce13b 100644 --- a/test/Python/src/lib/Count.lf +++ b/test/Python/src/lib/Count.lf @@ -1,7 +1,7 @@ target Python -reactor Count(offset(0), period(1 sec)) { - state count(1) +reactor Count(offset = 0, period = 1 sec) { + state count = 1 output out timer t(offset, period) diff --git a/test/Python/src/lib/InternalDelay.lf b/test/Python/src/lib/InternalDelay.lf index f2ac5449c0..a9796aa8d9 100644 --- a/test/Python/src/lib/InternalDelay.lf +++ b/test/Python/src/lib/InternalDelay.lf @@ -1,6 +1,6 @@ target Python -reactor InternalDelay(delay(10 msec)) { +reactor InternalDelay(delay = 10 msec) { input in_ output out logical action d diff --git a/test/Python/src/lib/LoopedActionSender.lf b/test/Python/src/lib/LoopedActionSender.lf index 3a01219ec8..9d4048d0ea 100644 --- a/test/Python/src/lib/LoopedActionSender.lf +++ b/test/Python/src/lib/LoopedActionSender.lf @@ -11,10 +11,10 @@ target Python * @param break_interval: Determines how long the reactor should take a break * after sending take_a_break_after messages. */ -reactor Sender(take_a_break_after(10), break_interval(400 msec)) { +reactor Sender(take_a_break_after = 10, break_interval = 400 msec) { output out logical action act - state sent_messages(0) + state sent_messages = 0 reaction(startup, act) -> act, out {= # Send a message on out diff --git a/test/Python/src/lib/Test.lf b/test/Python/src/lib/Test.lf index f6bbf867ee..301ed9c8e6 100644 --- a/test/Python/src/lib/Test.lf +++ b/test/Python/src/lib/Test.lf @@ -2,7 +2,7 @@ target Python reactor TestDouble(expected(1.0, 1.0, 1.0, 1.0)) { input t_in - state count(0) + state count = 0 reaction(t_in) {= print("Received: ", t_in.value) diff --git a/test/Python/src/lib/TestCount.lf b/test/Python/src/lib/TestCount.lf index dfe11cd5b9..de611cbc69 100644 --- a/test/Python/src/lib/TestCount.lf +++ b/test/Python/src/lib/TestCount.lf @@ -9,10 +9,10 @@ */ target Python -reactor TestCount(start(1), stride(1), num_inputs(1)) { +reactor TestCount(start = 1, stride = 1, num_inputs = 1) { preamble {= import sys =} - state count(start) - state inputs_received(0) + state count = start + state inputs_received = 0 input in_ reaction(in_) {= diff --git a/test/Python/src/lib/TestCountMultiport.lf b/test/Python/src/lib/TestCountMultiport.lf index d9b711ce89..c30572e4d5 100644 --- a/test/Python/src/lib/TestCountMultiport.lf +++ b/test/Python/src/lib/TestCountMultiport.lf @@ -11,10 +11,10 @@ */ target Python -reactor TestCountMultiport(start(1), stride(1), num_inputs(1), width(2)) { +reactor TestCountMultiport(start = 1, stride = 1, num_inputs = 1, width = 2) { preamble {= import sys =} - state count(start) - state inputs_received(0) + state count = start + state inputs_received = 0 input[width] inp reaction(inp) {= diff --git a/test/Python/src/modal_models/ConvertCaseTest.lf b/test/Python/src/modal_models/ConvertCaseTest.lf index f391660322..f3bdee59fe 100644 --- a/test/Python/src/modal_models/ConvertCaseTest.lf +++ b/test/Python/src/modal_models/ConvertCaseTest.lf @@ -67,9 +67,9 @@ reactor Converter { } } -reactor InputFeeder(message("")) { +reactor InputFeeder(message = "") { output character - state idx(0) + state idx = 0 timer t(0, 250 msec) diff --git a/test/Python/src/modal_models/Count3Modes.lf b/test/Python/src/modal_models/Count3Modes.lf index e512e7d54a..7e2e9d98e2 100644 --- a/test/Python/src/modal_models/Count3Modes.lf +++ b/test/Python/src/modal_models/Count3Modes.lf @@ -34,7 +34,7 @@ main reactor { timer stepper(0, 250 msec) counter = new CounterCycle() - state expected_value(1) + state expected_value = 1 reaction(stepper) -> counter.next {= counter.next.set(True) =} # Trigger diff --git a/test/Python/src/modal_models/ModalAfter.lf b/test/Python/src/modal_models/ModalAfter.lf index c86002c9fa..19c84f33d5 100644 --- a/test/Python/src/modal_models/ModalAfter.lf +++ b/test/Python/src/modal_models/ModalAfter.lf @@ -45,7 +45,7 @@ reactor Modal { } } -reactor Producer(mode_id(0)) { +reactor Producer(mode_id = 0) { output product timer t(0, 750 msec) @@ -56,7 +56,7 @@ reactor Producer(mode_id(0)) { =} } -reactor Consumer(mode_id(0)) { +reactor Consumer(mode_id = 0) { input product output report diff --git a/test/Python/src/modal_models/ModalCycleBreaker.lf b/test/Python/src/modal_models/ModalCycleBreaker.lf index baafdf8378..1ceb2eb5d0 100644 --- a/test/Python/src/modal_models/ModalCycleBreaker.lf +++ b/test/Python/src/modal_models/ModalCycleBreaker.lf @@ -39,11 +39,11 @@ reactor Modal { } } -reactor Counter(period(1 sec)) { +reactor Counter(period = 1 sec) { output value timer t(0, period) - state curval(0) + state curval = 0 reaction(t) -> value {= value.set(self.curval) diff --git a/test/Python/src/modal_models/ModalStateReset.lf b/test/Python/src/modal_models/ModalStateReset.lf index 9bd969b15c..11b7425b63 100644 --- a/test/Python/src/modal_models/ModalStateReset.lf +++ b/test/Python/src/modal_models/ModalStateReset.lf @@ -14,7 +14,7 @@ reactor Modal { output count1 output count2 - state counter0(0) + state counter0 = 0 reaction(next) -> count0 {= print(f"Counter0: {self.counter0}") @@ -23,7 +23,7 @@ reactor Modal { =} initial mode One { - state counter1(0) + state counter1 = 0 timer T1(0 msec, 250 msec) reaction(reset) {= self.counter1 = 0 =} @@ -41,7 +41,7 @@ reactor Modal { } mode Two { - state counter2(-2) + state counter2 = -2 timer T2(0 msec, 250 msec) reaction(reset) {= self.counter2 = -2 =} diff --git a/test/Python/src/modal_models/ModalStateResetAuto.lf b/test/Python/src/modal_models/ModalStateResetAuto.lf index be664676db..492e12259b 100644 --- a/test/Python/src/modal_models/ModalStateResetAuto.lf +++ b/test/Python/src/modal_models/ModalStateResetAuto.lf @@ -14,7 +14,7 @@ reactor Modal { output count1 output count2 - state counter0(0) + state counter0 = 0 reaction(next) -> count0 {= print(f"Counter0: {self.counter0}") @@ -23,7 +23,7 @@ reactor Modal { =} initial mode One { - state counter1(0) + state counter1 = 0 timer T1(0 msec, 250 msec) reaction(T1) -> count1 {= print(f"Counter1: {self.counter1}") @@ -39,7 +39,7 @@ reactor Modal { } mode Two { - reset state counter2(-2) + reset state counter2 = -2 timer T2(0 msec, 250 msec) reaction(T2) -> count2 {= print(f"Counter2: {self.counter2}") diff --git a/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf b/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf index 5654dd9b1e..3dd42f67ce 100644 --- a/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf +++ b/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf @@ -28,11 +28,11 @@ reactor Modal { } } -reactor Counter(period(1 sec)) { +reactor Counter(period = 1 sec) { output value timer t(0, period) - reset state curval(0) + reset state curval = 0 reaction(t) -> value {= value.set(self.curval) diff --git a/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf b/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf index 80c4247972..86f5668357 100644 --- a/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf +++ b/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf @@ -31,11 +31,11 @@ reactor Modal { } } -reactor Counter(period(1 sec)) { +reactor Counter(period = 1 sec) { output value timer t(0, period) - reset state curval(0) + reset state curval = 0 reaction(t) -> value {= value.set(self.curval) diff --git a/test/Python/src/modal_models/util/TraceTesting.lf b/test/Python/src/modal_models/util/TraceTesting.lf index 1014ac3c8d..305ff31a5b 100644 --- a/test/Python/src/modal_models/util/TraceTesting.lf +++ b/test/Python/src/modal_models/util/TraceTesting.lf @@ -1,13 +1,13 @@ /** Utility reactor to record and test execution traces. */ target Python -reactor TraceTesting(events_size(0), trace({= [] =}), training(False)) { +reactor TraceTesting(events_size = 0, trace = {= [] =}, training = False) { input[events_size] events - state last_reaction_time(0) - state trace_idx(0) - state recorded_events({= [] =}) - state recorded_events_next(0) + state last_reaction_time = 0 + state trace_idx = 0 + state recorded_events = {= [] =} + state recorded_events_next = 0 reaction(startup) {= self.last_reaction_time = lf.time.logical() =} diff --git a/test/Python/src/multiport/BankIndexInitializer.lf b/test/Python/src/multiport/BankIndexInitializer.lf index 6754f31d97..52ef58bb1d 100644 --- a/test/Python/src/multiport/BankIndexInitializer.lf +++ b/test/Python/src/multiport/BankIndexInitializer.lf @@ -3,15 +3,15 @@ target Python preamble {= table = [4, 3, 2, 1] =} -reactor Source(bank_index(0), value(0)) { +reactor Source(bank_index = 0, value = 0) { output out reaction(startup) -> out {= out.set(self.value) =} } -reactor Sink(width(4)) { +reactor Sink(width = 4) { input[width] _in - state received(false) + state received = False reaction(_in) {= for (idx, port) in enumerate(_in): @@ -30,7 +30,7 @@ reactor Sink(width(4)) { =} } -main reactor(width(4)) { +main reactor(width = 4) { source = new[width] Source(value = {= table[bank_index] =}) sink = new Sink(width = width) source.out -> sink._in diff --git a/test/Python/src/multiport/BankReactionsInContainer.lf b/test/Python/src/multiport/BankReactionsInContainer.lf index 3a72418405..7316463c3d 100644 --- a/test/Python/src/multiport/BankReactionsInContainer.lf +++ b/test/Python/src/multiport/BankReactionsInContainer.lf @@ -5,10 +5,10 @@ target Python { timeout: 1 sec } -reactor R(bank_index(0)) { +reactor R(bank_index = 0) { output[2] out input[2] inp - state received(false) + state received = False reaction(startup) -> out {= for (i, p) in enumerate(out): @@ -37,7 +37,7 @@ reactor R(bank_index(0)) { main reactor { s = new[2] R() - state received(false) + state received = False reaction(startup) -> s.inp {= count = 0 diff --git a/test/Python/src/multiport/BankToBank.lf b/test/Python/src/multiport/BankToBank.lf index e94b7dd717..6e3ddc734d 100644 --- a/test/Python/src/multiport/BankToBank.lf +++ b/test/Python/src/multiport/BankToBank.lf @@ -4,10 +4,10 @@ target Python { fast: true } -reactor Source(bank_index(0)) { +reactor Source(bank_index = 0) { timer t(0, 200 msec) output out - state s(0) + state s = 0 reaction(t) -> out {= out.set(self.s) @@ -15,8 +15,8 @@ reactor Source(bank_index(0)) { =} } -reactor Destination(bank_index(0)) { - state s(0) +reactor Destination(bank_index = 0) { + state s = 0 input _in reaction(_in) {= @@ -35,7 +35,7 @@ reactor Destination(bank_index(0)) { =} } -main reactor BankToBank(width(4)) { +main reactor BankToBank(width = 4) { a = new[width] Source() b = new[width] Destination() a.out -> b._in diff --git a/test/Python/src/multiport/BankToBankMultiport.lf b/test/Python/src/multiport/BankToBankMultiport.lf index de2f05a55d..f335e53858 100644 --- a/test/Python/src/multiport/BankToBankMultiport.lf +++ b/test/Python/src/multiport/BankToBankMultiport.lf @@ -4,10 +4,10 @@ target Python { fast: true } -reactor Source(width(1)) { +reactor Source(width = 1) { timer t(0, 200 msec) output[width] out - state s(0) + state s = 0 reaction(t) -> out {= for port in out: @@ -16,8 +16,8 @@ reactor Source(width(1)) { =} } -reactor Destination(width(1)) { - state s(6) +reactor Destination(width = 1) { + state s = 6 input[width] _in reaction(_in) {= @@ -43,7 +43,7 @@ reactor Destination(width(1)) { =} } -main reactor BankToBankMultiport(bank_width(4)) { +main reactor BankToBankMultiport(bank_width = 4) { a = new[bank_width] Source(width = 4) b = new[bank_width] Destination(width = 4) a.out -> b._in diff --git a/test/Python/src/multiport/BankToBankMultiportAfter.lf b/test/Python/src/multiport/BankToBankMultiportAfter.lf index eb7f025772..9d6e946122 100644 --- a/test/Python/src/multiport/BankToBankMultiportAfter.lf +++ b/test/Python/src/multiport/BankToBankMultiportAfter.lf @@ -6,7 +6,7 @@ target Python { import Source, Destination from "BankToBankMultiport.lf" -main reactor BankToBankMultiportAfter(bank_width(4)) { +main reactor BankToBankMultiportAfter(bank_width = 4) { a = new[bank_width] Source(width = 4) b = new[bank_width] Destination(width = 4) a.out -> b._in after 200 msec diff --git a/test/Python/src/multiport/BankToMultiport.lf b/test/Python/src/multiport/BankToMultiport.lf index 110295400d..769e0ea2fc 100644 --- a/test/Python/src/multiport/BankToMultiport.lf +++ b/test/Python/src/multiport/BankToMultiport.lf @@ -1,15 +1,15 @@ # Test bank of reactors to multiport input with id parameter in the bank. target Python -reactor Source(bank_index(0)) { +reactor Source(bank_index = 0) { output out reaction(startup) -> out {= out.set(self.bank_index) =} } -reactor Sink(width(4)) { +reactor Sink(width = 4) { input[width] _in - state received(false) + state received = False reaction(_in) {= for (idx, port) in enumerate(_in): @@ -28,7 +28,7 @@ reactor Sink(width(4)) { =} } -main reactor BankToMultiport(width(5)) { +main reactor BankToMultiport(width = 5) { source = new[width] Source() sink = new Sink(width = width) source.out -> sink._in diff --git a/test/Python/src/multiport/Broadcast.lf b/test/Python/src/multiport/Broadcast.lf index ebc4de3067..2e507d607f 100644 --- a/test/Python/src/multiport/Broadcast.lf +++ b/test/Python/src/multiport/Broadcast.lf @@ -3,15 +3,15 @@ target Python { fast: true } -reactor Source(value(42)) { +reactor Source(value = 42) { output out reaction(startup) -> out {= out.set(self.value) =} } -reactor Destination(bank_index(0), delay(0)) { +reactor Destination(bank_index = 0, delay = 0) { input _in - state received(false) + state received = False reaction(_in) {= print(f"Destination {self.bank_index} received {_in.value}.") diff --git a/test/Python/src/multiport/BroadcastMultipleAfter.lf b/test/Python/src/multiport/BroadcastMultipleAfter.lf index 7777406c38..9fc327e6fd 100644 --- a/test/Python/src/multiport/BroadcastMultipleAfter.lf +++ b/test/Python/src/multiport/BroadcastMultipleAfter.lf @@ -5,9 +5,9 @@ target Python { import Source from "Broadcast.lf" -reactor Destination(bank_index(0), delay(0)) { +reactor Destination(bank_index = 0, delay = 0) { input _in - state received(false) + state received = False reaction(_in) {= print(f"Destination {self.bank_index} received {_in.value}.") diff --git a/test/Python/src/multiport/MultiportFromBank.lf b/test/Python/src/multiport/MultiportFromBank.lf index 05dc165fa1..92d9363df5 100644 --- a/test/Python/src/multiport/MultiportFromBank.lf +++ b/test/Python/src/multiport/MultiportFromBank.lf @@ -5,7 +5,7 @@ target Python { fast: true } -reactor Source(check_override(0), bank_index(0)) { +reactor Source(check_override = 0, bank_index = 0) { output out reaction(startup) -> out {= @@ -15,7 +15,7 @@ reactor Source(check_override(0), bank_index(0)) { reactor Destination { input[3] _in - state received(0) + state received = 0 reaction(_in) {= for (idx, port) in enumerate(_in): diff --git a/test/Python/src/multiport/MultiportFromBankHierarchy.lf b/test/Python/src/multiport/MultiportFromBankHierarchy.lf index ac62f5829f..27ecb3c80b 100644 --- a/test/Python/src/multiport/MultiportFromBankHierarchy.lf +++ b/test/Python/src/multiport/MultiportFromBankHierarchy.lf @@ -7,7 +7,7 @@ target Python { import Destination from "MultiportFromBank.lf" -reactor Source(bank_index(0)) { +reactor Source(bank_index = 0) { output out reaction(startup) -> out {= out.set(self.bank_index) =} diff --git a/test/Python/src/multiport/MultiportFromHierarchy.lf b/test/Python/src/multiport/MultiportFromHierarchy.lf index dc7a9a2f68..4116587f01 100644 --- a/test/Python/src/multiport/MultiportFromHierarchy.lf +++ b/test/Python/src/multiport/MultiportFromHierarchy.lf @@ -8,7 +8,7 @@ target Python { reactor Source { timer t(0, 200 msec) output[4] out - state s(0) + state s = 0 reaction(t) -> out {= for port in out: @@ -18,7 +18,7 @@ reactor Source { } reactor Destination { - state s(6) + state s = 6 input[4] _in reaction(_in) {= diff --git a/test/Python/src/multiport/MultiportFromReaction.lf b/test/Python/src/multiport/MultiportFromReaction.lf index f005711683..05b280b126 100644 --- a/test/Python/src/multiport/MultiportFromReaction.lf +++ b/test/Python/src/multiport/MultiportFromReaction.lf @@ -4,8 +4,8 @@ target Python { fast: true } -reactor Destination(width(1)) { - state s(6) +reactor Destination(width = 1) { + state s = 6 input[width] _in reaction(_in) {= @@ -31,7 +31,7 @@ reactor Destination(width(1)) { main reactor MultiportFromReaction { timer t(0, 200 msec) - state s(0) + state s = 0 b = new Destination(width = 4) reaction(t) -> b._in {= diff --git a/test/Python/src/multiport/MultiportIn.lf b/test/Python/src/multiport/MultiportIn.lf index 146dfa6727..35bdee7341 100644 --- a/test/Python/src/multiport/MultiportIn.lf +++ b/test/Python/src/multiport/MultiportIn.lf @@ -8,7 +8,7 @@ target Python { reactor Source { timer t(0, 200 msec) output out - state s(0) + state s = 0 reaction(t) -> out {= out.set(self.s) @@ -24,7 +24,7 @@ reactor Computation { } reactor Destination { - state s(0) + state s = 0 input[4] _in reaction(_in) {= diff --git a/test/Python/src/multiport/MultiportInParameterized.lf b/test/Python/src/multiport/MultiportInParameterized.lf index c4906d7a9c..28b15808e3 100644 --- a/test/Python/src/multiport/MultiportInParameterized.lf +++ b/test/Python/src/multiport/MultiportInParameterized.lf @@ -8,7 +8,7 @@ target Python { reactor Source { timer t(0, 200 msec) output out - state s(0) + state s = 0 reaction(t) -> out {= out.set(self.s) @@ -23,8 +23,8 @@ reactor Computation { reaction(_in) -> out {= out.set(_in.value) =} } -reactor Destination(width(1)) { - state s(0) +reactor Destination(width = 1) { + state s = 0 input[width] _in reaction(_in) {= diff --git a/test/Python/src/multiport/MultiportMutableInput.lf b/test/Python/src/multiport/MultiportMutableInput.lf index d17e1cd8bf..f931900ef7 100644 --- a/test/Python/src/multiport/MultiportMutableInput.lf +++ b/test/Python/src/multiport/MultiportMutableInput.lf @@ -12,7 +12,7 @@ reactor Source { =} } -reactor Print(scale(1)) { # The scale parameter is just for testing. +reactor Print(scale = 1) { # The scale parameter is just for testing. input[2] _in reaction(_in) {= @@ -26,7 +26,7 @@ reactor Print(scale(1)) { # The scale parameter is just for testing. =} } -reactor Scale(scale(2)) { +reactor Scale(scale = 2) { mutable input[2] _in output[2] out diff --git a/test/Python/src/multiport/MultiportMutableInputArray.lf b/test/Python/src/multiport/MultiportMutableInputArray.lf index 76fa5203a6..9efa3eeebf 100644 --- a/test/Python/src/multiport/MultiportMutableInputArray.lf +++ b/test/Python/src/multiport/MultiportMutableInputArray.lf @@ -13,7 +13,7 @@ reactor Source { =} } -reactor Print(scale(1)) { # The scale parameter is just for testing. +reactor Print(scale = 1) { # The scale parameter is just for testing. input[2] _in reaction(_in) {= @@ -25,7 +25,7 @@ reactor Print(scale(1)) { # The scale parameter is just for testing. =} } -reactor Scale(scale(2)) { +reactor Scale(scale = 2) { mutable input[2] _in output[2] out diff --git a/test/Python/src/multiport/MultiportOut.lf b/test/Python/src/multiport/MultiportOut.lf index 0a48f355e6..3d29c5188a 100644 --- a/test/Python/src/multiport/MultiportOut.lf +++ b/test/Python/src/multiport/MultiportOut.lf @@ -7,7 +7,7 @@ target Python { reactor Source { timer t(0, 200 msec) output[4] out - state s(0) + state s = 0 reaction(t) -> out {= for port in out: @@ -25,7 +25,7 @@ reactor Computation { } reactor Destination { - state s(0) + state s = 0 input[4] _in reaction(_in) {= diff --git a/test/Python/src/multiport/MultiportToBank.lf b/test/Python/src/multiport/MultiportToBank.lf index 61b1cabc8b..12d68d6662 100644 --- a/test/Python/src/multiport/MultiportToBank.lf +++ b/test/Python/src/multiport/MultiportToBank.lf @@ -13,9 +13,9 @@ reactor Source { =} } -reactor Destination(bank_index(0)) { +reactor Destination(bank_index = 0) { input _in - state received(0) + state received = 0 reaction(_in) {= print("Destination " + str(self.bank_index) + " received " + str(_in.value)) diff --git a/test/Python/src/multiport/MultiportToBankAfter.lf b/test/Python/src/multiport/MultiportToBankAfter.lf index aeb8a9d0d7..85fc91e513 100644 --- a/test/Python/src/multiport/MultiportToBankAfter.lf +++ b/test/Python/src/multiport/MultiportToBankAfter.lf @@ -7,9 +7,9 @@ target Python { import Source from "MultiportToBank.lf" -reactor Destination(bank_index(0)) { +reactor Destination(bank_index = 0) { input _in - state received(false) + state received = False reaction(_in) {= print("Destination {:d} received {:d}.".format(self.bank_index, _in.value)) diff --git a/test/Python/src/multiport/MultiportToBankHierarchy.lf b/test/Python/src/multiport/MultiportToBankHierarchy.lf index b36ae9b40b..f70fa8ad92 100644 --- a/test/Python/src/multiport/MultiportToBankHierarchy.lf +++ b/test/Python/src/multiport/MultiportToBankHierarchy.lf @@ -7,9 +7,9 @@ target Python { import Source from "MultiportToBank.lf" -reactor Destination(bank_index(0)) { +reactor Destination(bank_index = 0) { input _in - state received(false) + state received = False reaction(_in) {= print("Destination {:d} received {:d}.\n".format(self.bank_index, _in.value)) diff --git a/test/Python/src/multiport/MultiportToHierarchy.lf b/test/Python/src/multiport/MultiportToHierarchy.lf index 6dbf76775f..2a9e25a5e6 100644 --- a/test/Python/src/multiport/MultiportToHierarchy.lf +++ b/test/Python/src/multiport/MultiportToHierarchy.lf @@ -9,7 +9,7 @@ target Python { reactor Source { timer t(0, 200 msec) output[4] out - state s(0) + state s = 0 reaction(t) -> out {= for port in out: @@ -18,8 +18,8 @@ reactor Source { =} } -reactor Destination(width(4)) { - state s(6) +reactor Destination(width = 4) { + state s = 6 input[width] _in reaction(_in) {= @@ -42,7 +42,7 @@ reactor Destination(width(4)) { =} } -reactor Container(width(4)) { +reactor Container(width = 4) { input[width] _in dst = new Destination() _in -> dst._in diff --git a/test/Python/src/multiport/MultiportToMultiport.lf b/test/Python/src/multiport/MultiportToMultiport.lf index 6fd4bbb231..b16e832e7e 100644 --- a/test/Python/src/multiport/MultiportToMultiport.lf +++ b/test/Python/src/multiport/MultiportToMultiport.lf @@ -6,10 +6,10 @@ target Python { import Destination from "MultiportToHierarchy.lf" -reactor Source(width(1)) { +reactor Source(width = 1) { timer t(0, 200 msec) output[width] out - state s(0) + state s = 0 reaction(t) -> out {= for i in range(len(out)): diff --git a/test/Python/src/multiport/MultiportToMultiport2.lf b/test/Python/src/multiport/MultiportToMultiport2.lf index 5b06d8d7d8..cb6da65f35 100644 --- a/test/Python/src/multiport/MultiportToMultiport2.lf +++ b/test/Python/src/multiport/MultiportToMultiport2.lf @@ -1,7 +1,7 @@ # Test multiport to multiport connections. See also MultiportToMultiport. target Python -reactor Source(width(2)) { +reactor Source(width = 2) { output[width] out reaction(startup) -> out {= @@ -10,7 +10,7 @@ reactor Source(width(2)) { =} } -reactor Destination(width(2)) { +reactor Destination(width = 2) { input[width] _in reaction(_in) {= diff --git a/test/Python/src/multiport/MultiportToMultiport2After.lf b/test/Python/src/multiport/MultiportToMultiport2After.lf index d8d85805ce..0e7ee9aef2 100644 --- a/test/Python/src/multiport/MultiportToMultiport2After.lf +++ b/test/Python/src/multiport/MultiportToMultiport2After.lf @@ -3,7 +3,7 @@ target Python import Source from "MultiportToMultiport2.lf" -reactor Destination(width(2)) { +reactor Destination(width = 2) { input[width] _in reaction(_in) {= diff --git a/test/Python/src/multiport/MultiportToMultiportArray.lf b/test/Python/src/multiport/MultiportToMultiportArray.lf index e22f3541b0..5eb043570d 100644 --- a/test/Python/src/multiport/MultiportToMultiportArray.lf +++ b/test/Python/src/multiport/MultiportToMultiportArray.lf @@ -8,7 +8,7 @@ target Python { reactor Source { timer t(0, 200 msec) output[2] out - state s(0) + state s = 0 reaction(t) -> out {= for port in out: @@ -18,7 +18,7 @@ reactor Source { } reactor Destination { - state s(15) + state s = 15 input[2] _in reaction(_in) {= diff --git a/test/Python/src/multiport/MultiportToMultiportParameter.lf b/test/Python/src/multiport/MultiportToMultiportParameter.lf index 933abfd846..b47905bb6e 100644 --- a/test/Python/src/multiport/MultiportToMultiportParameter.lf +++ b/test/Python/src/multiport/MultiportToMultiportParameter.lf @@ -7,7 +7,7 @@ target Python { import Source from "MultiportToMultiport.lf" import Destination from "MultiportToHierarchy.lf" -main reactor MultiportToMultiportParameter(width(4)) { +main reactor MultiportToMultiportParameter(width = 4) { a = new Source(width = width) b = new Destination(width = width) a.out -> b._in diff --git a/test/Python/src/multiport/MultiportToPort.lf b/test/Python/src/multiport/MultiportToPort.lf index 52bb7b7c67..ebfbf81991 100644 --- a/test/Python/src/multiport/MultiportToPort.lf +++ b/test/Python/src/multiport/MultiportToPort.lf @@ -15,9 +15,9 @@ reactor Source { =} } -reactor Destination(expected(0)) { +reactor Destination(expected = 0) { input _in - state received(false) + state received = False reaction(_in) {= print("Received: ", _in.value) diff --git a/test/Python/src/multiport/MultiportToReaction.lf b/test/Python/src/multiport/MultiportToReaction.lf index 6d03ba28ee..b45ee9ac9e 100644 --- a/test/Python/src/multiport/MultiportToReaction.lf +++ b/test/Python/src/multiport/MultiportToReaction.lf @@ -4,9 +4,9 @@ target Python { fast: true } -reactor Source(width(1)) { +reactor Source(width = 1) { timer t(0, 200 msec) - state s(0) + state s = 0 output[width] out reaction(t) -> out {= @@ -17,7 +17,7 @@ reactor Source(width(1)) { } main reactor { - state s(6) + state s = 6 b = new Source(width = 4) reaction(b.out) {= diff --git a/test/Python/src/multiport/NestedBanks.lf b/test/Python/src/multiport/NestedBanks.lf index 8e49b76aef..1e863043e6 100644 --- a/test/Python/src/multiport/NestedBanks.lf +++ b/test/Python/src/multiport/NestedBanks.lf @@ -13,13 +13,13 @@ main reactor { (a.x)+ -> c.z, d.u, e.t } -reactor A(bank_index(0)) { +reactor A(bank_index = 0) { output[4] x b = new[2] B(a_bank_index = bank_index) b.y -> x } -reactor B(a_bank_index(0), bank_index(0)) { +reactor B(a_bank_index = 0, bank_index = 0) { output[2] y reaction(startup) -> y {= @@ -29,7 +29,7 @@ reactor B(a_bank_index(0), bank_index(0)) { =} } -reactor C(bank_index(0)) { +reactor C(bank_index = 0) { input[2] z f = new F(c_bank_index = bank_index) g = new G(c_bank_index = bank_index) @@ -57,7 +57,7 @@ reactor E { =} } -reactor F(c_bank_index(0)) { +reactor F(c_bank_index = 0) { input w reaction(w) {= @@ -68,7 +68,7 @@ reactor F(c_bank_index(0)) { =} } -reactor G(c_bank_index(0)) { +reactor G(c_bank_index = 0) { input s reaction(s) {= diff --git a/test/Python/src/multiport/NestedInterleavedBanks.lf b/test/Python/src/multiport/NestedInterleavedBanks.lf index 5d615a3397..9be37badcd 100644 --- a/test/Python/src/multiport/NestedInterleavedBanks.lf +++ b/test/Python/src/multiport/NestedInterleavedBanks.lf @@ -4,7 +4,7 @@ */ target Python -reactor A(bank_index(0), outer_bank_index(0)) { +reactor A(bank_index = 0, outer_bank_index = 0) { output[2] p reaction(startup) -> p {= @@ -14,7 +14,7 @@ reactor A(bank_index(0), outer_bank_index(0)) { =} } -reactor B(bank_index(0)) { +reactor B(bank_index = 0) { output[4] q a = new[2] A(outer_bank_index = bank_index) interleaved (a.p) -> q diff --git a/test/Python/src/multiport/ReactionsToNested.lf b/test/Python/src/multiport/ReactionsToNested.lf index 9870cb1b68..2c207dffb0 100644 --- a/test/Python/src/multiport/ReactionsToNested.lf +++ b/test/Python/src/multiport/ReactionsToNested.lf @@ -4,9 +4,9 @@ target Python { timeout: 1 sec } -reactor T(expected(0)) { +reactor T(expected = 0) { input z - state received(false) + state received = False reaction(z) {= print(f"T received {z.value}.") diff --git a/test/Python/src/target/AfterNoTypes.lf b/test/Python/src/target/AfterNoTypes.lf index 3ff3983da5..44aaa89c73 100644 --- a/test/Python/src/target/AfterNoTypes.lf +++ b/test/Python/src/target/AfterNoTypes.lf @@ -13,8 +13,8 @@ reactor Foo { } reactor Print { - state expected_time(10 msec) - state received(0) + state expected_time = 10 msec + state received = 0 input x reaction(x) {= diff --git a/test/Rust/src/ActionDelay.lf b/test/Rust/src/ActionDelay.lf index 8612eabbb2..75c7ab909f 100644 --- a/test/Rust/src/ActionDelay.lf +++ b/test/Rust/src/ActionDelay.lf @@ -4,7 +4,7 @@ target Rust main reactor ActionDelay { logical action act0 logical action act1(100 msec) - state count: u32(0) + state count: u32 = 0 reaction(startup) -> act0, act1 {= ctx.schedule(act0, after!(100 ms)); diff --git a/test/Rust/src/ActionImplicitDelay.lf b/test/Rust/src/ActionImplicitDelay.lf index 1278472528..9a3a900d93 100644 --- a/test/Rust/src/ActionImplicitDelay.lf +++ b/test/Rust/src/ActionImplicitDelay.lf @@ -3,7 +3,7 @@ target Rust main reactor ActionImplicitDelay { logical action act(40 msec) - state count: u64(1) + state count: u64 = 1 reaction(startup) -> act {= ctx.schedule(act, Asap); =} diff --git a/test/Rust/src/ActionIsPresent.lf b/test/Rust/src/ActionIsPresent.lf index efe6158e43..3a1e556c5a 100644 --- a/test/Rust/src/ActionIsPresent.lf +++ b/test/Rust/src/ActionIsPresent.lf @@ -3,8 +3,8 @@ target Rust main reactor ActionIsPresent { logical action a - state success: bool(false) - state tried: bool(false) + state success: bool = false + state tried: bool = false reaction(startup, a) -> a {= if !ctx.is_present(a) { diff --git a/test/Rust/src/ActionScheduleMicrostep.lf b/test/Rust/src/ActionScheduleMicrostep.lf index 5ded693d2c..53bf8781b9 100644 --- a/test/Rust/src/ActionScheduleMicrostep.lf +++ b/test/Rust/src/ActionScheduleMicrostep.lf @@ -3,7 +3,7 @@ target Rust main reactor ActionScheduleMicrostep { logical action act - state count: u32(1) + state count: u32 = 1 reaction(startup) -> act {= assert_tag_is!(ctx, T0); diff --git a/test/Rust/src/ActionValues.lf b/test/Rust/src/ActionValues.lf index 635516c3d0..007da1fb5a 100644 --- a/test/Rust/src/ActionValues.lf +++ b/test/Rust/src/ActionValues.lf @@ -2,8 +2,8 @@ target Rust main reactor ActionValues { - state r1done: bool(false) - state r2done: bool(false) + state r1done: bool = false + state r2done: bool = false logical action act(100 msec): i32 reaction(startup) -> act {= diff --git a/test/Rust/src/ActionValuesCleanup.lf b/test/Rust/src/ActionValuesCleanup.lf index 615bd68a90..f4bab7a2b5 100644 --- a/test/Rust/src/ActionValuesCleanup.lf +++ b/test/Rust/src/ActionValuesCleanup.lf @@ -20,7 +20,7 @@ main reactor ActionValuesCleanup { =} logical action act: FooDrop - state count: u32(0) + state count: u32 = 0 reaction(startup) -> act {= ctx.schedule_with_v(act, Some(FooDrop { }), Asap) diff --git a/test/Rust/src/CtorParamDefault.lf b/test/Rust/src/CtorParamDefault.lf index 2a4f4b8074..a7d2d868bc 100644 --- a/test/Rust/src/CtorParamDefault.lf +++ b/test/Rust/src/CtorParamDefault.lf @@ -1,7 +1,7 @@ target Rust -reactor Print(value: i32(42)) { - state v: i32(value) +reactor Print(value: i32 = 42) { + state v: i32 = value reaction(startup) {= assert_eq!(42, self.v); diff --git a/test/Rust/src/CtorParamMixed.lf b/test/Rust/src/CtorParamMixed.lf index cb8536fe0a..5de7a3dc4c 100644 --- a/test/Rust/src/CtorParamMixed.lf +++ b/test/Rust/src/CtorParamMixed.lf @@ -1,13 +1,13 @@ target Rust reactor Print( - value: i32(42), - name: String({= "xxx".into() =}), - other: bool(false) + value: i32 = 42, + name: String = {= "xxx".into() =}, + other: bool = false ) { - state value(value) - state name(name) - state other(other) + state value = value + state name = name + state other = other reaction(startup) {= assert_eq!(42, self.value); diff --git a/test/Rust/src/CtorParamSimple.lf b/test/Rust/src/CtorParamSimple.lf index 8594adde3d..bbf38c04a0 100644 --- a/test/Rust/src/CtorParamSimple.lf +++ b/test/Rust/src/CtorParamSimple.lf @@ -1,7 +1,7 @@ target Rust -reactor Print(value: i32(42)) { - state v: i32(value) +reactor Print(value: i32 = 42) { + state v: i32 = value reaction(startup) {= assert_eq!(self.v, 23); diff --git a/test/Rust/src/DependencyOnChildPort.lf b/test/Rust/src/DependencyOnChildPort.lf index e5c9336c11..ed8b6b2120 100644 --- a/test/Rust/src/DependencyOnChildPort.lf +++ b/test/Rust/src/DependencyOnChildPort.lf @@ -9,7 +9,7 @@ reactor Box { } main reactor { - state done: bool(false) + state done: bool = false box0 = new Box() box1 = new Box() diff --git a/test/Rust/src/DependencyUseOnLogicalAction.lf b/test/Rust/src/DependencyUseOnLogicalAction.lf index bf51944c8b..6278ec76f7 100644 --- a/test/Rust/src/DependencyUseOnLogicalAction.lf +++ b/test/Rust/src/DependencyUseOnLogicalAction.lf @@ -10,7 +10,7 @@ main reactor { timer t(0, 2 msec) - state tick: u32(0) + state tick: u32 = 0 reaction(startup) -> clock, a {= ctx.schedule(a, after!(3 ms)); // out of order on purpose diff --git a/test/Rust/src/FloatLiteral.lf b/test/Rust/src/FloatLiteral.lf index 645581c844..b1dd37ca5e 100644 --- a/test/Rust/src/FloatLiteral.lf +++ b/test/Rust/src/FloatLiteral.lf @@ -2,10 +2,10 @@ target Rust // This test verifies that floating-point literals are handled correctly. main reactor { - state N: f64(6.0221409e+23) - state charge: f64(-1.6021766E-19) - state minus_epsilon: f64(-.01e0) - state expected: f64(.964853323188E5) + state N: f64 = 6.0221409e+23 + state charge: f64 = -1.6021766E-19 + state minus_epsilon: f64 = -.01e0 + state expected: f64 = .964853323188E5 reaction(startup) {= let F = - self.N * self.charge; diff --git a/test/Rust/src/MainReactorParam.lf b/test/Rust/src/MainReactorParam.lf index 36a78d7699..0c9f189548 100644 --- a/test/Rust/src/MainReactorParam.lf +++ b/test/Rust/src/MainReactorParam.lf @@ -1,8 +1,8 @@ target Rust -main reactor(one: u64(1152921504606846976), two: u64({= 1 << 60 =})) { - state one(one) - state two(two) +main reactor(one: u64 = 1152921504606846976, two: u64 = {= 1 << 60 =}) { + state one = one + state two = two reaction(startup) {= assert_eq!(self.one, self.two); =} } diff --git a/test/Rust/src/MovingAverage.lf b/test/Rust/src/MovingAverage.lf index 228c69bd17..3dcbb69c98 100644 --- a/test/Rust/src/MovingAverage.lf +++ b/test/Rust/src/MovingAverage.lf @@ -7,7 +7,7 @@ target Rust { reactor Source { output out: f64 - state count: u32(0) + state count: u32 = 0 timer clock(0, 10 msec) reaction(clock) -> out {= @@ -17,9 +17,8 @@ reactor Source { } reactor MovingAverageImpl { - // fixme inaccessible ({=[0.0 ; 4]=}); - state delay_line: f64[4](0.0, 0.0, 0.0, 0.0) - state index: usize(0) + state delay_line: {= [f64 ; 4] =} = {= [ 0.0 ; 4 ] =} + state index: usize = 0 input in_: f64 output out: f64 @@ -39,7 +38,7 @@ reactor MovingAverageImpl { reactor Print { input in_: f64 - state count: usize(0) + state count: usize = 0 preamble {= const EXPECTED: [ f64 ; 6 ] = [0.0, 0.25, 0.75, 1.5, 2.5, 3.5]; diff --git a/test/Rust/src/NativeListsAndTimes.lf b/test/Rust/src/NativeListsAndTimes.lf index 3e378c2e43..79fd56e004 100644 --- a/test/Rust/src/NativeListsAndTimes.lf +++ b/test/Rust/src/NativeListsAndTimes.lf @@ -2,18 +2,18 @@ target Rust // This test passes if it is successfully compiled into valid target code. reactor Foo( - x: i32(0), - y: time(0), // Units are missing but not required - z(1 msec), // Type is missing but not required + x: i32 = 0, + y: time = 0, // Units are missing but not required + z = 1 msec, // Type is missing but not required p: i32[](1, 2, 3, 4), // List of integers - p2: i32[]({= vec![1] =}), // List of integers with single element + p2: i32[] = {= vec![1] =}, // List of integers with single element // todo // p2: i32[](1), // List of integers with single element p3: // i32[](), // Empty list of integers List of time values q: Vec(1 msec, 2 msec, 3 msec), g: time[](1 msec, 2 msec) // List of time values ) { - state s: time(y) // Reference to explicitly typed time parameter - state t: time(z) // Reference to implicitly typed time parameter + state s: time = y // Reference to explicitly typed time parameter + state t: time = z // Reference to implicitly typed time parameter state v: bool // Uninitialized boolean state variable state w: time // Uninitialized time state variable timer tick(0) // Units missing but not required @@ -29,7 +29,7 @@ reactor Foo( */ // state baz(p); // Implicit type i32[] fixme this interplays badly with // syntax for array init Implicit type time - state period(z) + state period = z state times: Vec>(q, g) // a list of lists /** diff --git a/test/Rust/src/PortConnectionInSelfInChild.lf b/test/Rust/src/PortConnectionInSelfInChild.lf index c72b4edae0..7fd66783f1 100644 --- a/test/Rust/src/PortConnectionInSelfInChild.lf +++ b/test/Rust/src/PortConnectionInSelfInChild.lf @@ -3,7 +3,7 @@ target Rust reactor Child { input inp: i32 - state done: bool(false) + state done: bool = false reaction(inp) {= assert_eq!(ctx.get(inp), Some(76600)); diff --git a/test/Rust/src/PortConnectionInSelfOutSelf.lf b/test/Rust/src/PortConnectionInSelfOutSelf.lf index 6d012b3d62..2e33b0e498 100644 --- a/test/Rust/src/PortConnectionInSelfOutSelf.lf +++ b/test/Rust/src/PortConnectionInSelfOutSelf.lf @@ -18,7 +18,7 @@ reactor TestCase { reactor Sink { input inp: i32 - state done: bool(false) + state done: bool = false reaction(inp) {= assert_eq!(ctx.get(inp), Some(76600)); diff --git a/test/Rust/src/PortConnectionOutChildOutSelf.lf b/test/Rust/src/PortConnectionOutChildOutSelf.lf index 54adbb56eb..4d58153d97 100644 --- a/test/Rust/src/PortConnectionOutChildOutSelf.lf +++ b/test/Rust/src/PortConnectionOutChildOutSelf.lf @@ -19,7 +19,7 @@ reactor Parent { reactor Sink { input inp: i32 - state done: bool(false) + state done: bool = false reaction(inp) {= assert_eq!(ctx.get(inp), Some(76600)); @@ -33,7 +33,7 @@ reactor Sink { main reactor { parent = new Parent() - state done: bool(false) + state done: bool = false reaction(parent.out) {= assert_eq!(ctx.get(parent__out), Some(76600)); diff --git a/test/Rust/src/PortRefCleanup.lf b/test/Rust/src/PortRefCleanup.lf index 65a5b415cf..30573ceee9 100644 --- a/test/Rust/src/PortRefCleanup.lf +++ b/test/Rust/src/PortRefCleanup.lf @@ -14,8 +14,8 @@ main reactor { timer t1(0) timer t2(15 msec) - state reaction_num: u32(0) - state done: bool(false) + state reaction_num: u32 = 0 + state done: bool = false reaction(t1) -> boxr.inp {= ctx.set(boxr__inp, 150); diff --git a/test/Rust/src/PortValueCleanup.lf b/test/Rust/src/PortValueCleanup.lf index be837c2908..9a11372cfb 100644 --- a/test/Rust/src/PortValueCleanup.lf +++ b/test/Rust/src/PortValueCleanup.lf @@ -11,8 +11,8 @@ reactor Sink { timer t2(15 msec) input in: u32 - state reaction_num: u32(0) - state done: bool(false) + state reaction_num: u32 = 0 + state done: bool = false reaction(in, t2) {= if self.reaction_num == 0 { diff --git a/test/Rust/src/ReservedKeywords.lf b/test/Rust/src/ReservedKeywords.lf index f1facfe8ea..5d30ba63a4 100644 --- a/test/Rust/src/ReservedKeywords.lf +++ b/test/Rust/src/ReservedKeywords.lf @@ -8,14 +8,14 @@ reactor box { in -> struct - state foo: bool(true) // not escaped + state foo: bool = true // not escaped reaction(in) {= ctx.get(r#in); =} } -main reactor ReservedKeywords(struct: u32(0)) { +main reactor ReservedKeywords(struct: u32 = 0) { box = new box() timer t1(0) diff --git a/test/Rust/src/StateInitializerVisibility.lf b/test/Rust/src/StateInitializerVisibility.lf index cef728ccfa..d192f0ca98 100644 --- a/test/Rust/src/StateInitializerVisibility.lf +++ b/test/Rust/src/StateInitializerVisibility.lf @@ -3,8 +3,8 @@ target Rust main reactor { - state foo: u32(123) - state x: u32({= *&foo =}) + state foo: u32 = 123 + state x: u32 = {= *&foo =} reaction(startup) {= assert_eq!(self.x, self.foo); diff --git a/test/Rust/src/Stop.lf b/test/Rust/src/Stop.lf index d2f380f634..c43f8c30ff 100644 --- a/test/Rust/src/Stop.lf +++ b/test/Rust/src/Stop.lf @@ -13,13 +13,13 @@ target Rust { * @param break_interval: Determines how long the reactor should take a break * after sending take_a_break_after messages. */ -reactor Sender(take_a_break_after: u32(10), break_interval: time(400 msec)) { +reactor Sender(take_a_break_after: u32 = 10, break_interval: time = 400 msec) { output out: u32 logical action act - state sent_messages: u32(0) + state sent_messages: u32 = 0 - state take_a_break_after(take_a_break_after) - state break_interval(break_interval) + state take_a_break_after = take_a_break_after + state break_interval = break_interval reaction(startup, act) -> act, out {= // Send a message on out @@ -40,7 +40,7 @@ reactor Sender(take_a_break_after: u32(10), break_interval: time(400 msec)) { reactor Consumer { input in_: u32 - state reaction_invoked_correctly: bool(false) + state reaction_invoked_correctly: bool = false reaction(in_) {= let current_tag = ctx.get_tag(); diff --git a/test/Rust/src/StopIdempotence.lf b/test/Rust/src/StopIdempotence.lf index ce9d17514f..1f79347758 100644 --- a/test/Rust/src/StopIdempotence.lf +++ b/test/Rust/src/StopIdempotence.lf @@ -4,7 +4,7 @@ target Rust { } main reactor StopIdempotence { - state count: u32(0) + state count: u32 = 0 reaction(startup) {= ctx.request_stop(Asap); // requested for (T0, 1) diff --git a/test/Rust/src/StopTimeoutExact.lf b/test/Rust/src/StopTimeoutExact.lf index 37e9616fb7..2b9eb85cda 100644 --- a/test/Rust/src/StopTimeoutExact.lf +++ b/test/Rust/src/StopTimeoutExact.lf @@ -9,7 +9,7 @@ target Rust { main reactor StopTimeoutExact { timer t(0, 10 msec) // the fifth triggering will coincide with the timeout - state reacted_on_shutdown: bool(false) + state reacted_on_shutdown: bool = false reaction(t) {= if ctx.get_tag() == tag!(T0 + 50 ms) { diff --git a/test/Rust/src/StructAsState.lf b/test/Rust/src/StructAsState.lf index 955affcc8b..9b35e7a3fb 100644 --- a/test/Rust/src/StructAsState.lf +++ b/test/Rust/src/StructAsState.lf @@ -15,7 +15,7 @@ main reactor StructAsState { * - state s: Hello(name: "Earth".into(), value: 42); * - state s: Hello { name: "Earth".into(), value: 42 }; */ - state s: Hello({= Hello { name: "Earth".into(), value: 42 } =}) + state s: Hello = {= Hello { name: "Earth".into(), value: 42 } =} reaction(startup) {= println!("State s.name=\"{}\", s.value={}.", self.s.name, self.s.value); diff --git a/test/Rust/src/StructAsType.lf b/test/Rust/src/StructAsType.lf index 76cee15f8e..0c8ed51456 100644 --- a/test/Rust/src/StructAsType.lf +++ b/test/Rust/src/StructAsType.lf @@ -18,9 +18,9 @@ reactor Source { =} } -reactor Print(expected: i32(42)) { // expected parameter is for testing. +reactor Print(expected: i32 = 42) { // expected parameter is for testing. input inp: {= super::source::Hello =} - state expected: i32(expected) + state expected: i32 = expected reaction(inp) {= ctx.use_ref_opt(inp, |hello| { diff --git a/test/Rust/src/TimeState.lf b/test/Rust/src/TimeState.lf index bbd80f730f..efffb00c2f 100644 --- a/test/Rust/src/TimeState.lf +++ b/test/Rust/src/TimeState.lf @@ -1,7 +1,7 @@ target Rust reactor Foo { - state baz: time(500 msec) + state baz: time = 500 msec reaction(startup) {= assert_eq!(500, self.baz.as_millis()); =} } diff --git a/test/Rust/src/TimerDefaults.lf b/test/Rust/src/TimerDefaults.lf index 3de189b62b..bbc920bf4a 100644 --- a/test/Rust/src/TimerDefaults.lf +++ b/test/Rust/src/TimerDefaults.lf @@ -1,7 +1,7 @@ target Rust main reactor TimerDefaults { - state i: i32(0) + state i: i32 = 0 timer t reaction(t) {= diff --git a/test/Rust/src/TimerIsPresent.lf b/test/Rust/src/TimerIsPresent.lf index 84327c06a6..1e87c11f51 100644 --- a/test/Rust/src/TimerIsPresent.lf +++ b/test/Rust/src/TimerIsPresent.lf @@ -8,8 +8,8 @@ main reactor { timer b(1 msec, 5 msec) timer c(1 msec) - state success: bool(false) - state tick: u32(0) + state success: bool = false + state tick: u32 = 0 reaction(startup, a, b, c) {= match self.tick { diff --git a/test/Rust/src/TimerPeriodic.lf b/test/Rust/src/TimerPeriodic.lf index fc62900e7c..c6dce10a73 100644 --- a/test/Rust/src/TimerPeriodic.lf +++ b/test/Rust/src/TimerPeriodic.lf @@ -3,7 +3,7 @@ target Rust { } main reactor TimerPeriodic { - state i: i32(0) + state i: i32 = 0 timer t2(0, 3 msec) reaction(t2) {= diff --git a/test/Rust/src/Timers.lf b/test/Rust/src/Timers.lf index 1043aad497..1ef5fc0ae5 100644 --- a/test/Rust/src/Timers.lf +++ b/test/Rust/src/Timers.lf @@ -6,7 +6,7 @@ target Rust { main reactor Timers { timer t(0, 1 sec) timer t2(0, 2 sec) - state counter: i32(0) + state counter: i32 = 0 reaction(t2) {= self.counter += 2; =} diff --git a/test/Rust/src/TypeVarLengthList.lf b/test/Rust/src/TypeVarLengthList.lf index 9dbf082f1f..37ff36df56 100644 --- a/test/Rust/src/TypeVarLengthList.lf +++ b/test/Rust/src/TypeVarLengthList.lf @@ -3,7 +3,7 @@ target Rust // this thing must compile needs to be modified when // https://github.com/lf-lang/lingua-franca/discussions/492 is implemented main reactor TypeVarLengthList { - state l0: i32[]({= Vec::new() =}) // generates l0: Vec::new() + state l0: i32[] = {= Vec::new() =} // generates l0: Vec::new() // generates l1: vec![1, 2] /** * - state l2: i32[](1); // generates l2: 1 // doesn't compile... diff --git a/test/Rust/src/concurrent/AsyncCallback.lf b/test/Rust/src/concurrent/AsyncCallback.lf index 053d8ae33d..dccbcf37c0 100644 --- a/test/Rust/src/concurrent/AsyncCallback.lf +++ b/test/Rust/src/concurrent/AsyncCallback.lf @@ -4,17 +4,17 @@ target Rust { cargo-features: ["cli"] } -main reactor AsyncCallback(period: time(10 msec)) { +main reactor AsyncCallback(period: time = 10 msec) { preamble {= use std::thread; =} timer t(0, period) state thread: Option> - state expected_time: time(period) - state period: time(period) - state toggle: bool(false) + state expected_time: time = period + state period: time = period + state toggle: bool = false physical action act - state i: u32(0) + state i: u32 = 0 reaction(t) -> act {= let act = act.clone(); diff --git a/test/Rust/src/generics/CtorParamGeneric.lf b/test/Rust/src/generics/CtorParamGeneric.lf index 93646e1746..1c8b1cab24 100644 --- a/test/Rust/src/generics/CtorParamGeneric.lf +++ b/test/Rust/src/generics/CtorParamGeneric.lf @@ -2,10 +2,10 @@ target Rust reactor Generic<{= T: Default + Eq + Sync + std::fmt::Debug =}>( - value: T({= Default::default() =}) + value: T = {= Default::default() =} ) { input in: T - state v: T(value) + state v: T = value reaction(in) {= ctx.use_ref_opt(r#in, |i| { diff --git a/test/Rust/src/generics/CtorParamGenericInst.lf b/test/Rust/src/generics/CtorParamGenericInst.lf index 63c38b2e16..86f696185c 100644 --- a/test/Rust/src/generics/CtorParamGenericInst.lf +++ b/test/Rust/src/generics/CtorParamGenericInst.lf @@ -5,10 +5,10 @@ target Rust reactor Generic2< {= T: Default + Eq + Sync + std::fmt::Debug + Send + 'static =} >( - value: T({= Default::default() =}) + value: T = {= Default::default() =} ) { input in: T - state v: T(value) + state v: T = value reaction(in) {= ctx.use_ref_opt(r#in, |i| { @@ -21,7 +21,7 @@ reactor Generic2< reactor Generic< {= T: Default + Eq + Sync + std::fmt::Debug + Copy + Send + 'static =} >( - value: T({= Default::default() =}) + value: T = {= Default::default() =} ) { input in: T diff --git a/test/Rust/src/generics/GenericReactor.lf b/test/Rust/src/generics/GenericReactor.lf index 0bf0675c02..3190524a5b 100644 --- a/test/Rust/src/generics/GenericReactor.lf +++ b/test/Rust/src/generics/GenericReactor.lf @@ -9,7 +9,7 @@ reactor Box<{= T: Sync =}> { } main reactor { - state done: bool(false) + state done: bool = false box0 = new Box() box1 = new Box() diff --git a/test/Rust/src/multiport/ConnectionToSelfBank.lf b/test/Rust/src/multiport/ConnectionToSelfBank.lf index cbdb4ffa5a..c39933de9b 100644 --- a/test/Rust/src/multiport/ConnectionToSelfBank.lf +++ b/test/Rust/src/multiport/ConnectionToSelfBank.lf @@ -1,10 +1,10 @@ target Rust -reactor Node(bank_index: usize(0), num_nodes: usize(4)) { +reactor Node(bank_index: usize = 0, num_nodes: usize = 4) { input[num_nodes] in: usize output out: usize - state bank_index(bank_index) - state num_nodes(num_nodes) + state bank_index = bank_index + state num_nodes = num_nodes reaction(startup) -> out {= ctx.set(out, self.bank_index); =} @@ -15,7 +15,7 @@ reactor Node(bank_index: usize(0), num_nodes: usize(4)) { =} } -main reactor(num_nodes: usize(4)) { +main reactor(num_nodes: usize = 4) { nodes = new[num_nodes] Node(num_nodes = num_nodes) (nodes.out)+ -> nodes.in } diff --git a/test/Rust/src/multiport/ConnectionToSelfMultiport.lf b/test/Rust/src/multiport/ConnectionToSelfMultiport.lf index b29efc3fdf..6042ad7af5 100644 --- a/test/Rust/src/multiport/ConnectionToSelfMultiport.lf +++ b/test/Rust/src/multiport/ConnectionToSelfMultiport.lf @@ -1,10 +1,10 @@ // test a connection from a multiport to another multiport of the same reactor target Rust -reactor Node(num_nodes: usize(4)) { +reactor Node(num_nodes: usize = 4) { input[num_nodes] in: usize output[num_nodes] out: usize - state num_nodes(num_nodes) + state num_nodes = num_nodes reaction(startup) -> out {= for (i, out) in out.into_iter().enumerate() { @@ -19,7 +19,7 @@ reactor Node(num_nodes: usize(4)) { =} } -main reactor(num_nodes: usize(4)) { +main reactor(num_nodes: usize = 4) { nodes = new Node(num_nodes = num_nodes) nodes.out -> nodes.in // todo: (nodes.out)+ -> nodes.in; } diff --git a/test/Rust/src/multiport/CycledLhs_SelfLoop.lf b/test/Rust/src/multiport/CycledLhs_SelfLoop.lf index 5248dcd11e..925fafce21 100644 --- a/test/Rust/src/multiport/CycledLhs_SelfLoop.lf +++ b/test/Rust/src/multiport/CycledLhs_SelfLoop.lf @@ -8,7 +8,7 @@ reactor Test { output out: u32 input[2] in: u32 logical action act: u32 - state last: u32(1) + state last: u32 = 1 reaction(startup) -> act {= ctx.schedule_with_v(act, Some(1), after!(1 us)); diff --git a/test/Rust/src/multiport/CycledLhs_Single.lf b/test/Rust/src/multiport/CycledLhs_Single.lf index 289bd85159..2fdd5c605c 100644 --- a/test/Rust/src/multiport/CycledLhs_Single.lf +++ b/test/Rust/src/multiport/CycledLhs_Single.lf @@ -8,7 +8,7 @@ reactor Test { output[2] out: u32 input[4] in: u32 logical action act: {= (u32, u32) =} - state last: u32(1) + state last: u32 = 1 reaction(startup) -> act {= ctx.schedule_with_v(act, Some((0, 1)), after!(1 us)); diff --git a/test/Rust/src/multiport/FullyConnected.lf b/test/Rust/src/multiport/FullyConnected.lf index 8dc87882ed..c4cd24f908 100644 --- a/test/Rust/src/multiport/FullyConnected.lf +++ b/test/Rust/src/multiport/FullyConnected.lf @@ -1,16 +1,16 @@ // test iterated connection on the left target Rust -reactor Left(bank_index: usize(0)) { +reactor Left(bank_index: usize = 0) { output out: usize - state bank_index(bank_index) + state bank_index = bank_index reaction(startup) -> out {= ctx.set(out, self.bank_index); =} } -reactor Right(bank_index: usize(0), num_nodes: usize(4)) { +reactor Right(bank_index: usize = 0, num_nodes: usize = 4) { input[num_nodes] in: usize - state num_nodes(num_nodes) + state num_nodes = num_nodes reaction(in) {= let count = r#in.iterate_set().count(); @@ -19,7 +19,7 @@ reactor Right(bank_index: usize(0), num_nodes: usize(4)) { =} } -main reactor(num_nodes: usize(4)) { +main reactor(num_nodes: usize = 4) { left = new[num_nodes] Left() right = new[num_nodes] Right(num_nodes = num_nodes) (left.out)+ -> right.in diff --git a/test/Rust/src/multiport/FullyConnectedAddressable.lf b/test/Rust/src/multiport/FullyConnectedAddressable.lf index 63aed7b840..2c8a056ed1 100644 --- a/test/Rust/src/multiport/FullyConnectedAddressable.lf +++ b/test/Rust/src/multiport/FullyConnectedAddressable.lf @@ -1,14 +1,14 @@ // In this pattern, each node can send direct messages to individual other nodes target Rust -reactor Node(bank_index: usize(0), num_nodes: usize(4)) { - state bank_index(bank_index) - state num_nodes(num_nodes) +reactor Node(bank_index: usize = 0, num_nodes: usize = 4) { + state bank_index = bank_index + state num_nodes = num_nodes input[num_nodes] inpt: usize output[num_nodes] out: usize - state received: bool(false) + state received: bool = false reaction(startup) -> out {= println!("Hello from node {}!", self.bank_index); @@ -43,7 +43,7 @@ reactor Node(bank_index: usize(0), num_nodes: usize(4)) { =} } -main reactor(num_nodes: usize(4)) { +main reactor(num_nodes: usize = 4) { nodes1 = new[num_nodes] Node(num_nodes = num_nodes) nodes1.out -> interleaved (nodes1.inpt) diff --git a/test/Rust/src/multiport/MultiportFromBank.lf b/test/Rust/src/multiport/MultiportFromBank.lf index f17a4ffa8d..6f0592ec36 100644 --- a/test/Rust/src/multiport/MultiportFromBank.lf +++ b/test/Rust/src/multiport/MultiportFromBank.lf @@ -3,14 +3,14 @@ target Rust { timeout: 2 sec } -reactor Source(bank_index: usize(0)) { +reactor Source(bank_index: usize = 0) { output out: usize - state bank_index(bank_index) + state bank_index = bank_index reaction(startup) -> out {= ctx.set(out, self.bank_index); =} } -reactor Destination(port_width: usize(2)) { +reactor Destination(port_width: usize = 2) { input[port_width] in: usize reaction(in) {= diff --git a/test/Rust/src/multiport/MultiportFromHierarchy.lf b/test/Rust/src/multiport/MultiportFromHierarchy.lf index 2745424e8c..d2af4253c4 100644 --- a/test/Rust/src/multiport/MultiportFromHierarchy.lf +++ b/test/Rust/src/multiport/MultiportFromHierarchy.lf @@ -7,7 +7,7 @@ target Rust { reactor Source { timer t(0, 200 msec) output[4] out: u32 - state s: u32(0) + state s: u32 = 0 reaction(t) -> out {= for chan in out { @@ -18,7 +18,7 @@ reactor Source { } reactor Destination { - state s: u32(6) + state s: u32 = 6 input[4] in: u32 reaction(in) {= diff --git a/test/Rust/src/multiport/MultiportIn.lf b/test/Rust/src/multiport/MultiportIn.lf index bbaeb65dda..4ebf1c56d5 100644 --- a/test/Rust/src/multiport/MultiportIn.lf +++ b/test/Rust/src/multiport/MultiportIn.lf @@ -8,7 +8,7 @@ target Rust { reactor Source { timer t(0, 200 msec) output out: u32 - state s: u32(0) + state s: u32 = 0 reaction(t) -> out {= ctx.set(out, self.s); @@ -26,7 +26,7 @@ reactor Computation { } reactor Destination { - state s: u32(0) + state s: u32 = 0 input[4] in: u32 reaction(in) {= diff --git a/test/Rust/src/multiport/MultiportOut.lf b/test/Rust/src/multiport/MultiportOut.lf index 010b3a9603..5e3b9afe77 100644 --- a/test/Rust/src/multiport/MultiportOut.lf +++ b/test/Rust/src/multiport/MultiportOut.lf @@ -6,7 +6,7 @@ target Rust { reactor Source { timer t(0, 200 msec) output[4] out: u32 - state s: u32(0) + state s: u32 = 0 reaction(t) -> out {= for i in 0..out.len() { @@ -30,7 +30,7 @@ reactor Computation { } reactor Destination { - state s: u32(0) + state s: u32 = 0 input[4] in: u32 reaction(in) {= diff --git a/test/Rust/src/multiport/MultiportToBank.lf b/test/Rust/src/multiport/MultiportToBank.lf index fcfdb8bf5d..445ab94d54 100644 --- a/test/Rust/src/multiport/MultiportToBank.lf +++ b/test/Rust/src/multiport/MultiportToBank.lf @@ -11,11 +11,11 @@ reactor Source { =} } -reactor Sink(bank_index: usize(0)) { +reactor Sink(bank_index: usize = 0) { input in: usize - state bank_index(bank_index) + state bank_index = bank_index - state asserts_done: u32(0) + state asserts_done: u32 = 0 reaction(in) {= assert_eq!(ctx.get(r#in), Some(self.bank_index)); diff --git a/test/Rust/src/multiport/MultiportToBankHierarchy.lf b/test/Rust/src/multiport/MultiportToBankHierarchy.lf index 6a7af77ebf..4d7dce445a 100644 --- a/test/Rust/src/multiport/MultiportToBankHierarchy.lf +++ b/test/Rust/src/multiport/MultiportToBankHierarchy.lf @@ -13,11 +13,11 @@ reactor Source { =} } -reactor Destination(bank_index: usize(0)) { +reactor Destination(bank_index: usize = 0) { input in: usize - state bank_index(bank_index) - state asserts_done: u32(0) + state bank_index = bank_index + state asserts_done: u32 = 0 reaction(in) {= assert_eq!(ctx.get(r#in), Some(self.bank_index)); diff --git a/test/Rust/src/multiport/MultiportToMultiport2.lf b/test/Rust/src/multiport/MultiportToMultiport2.lf index 703c498ab3..a1e2db235d 100644 --- a/test/Rust/src/multiport/MultiportToMultiport2.lf +++ b/test/Rust/src/multiport/MultiportToMultiport2.lf @@ -2,7 +2,7 @@ // disconnected) target Rust -reactor Source(width: usize(2)) { +reactor Source(width: usize = 2) { output[width] out: usize reaction(startup) -> out {= @@ -12,7 +12,7 @@ reactor Source(width: usize(2)) { =} } -reactor Destination(width: usize(2)) { +reactor Destination(width: usize = 2) { input[width] in: usize reaction(in) {= diff --git a/test/Rust/src/multiport/ReadOutputOfContainedBank.lf b/test/Rust/src/multiport/ReadOutputOfContainedBank.lf index 80103366db..21bbdf6967 100644 --- a/test/Rust/src/multiport/ReadOutputOfContainedBank.lf +++ b/test/Rust/src/multiport/ReadOutputOfContainedBank.lf @@ -2,8 +2,8 @@ // permutations. target Rust -reactor Contained(bank_index: usize(0)) { - state bank_index(bank_index) +reactor Contained(bank_index: usize = 0) { + state bank_index = bank_index output out: usize @@ -12,7 +12,7 @@ reactor Contained(bank_index: usize(0)) { main reactor { c = new[4] Contained() - state count: usize(0) + state count: usize = 0 reaction(startup) c.out {= for (i, chan) in c__out.iter().enumerate() { diff --git a/test/Rust/src/multiport/WidthWithParameter.lf b/test/Rust/src/multiport/WidthWithParameter.lf index e32f67bec5..03e4617e87 100644 --- a/test/Rust/src/multiport/WidthWithParameter.lf +++ b/test/Rust/src/multiport/WidthWithParameter.lf @@ -1,6 +1,6 @@ target Rust -reactor Some(value: usize(30)) { +reactor Some(value: usize = 30) { output[value] finished: unit reaction(startup) -> finished {= diff --git a/test/Rust/src/multiport/WriteInputOfContainedBank.lf b/test/Rust/src/multiport/WriteInputOfContainedBank.lf index cab6e80e16..f339a3aea1 100644 --- a/test/Rust/src/multiport/WriteInputOfContainedBank.lf +++ b/test/Rust/src/multiport/WriteInputOfContainedBank.lf @@ -1,11 +1,11 @@ // Test writing inputs to a contained reactor bank target Rust -reactor Contained(bank_index: usize(0)) { - state bank_index(bank_index) +reactor Contained(bank_index: usize = 0) { + state bank_index = bank_index input inpt: usize - state count: usize(0) + state count: usize = 0 reaction(inpt) {= let result = ctx.get(inpt).unwrap(); diff --git a/test/Rust/src/target/CliFeature.lf b/test/Rust/src/target/CliFeature.lf index bfa8fb5746..2ef81e76bb 100644 --- a/test/Rust/src/target/CliFeature.lf +++ b/test/Rust/src/target/CliFeature.lf @@ -4,6 +4,6 @@ target Rust { } // todo allow test framework to pass CLI arguments. -main reactor CliFeature(size: u32(4), t: time(4 sec)) { +main reactor CliFeature(size: u32 = 4, t: time = 4 sec) { reaction(startup) {= println!("success"); =} } diff --git a/test/Rust/src/target/MainParameterCanBeExpression.lf b/test/Rust/src/target/MainParameterCanBeExpression.lf index 9e62cb29cb..99ab0d7389 100644 --- a/test/Rust/src/target/MainParameterCanBeExpression.lf +++ b/test/Rust/src/target/MainParameterCanBeExpression.lf @@ -3,8 +3,8 @@ target Rust { cargo-features: ["cli"] } -main reactor(size: u32({= 1u32 << 23 =})) { - state s(size) +main reactor(size: u32 = {= 1u32 << 23 =}) { + state s = size reaction(startup) {= assert_eq!(1u32 << 23, self.s); diff --git a/test/TypeScript/src/ActionDelay.lf b/test/TypeScript/src/ActionDelay.lf index 387e0331de..5882d18962 100644 --- a/test/TypeScript/src/ActionDelay.lf +++ b/test/TypeScript/src/ActionDelay.lf @@ -4,7 +4,7 @@ target TypeScript reactor GeneratedDelay { input y_in: number output y_out: number - state y_state: number(0) + state y_state: number = 0 logical action act(100 msec) reaction(y_in) -> act {= diff --git a/test/TypeScript/src/After.lf b/test/TypeScript/src/After.lf index 24aa2cdb29..8a872103e9 100644 --- a/test/TypeScript/src/After.lf +++ b/test/TypeScript/src/After.lf @@ -13,7 +13,7 @@ reactor Foo { } reactor Print { - state expected_time: time(10 msec) + state expected_time: time = 10 msec input x: number reaction(x) {= diff --git a/test/TypeScript/src/ArrayAsParameter.lf b/test/TypeScript/src/ArrayAsParameter.lf index 7f5e272d46..1fd7e47bb3 100644 --- a/test/TypeScript/src/ArrayAsParameter.lf +++ b/test/TypeScript/src/ArrayAsParameter.lf @@ -1,9 +1,9 @@ // Source has an array as a parameter, the elements of which it passes to Print. target TypeScript -reactor Source(sequence: {= Array =}({= [0, 1, 2] =})) { +reactor Source(sequence: {= Array =} = {= [0, 1, 2] =}) { output out: number - state count: number(0) + state count: number = 0 logical action next reaction(startup, next) -> out, next {= @@ -17,7 +17,7 @@ reactor Source(sequence: {= Array =}({= [0, 1, 2] =})) { reactor Print { input x: number - state count: number(1) + state count: number = 1 reaction(x) {= console.log("Received: " + x + "."); diff --git a/test/TypeScript/src/ArrayAsType.lf b/test/TypeScript/src/ArrayAsType.lf index 651dc37f29..9e30764307 100644 --- a/test/TypeScript/src/ArrayAsType.lf +++ b/test/TypeScript/src/ArrayAsType.lf @@ -15,7 +15,7 @@ reactor Source { =} } -reactor Print(scale: number(1)) { // The scale parameter is just for testing. +reactor Print(scale: number = 1) { // The scale parameter is just for testing. input x: {= Array =} reaction(x) {= diff --git a/test/TypeScript/src/ArrayPrint.lf b/test/TypeScript/src/ArrayPrint.lf index 62312c6a8e..93b6486506 100644 --- a/test/TypeScript/src/ArrayPrint.lf +++ b/test/TypeScript/src/ArrayPrint.lf @@ -15,7 +15,7 @@ reactor Source { =} } -reactor Print(scale: number(1)) { // The scale parameter is just for testing. +reactor Print(scale: number = 1) { // The scale parameter is just for testing. input x: {= Array =} reaction(x) {= diff --git a/test/TypeScript/src/ArrayScale.lf b/test/TypeScript/src/ArrayScale.lf index 0b54e4a4ef..107ef8cf2f 100644 --- a/test/TypeScript/src/ArrayScale.lf +++ b/test/TypeScript/src/ArrayScale.lf @@ -6,7 +6,7 @@ target TypeScript import Source, Print from "ArrayPrint.lf" -reactor Scale(scale: number(2)) { +reactor Scale(scale: number = 2) { mutable input x: {= Array =} output out: {= Array =} diff --git a/test/TypeScript/src/Composition.lf b/test/TypeScript/src/Composition.lf index 35c259d967..e74bbe9aa9 100644 --- a/test/TypeScript/src/Composition.lf +++ b/test/TypeScript/src/Composition.lf @@ -4,10 +4,10 @@ target TypeScript { timeout: 5 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: number timer t(1 sec, period) - state count: number(0) + state count: number = 0 reaction(t) -> y {= count++; @@ -17,7 +17,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: number - state count: number(0) + state count: number = 0 reaction(x) {= count++; diff --git a/test/TypeScript/src/CompositionAfter.lf b/test/TypeScript/src/CompositionAfter.lf index afce86ccfe..8840298c2d 100644 --- a/test/TypeScript/src/CompositionAfter.lf +++ b/test/TypeScript/src/CompositionAfter.lf @@ -5,10 +5,10 @@ target TypeScript { timeout: 10 sec } -reactor Source(period: time(2 sec)) { +reactor Source(period: time = 2 sec) { output y: number timer t(1 sec, period) - state count: number(0) + state count: number = 0 reaction(t) -> y {= count++; @@ -18,7 +18,7 @@ reactor Source(period: time(2 sec)) { reactor Test { input x: number - state count: number(0) + state count: number = 0 reaction(x) {= count++; @@ -29,7 +29,7 @@ reactor Test { =} } -main reactor(delay: time(5 sec)) { +main reactor(delay: time = 5 sec) { s = new Source() d = new Test() s.y -> d.x after delay diff --git a/test/TypeScript/src/CountTest.lf b/test/TypeScript/src/CountTest.lf index 66737a6007..ba9e179b9b 100644 --- a/test/TypeScript/src/CountTest.lf +++ b/test/TypeScript/src/CountTest.lf @@ -6,7 +6,7 @@ import Count from "lib/Count.lf" reactor Test { input c: number - state i: number(0) + state i: number = 0 reaction(c) {= console.log("Received " + c); diff --git a/test/TypeScript/src/Deadline.lf b/test/TypeScript/src/Deadline.lf index 1d639e94f3..9f3086f1f5 100644 --- a/test/TypeScript/src/Deadline.lf +++ b/test/TypeScript/src/Deadline.lf @@ -5,10 +5,10 @@ target TypeScript { timeout: 4 sec } -reactor Source(period: time(2 sec)) { // run = "bin/Deadline -timeout 4 sec" +reactor Source(period: time = 2 sec) { // run = "bin/Deadline -timeout 4 sec" output y: number timer t(0, period) - state count: number(0) + state count: number = 0 reaction(t) -> y {= if (2 * Math.floor(count / 2) != count){ @@ -25,9 +25,9 @@ reactor Source(period: time(2 sec)) { // run = "bin/Deadline -timeout 4 sec" =} } -reactor Destination(timeout: time(1 sec)) { +reactor Destination(timeout: time = 1 sec) { input x: number - state count: number(0) + state count: number = 0 reaction(x) {= console.log("Destination receives: " + x); diff --git a/test/TypeScript/src/DeadlineHandledAbove.lf b/test/TypeScript/src/DeadlineHandledAbove.lf index 83ab279b23..ba47fd7aac 100644 --- a/test/TypeScript/src/DeadlineHandledAbove.lf +++ b/test/TypeScript/src/DeadlineHandledAbove.lf @@ -4,7 +4,7 @@ target TypeScript { timeout: 2 sec } -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { input x: number output deadline_violation: boolean @@ -19,7 +19,7 @@ reactor Deadline(threshold: time(100 msec)) { } main reactor DeadlineHandledAbove { - state violation_detected: boolean(false) + state violation_detected: boolean = false d = new Deadline(threshold = 10 msec) reaction(startup) -> d.x {= diff --git a/test/TypeScript/src/DelayInt.lf b/test/TypeScript/src/DelayInt.lf index d38e1103f5..224a7eab4a 100644 --- a/test/TypeScript/src/DelayInt.lf +++ b/test/TypeScript/src/DelayInt.lf @@ -2,7 +2,7 @@ // is a start at handling dynamic memory allocation for such actions. target TypeScript -reactor Delay(delay: time(100 msec)) { +reactor Delay(delay: time = 100 msec) { input x: number output out: number logical action a: number @@ -18,8 +18,8 @@ reactor Delay(delay: time(100 msec)) { reactor Test { input x: number - state start_time: time(0) - state received_value: boolean(false) + state start_time: time = 0 + state received_value: boolean = false reaction(startup) {= // Record the logical time at the start. diff --git a/test/TypeScript/src/DelayedAction.lf b/test/TypeScript/src/DelayedAction.lf index 6b6a155952..53ee33e963 100644 --- a/test/TypeScript/src/DelayedAction.lf +++ b/test/TypeScript/src/DelayedAction.lf @@ -5,7 +5,7 @@ target TypeScript { main reactor DelayedAction { timer t(0, 1 sec) logical action a - state count: number(0) + state count: number = 0 reaction(t) -> a {= actions.a.schedule(TimeValue.msec(100), null); =} diff --git a/test/TypeScript/src/DoubleInvocation.lf b/test/TypeScript/src/DoubleInvocation.lf index e2b61789e4..9557857ee2 100644 --- a/test/TypeScript/src/DoubleInvocation.lf +++ b/test/TypeScript/src/DoubleInvocation.lf @@ -13,7 +13,7 @@ target TypeScript { reactor Ball { output position: number output velocity: number - state p: number(200) + state p: number = 200 timer trigger(0, 1 sec) reaction(trigger) -> position, velocity {= @@ -26,7 +26,7 @@ reactor Ball { reactor Print { input velocity: number input position: number - state previous: number(-1) + state previous: number = -1 reaction(startup) {= console.log("####### Print startup"); diff --git a/test/TypeScript/src/DoubleReaction.lf b/test/TypeScript/src/DoubleReaction.lf index 26dccb8895..b172166613 100644 --- a/test/TypeScript/src/DoubleReaction.lf +++ b/test/TypeScript/src/DoubleReaction.lf @@ -5,10 +5,10 @@ target TypeScript { fast: true } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: number timer t(offset, period) - state count: number(0) + state count: number = 0 reaction(t) -> y {= count++; @@ -19,7 +19,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: number input w: number - state s: number(2) + state s: number = 2 reaction(x, w) {= let sum = 0; diff --git a/test/TypeScript/src/DoubleTrigger.lf b/test/TypeScript/src/DoubleTrigger.lf index 34c1596325..fc8921622c 100644 --- a/test/TypeScript/src/DoubleTrigger.lf +++ b/test/TypeScript/src/DoubleTrigger.lf @@ -8,7 +8,7 @@ target TypeScript { main reactor DoubleTrigger { timer t1 timer t2 - state s: number(0) + state s: number = 0 reaction(t1, t2) {= s++; diff --git a/test/TypeScript/src/FloatLiteral.lf b/test/TypeScript/src/FloatLiteral.lf index 1d3e688deb..fb50d007d0 100644 --- a/test/TypeScript/src/FloatLiteral.lf +++ b/test/TypeScript/src/FloatLiteral.lf @@ -2,10 +2,10 @@ target TypeScript // This test verifies that floating-point literals are handled correctly. main reactor { - state N: number(6.0221409e+23) - state charge: number(-1.6021766E-19) - state minus_epsilon: number(-.01e0) - state expected: number(.964853323188E5) + state N: number = 6.0221409e+23 + state charge: number = -1.6021766E-19 + state minus_epsilon: number = -.01e0 + state expected: number = .964853323188E5 reaction(startup) {= const F: number = - N * charge; diff --git a/test/TypeScript/src/Gain.lf b/test/TypeScript/src/Gain.lf index 056b15e2d3..23558da9cf 100644 --- a/test/TypeScript/src/Gain.lf +++ b/test/TypeScript/src/Gain.lf @@ -1,7 +1,7 @@ // Example in the Wiki. target TypeScript -reactor Scale(scale: number(2)) { +reactor Scale(scale: number = 2) { input x: number output y: number @@ -10,7 +10,7 @@ reactor Scale(scale: number(2)) { reactor Test { input x: number - state received_value: boolean(false) + state received_value: boolean = false reaction(x) {= console.log("Received " + x + "."); diff --git a/test/TypeScript/src/Hello.lf b/test/TypeScript/src/Hello.lf index d762535149..c2806f83b8 100644 --- a/test/TypeScript/src/Hello.lf +++ b/test/TypeScript/src/Hello.lf @@ -8,9 +8,9 @@ target TypeScript { fast: true } -reactor Reschedule(period: time(2 sec), message: string("Hello TypeScript")) { - state count: number(0) - state previous_time: time(0) +reactor Reschedule(period: time = 2 sec, message: string = "Hello TypeScript") { + state count: number = 0 + state previous_time: time = 0 timer t(1 sec, period) logical action a @@ -38,8 +38,8 @@ reactor Reschedule(period: time(2 sec), message: string("Hello TypeScript")) { } reactor Inside( - period: time(1 sec), - message: string("Composite default message.") + period: time = 1 sec, + message: string = "Composite default message." ) { third_instance = new Reschedule(period = period, message = message) } diff --git a/test/TypeScript/src/Hierarchy2.lf b/test/TypeScript/src/Hierarchy2.lf index bd469242f7..da2db6308f 100644 --- a/test/TypeScript/src/Hierarchy2.lf +++ b/test/TypeScript/src/Hierarchy2.lf @@ -14,7 +14,7 @@ reactor Source { reactor Count { output out: number timer t(0, 1 sec) - state i: number(0) + state i: number = 0 reaction(t) -> out {= i++; @@ -37,7 +37,7 @@ reactor Add { reactor Print { input x: number - state expected: number(2) + state expected: number = 2 reaction(x) {= x = x as number; diff --git a/test/TypeScript/src/MovingAverage.lf b/test/TypeScript/src/MovingAverage.lf index b7b11dc7fd..19e767e3e2 100644 --- a/test/TypeScript/src/MovingAverage.lf +++ b/test/TypeScript/src/MovingAverage.lf @@ -8,7 +8,7 @@ target TypeScript { reactor Source { output out: number - state count: number(0) + state count: number = 0 timer clock(0, 200 msec) reaction(clock) -> out {= @@ -18,8 +18,8 @@ reactor Source { } reactor MovingAverageImpl { - state delay_line: {= Array =}({= [0.0, 0.0, 0.0] =}) - state index: number(0) + state delay_line: {= Array =} = {= [0.0, 0.0, 0.0] =} + state index: number = 0 input x: number output out: number @@ -45,7 +45,7 @@ reactor MovingAverageImpl { reactor Print { input x: number - state count: number(0) + state count: number = 0 reaction(x) {= x = x as number; diff --git a/test/TypeScript/src/NativeListsAndTimes.lf b/test/TypeScript/src/NativeListsAndTimes.lf index 4a3751bd09..ad22d67d02 100644 --- a/test/TypeScript/src/NativeListsAndTimes.lf +++ b/test/TypeScript/src/NativeListsAndTimes.lf @@ -2,22 +2,22 @@ target TypeScript // This test passes if it is successfully compiled into valid target code. main reactor( - x: number(0), - y: time(0), // Units are missing but not required - z(1 msec), // Type is missing but not required + x: number = 0, + y: time = 0, // Units are missing but not required + z = 1 msec, // Type is missing but not required p: number[](1, 2, 3, 4), // List of integers q: TimeValue[](1 msec, 2 msec, 3 msec), // list of time values g: time[](1 msec, 2 msec) // List of time values ) { - state s: time(y) // Reference to explicitly typed time parameter - state t: time(z) // Reference to implicitly typed time parameter + state s: time = y // Reference to explicitly typed time parameter + state t: time = z // Reference to implicitly typed time parameter state v: boolean // Uninitialized boolean state variable state w: time // Uninitialized time state variable timer tick(0) // Units missing but not required timer tock(1 sec) // Implicit type time timer toe(z) // Implicit type time - state baz(p) // Implicit type int[] - state period(z) // Implicit type time + state baz = p // Implicit type int[] + state period = z // Implicit type time reaction(tick) {= // Target code diff --git a/test/TypeScript/src/ParameterizedState.lf b/test/TypeScript/src/ParameterizedState.lf index d2ea34da12..1484812489 100644 --- a/test/TypeScript/src/ParameterizedState.lf +++ b/test/TypeScript/src/ParameterizedState.lf @@ -1,7 +1,7 @@ target TypeScript -reactor Foo(bar: number(42)) { - state baz(bar) +reactor Foo(bar: number = 42) { + state baz = bar reaction(startup) {= console.log("Baz: " + baz); =} } diff --git a/test/TypeScript/src/PeriodicDesugared.lf b/test/TypeScript/src/PeriodicDesugared.lf index aaeac0e7d9..b99317e07a 100644 --- a/test/TypeScript/src/PeriodicDesugared.lf +++ b/test/TypeScript/src/PeriodicDesugared.lf @@ -1,9 +1,9 @@ target TypeScript -main reactor(offset: time(0), period: time(500 msec)) { +main reactor(offset: time = 0, period: time = 500 msec) { logical action init(offset) logical action recur(period) - state count: number(0) + state count: number = 0 reaction(startup) -> init, recur {= if (offset.isZero()) { diff --git a/test/TypeScript/src/ReadOutputOfContainedReactor.lf b/test/TypeScript/src/ReadOutputOfContainedReactor.lf index af1c4ed0e0..01b725e181 100644 --- a/test/TypeScript/src/ReadOutputOfContainedReactor.lf +++ b/test/TypeScript/src/ReadOutputOfContainedReactor.lf @@ -10,7 +10,7 @@ reactor Contained { main reactor ReadOutputOfContainedReactor { c = new Contained() - state count: number(0) + state count: number = 0 reaction(startup) c.out {= console.log("Startup reaction reading output of contained reactor: " + c.out); diff --git a/test/TypeScript/src/ScheduleLogicalAction.lf b/test/TypeScript/src/ScheduleLogicalAction.lf index 65fae1a237..84e8fbe1cb 100644 --- a/test/TypeScript/src/ScheduleLogicalAction.lf +++ b/test/TypeScript/src/ScheduleLogicalAction.lf @@ -21,7 +21,7 @@ reactor foo { } reactor print { - state expected_time: time(0) + state expected_time: time = 0 input x: number reaction(x) {= diff --git a/test/TypeScript/src/SendingInside.lf b/test/TypeScript/src/SendingInside.lf index 720ce71e64..ca4b6489b3 100644 --- a/test/TypeScript/src/SendingInside.lf +++ b/test/TypeScript/src/SendingInside.lf @@ -7,7 +7,7 @@ target TypeScript { reactor Printer { input x: number - state count: number(1) + state count: number = 1 reaction(x) {= console.log("Inside reactor received: " + x); @@ -19,7 +19,7 @@ reactor Printer { } main reactor SendingInside { - state count: number(0) + state count: number = 0 timer t(0, 1 sec) p = new Printer() diff --git a/test/TypeScript/src/SendsPointerTest.lf b/test/TypeScript/src/SendsPointerTest.lf index 55eb6eca26..3c80613b52 100644 --- a/test/TypeScript/src/SendsPointerTest.lf +++ b/test/TypeScript/src/SendsPointerTest.lf @@ -12,7 +12,7 @@ reactor SendsPointer { } // expected parameter is for testing. -reactor Print(expected: {= {value: number} =}({= { value: 42 } =})) { +reactor Print(expected: {= {value: number} =} = {= { value: 42 } =}) { input x: {= {value: number} =} reaction(x) {= diff --git a/test/TypeScript/src/SimpleDeadline.lf b/test/TypeScript/src/SimpleDeadline.lf index 7327dab0a3..a518e04e5e 100644 --- a/test/TypeScript/src/SimpleDeadline.lf +++ b/test/TypeScript/src/SimpleDeadline.lf @@ -3,7 +3,7 @@ // violation. target TypeScript -reactor Deadline(threshold: time(100 msec)) { +reactor Deadline(threshold: time = 100 msec) { input x: number output deadlineViolation: boolean diff --git a/test/TypeScript/src/SlowingClock.lf b/test/TypeScript/src/SlowingClock.lf index cc447bcd7c..772d2e2913 100644 --- a/test/TypeScript/src/SlowingClock.lf +++ b/test/TypeScript/src/SlowingClock.lf @@ -5,8 +5,8 @@ target TypeScript { main reactor SlowingClock { logical action a(100 msec) - state interval: time(100 msec) - state expected_time: time(100 msec) + state interval: time = 100 msec + state expected_time: time = 100 msec reaction(startup) -> a {= actions.a.schedule(0, null); =} diff --git a/test/TypeScript/src/SlowingClockPhysical.lf b/test/TypeScript/src/SlowingClockPhysical.lf index 4fa5907f73..53bc3f9da4 100644 --- a/test/TypeScript/src/SlowingClockPhysical.lf +++ b/test/TypeScript/src/SlowingClockPhysical.lf @@ -14,8 +14,8 @@ target TypeScript { main reactor SlowingClockPhysical { physical action a(100 msec) - state interval: time(100 msec) - state expected_time: time(100 msec) + state interval: time = 100 msec + state expected_time: time = 100 msec reaction(startup) -> a {= expected_time = TimeValue.msec(100); diff --git a/test/TypeScript/src/Stop.lf b/test/TypeScript/src/Stop.lf index 625cedaac7..6104c418d5 100644 --- a/test/TypeScript/src/Stop.lf +++ b/test/TypeScript/src/Stop.lf @@ -13,7 +13,7 @@ import Sender from "lib/LoopedActionSender.lf" reactor Consumer { input in1: number - state reactionInvokedCorrectly: boolean(false) + state reactionInvokedCorrectly: boolean = false reaction(in1) {= const currentTag = util.getCurrentTag(); diff --git a/test/TypeScript/src/Stride.lf b/test/TypeScript/src/Stride.lf index c8afaa63ad..04d7805d0b 100644 --- a/test/TypeScript/src/Stride.lf +++ b/test/TypeScript/src/Stride.lf @@ -5,8 +5,8 @@ target TypeScript { fast: true } -reactor Count(stride: number(1)) { - state count: number(0) +reactor Count(stride: number = 1) { + state count: number = 0 output y: number timer t(0, 100 msec) diff --git a/test/TypeScript/src/StructAsState.lf b/test/TypeScript/src/StructAsState.lf index 10da7b2876..a3b1cb081c 100644 --- a/test/TypeScript/src/StructAsState.lf +++ b/test/TypeScript/src/StructAsState.lf @@ -8,7 +8,7 @@ main reactor StructAsState { value: number; } =} - state s: hello_t({= {name: "Earth", value: 42} =}) + state s: hello_t = {= {name: "Earth", value: 42} =} reaction(startup) {= console.log("State s.name=" + s.name + ", s.value=" + s.value); diff --git a/test/TypeScript/src/StructAsType.lf b/test/TypeScript/src/StructAsType.lf index c9d092c7cb..ed70a46653 100644 --- a/test/TypeScript/src/StructAsType.lf +++ b/test/TypeScript/src/StructAsType.lf @@ -18,7 +18,7 @@ reactor Source { =} } -reactor Print(expected: number(42)) { // expected parameter is for testing. +reactor Print(expected: number = 42) { // expected parameter is for testing. input x: hello_t reaction(x) {= diff --git a/test/TypeScript/src/StructAsTypeDirect.lf b/test/TypeScript/src/StructAsTypeDirect.lf index 079735f108..3f891747fc 100644 --- a/test/TypeScript/src/StructAsTypeDirect.lf +++ b/test/TypeScript/src/StructAsTypeDirect.lf @@ -18,7 +18,7 @@ reactor Source { =} } -reactor Print(expected: number(42)) { // expected parameter is for testing. +reactor Print(expected: number = 42) { // expected parameter is for testing. input x: hello_t reaction(x) {= diff --git a/test/TypeScript/src/StructPrint.lf b/test/TypeScript/src/StructPrint.lf index 400d26b610..e75d9ead37 100644 --- a/test/TypeScript/src/StructPrint.lf +++ b/test/TypeScript/src/StructPrint.lf @@ -17,7 +17,7 @@ reactor Source { =} } -reactor Print(expected: number(42)) { // expected parameter is for testing. +reactor Print(expected: number = 42) { // expected parameter is for testing. input x: hello_t reaction(x) {= diff --git a/test/TypeScript/src/StructScale.lf b/test/TypeScript/src/StructScale.lf index 46bef4bc2d..788fd2fe61 100644 --- a/test/TypeScript/src/StructScale.lf +++ b/test/TypeScript/src/StructScale.lf @@ -6,7 +6,7 @@ target TypeScript import Print, Source from "StructPrint.lf" -reactor Scale(scale: number(2)) { +reactor Scale(scale: number = 2) { // Mutable keyword indicates that this reactor wants a writable copy of the // input. mutable input x: hello_t diff --git a/test/TypeScript/src/TimeLimit.lf b/test/TypeScript/src/TimeLimit.lf index d581c8c8a9..05a2a5a3b3 100644 --- a/test/TypeScript/src/TimeLimit.lf +++ b/test/TypeScript/src/TimeLimit.lf @@ -7,10 +7,10 @@ target TypeScript { logging: INFO } -reactor Clock(offset: time(0), period: time(1 sec)) { +reactor Clock(offset: time = 0, period: time = 1 sec) { output y: number timer t(offset, period) - state count: number(0) + state count: number = 0 reaction(t) -> y {= count++; @@ -20,7 +20,7 @@ reactor Clock(offset: time(0), period: time(1 sec)) { reactor Destination { input x: number - state s: number(1) + state s: number = 1 reaction(x) {= if (x != s) { @@ -30,7 +30,7 @@ reactor Destination { =} } -main reactor TimeLimit(period: time(1 msec)) { // usecs take a little too long +main reactor TimeLimit(period: time = 1 msec) { // usecs take a little too long timer stop(10 sec) c = new Clock(period = period) diff --git a/test/TypeScript/src/TimeState.lf b/test/TypeScript/src/TimeState.lf index 95d21f8f52..1dfa86312f 100644 --- a/test/TypeScript/src/TimeState.lf +++ b/test/TypeScript/src/TimeState.lf @@ -1,7 +1,7 @@ target TypeScript -reactor Foo(bar: number(42)) { - state baz: time(500 msec) +reactor Foo(bar: number = 42) { + state baz: time = 500 msec reaction(startup) {= console.log("Baz: " + baz); =} } diff --git a/test/TypeScript/src/concurrent/AsyncCallback.lf b/test/TypeScript/src/concurrent/AsyncCallback.lf index bedf2a47a8..2154d52533 100644 --- a/test/TypeScript/src/concurrent/AsyncCallback.lf +++ b/test/TypeScript/src/concurrent/AsyncCallback.lf @@ -17,11 +17,11 @@ main reactor AsyncCallback { } =} timer t(0, 200 msec) - state expected_time: time(100 msec) - state toggle: boolean(false) + state expected_time: time = 100 msec + state toggle: boolean = false physical action a(100 msec): number - state i: number(0) + state i: number = 0 reaction(t) -> a {= // set a timeout for the callback diff --git a/test/TypeScript/src/docker/federated/DistributedCountContainerized.lf b/test/TypeScript/src/docker/federated/DistributedCountContainerized.lf index 34813bef73..ee03246c37 100644 --- a/test/TypeScript/src/docker/federated/DistributedCountContainerized.lf +++ b/test/TypeScript/src/docker/federated/DistributedCountContainerized.lf @@ -11,7 +11,7 @@ target TypeScript { import Count from "../../lib/Count.lf" import Print from "../../federated/DistributedCount.lf" -federated reactor(offset: time(200 msec)) at rti { +federated reactor(offset: time = 200 msec) at rti { c = new Count() p = new Print() c.out -> p.inp after offset diff --git a/test/TypeScript/src/federated/DistributedCount.lf b/test/TypeScript/src/federated/DistributedCount.lf index 423dadd499..50f510cdcd 100644 --- a/test/TypeScript/src/federated/DistributedCount.lf +++ b/test/TypeScript/src/federated/DistributedCount.lf @@ -14,7 +14,7 @@ import Count from "../lib/Count.lf" reactor Print { input inp: number - state c: number(1) + state c: number = 1 reaction(inp) {= const elapsedTime = util.getElapsedLogicalTime(); @@ -35,7 +35,7 @@ reactor Print { =} } -federated reactor DistributedCount(offset: time(200 msec)) { +federated reactor DistributedCount(offset: time = 200 msec) { c = new Count() p = new Print() c.out -> p.inp after offset diff --git a/test/TypeScript/src/federated/DistributedCountPhysical.lf b/test/TypeScript/src/federated/DistributedCountPhysical.lf index c0edeb4c33..9ab549e0d0 100644 --- a/test/TypeScript/src/federated/DistributedCountPhysical.lf +++ b/test/TypeScript/src/federated/DistributedCountPhysical.lf @@ -14,7 +14,7 @@ target TypeScript { reactor Count { timer t(200 msec, 1 sec) - state s: number(0) + state s: number = 0 output out: number reaction(t) -> out {= @@ -25,8 +25,8 @@ reactor Count { reactor Print { input inp: number - state c: number(0) - state compareTime: time(200 msec) + state c: number = 0 + state compareTime: time = 200 msec reaction(inp) {= let elapsedTime = util.getElapsedLogicalTime(); diff --git a/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf b/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf new file mode 100644 index 0000000000..5f81d3cc5f --- /dev/null +++ b/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf @@ -0,0 +1,49 @@ +/** + * Test a distributed system where a federation receives messages only over + * connections that are marked 'physical' (using the ~> arrow) with an after + * delay. The receiver verifies that the after delay is correctly imposed. + * + * @author Edward A. Lee + * @author Soroush Bateni + * @author Byeong-gil Jun + */ +target TypeScript { + logging: debug, + keepalive: true +} + +import Count from "../lib/Count.lf" + +reactor Print { + input inp: number + state c: number = 1 + + reaction(inp) {= + const elapsedTime = util.getElapsedLogicalTime(); + console.log(`At time ${elapsedTime}, received ${inp}`); + if (inp !== c) { + util.requestErrorStop(`ERROR: Expected to receive ${c}.`); + } + if (!elapsedTime.isLaterThan(TimeValue.msec(600))) { + util.requestErrorStop(`ERROR: Expected received time to be strictly greater than ${TimeValue.msec(600)}`); + } + c++; + console.log(`c = ${c}`); + util.requestStop(); + =} + + reaction(shutdown) {= + if (c !== 2) { + util.requestErrorStop(`ERROR: Expected to receive 1 item. Received ${c - 1}.`); + } else { + console.log("SUCCESS: Successfully received 1 item."); + } + =} +} + +federated reactor at localhost { + c = new Count(offset = 200 msec, period = 0) + p = new Print() + // Indicating a 'physical' connection with a 400 msec after delay. + c.out ~> p.inp after 400 msec +} diff --git a/test/TypeScript/src/federated/DistributedDoublePort.lf b/test/TypeScript/src/federated/DistributedDoublePort.lf index d8677d65a4..d3a2636661 100644 --- a/test/TypeScript/src/federated/DistributedDoublePort.lf +++ b/test/TypeScript/src/federated/DistributedDoublePort.lf @@ -17,7 +17,7 @@ target TypeScript { import Count from "../lib/Count.lf" reactor CountMicrostep { - state count: number(1) + state count: number = 1 output out: number logical action act: number timer t(0, 1 sec) diff --git a/test/TypeScript/src/federated/DistributedLoopedAction.lf b/test/TypeScript/src/federated/DistributedLoopedAction.lf index 5e8619395f..02a7f5dc62 100644 --- a/test/TypeScript/src/federated/DistributedLoopedAction.lf +++ b/test/TypeScript/src/federated/DistributedLoopedAction.lf @@ -11,11 +11,11 @@ target TypeScript { import Sender from "../lib/LoopedActionSender.lf" -reactor Receiver(takeBreakAfter: number(10), breakInterval: time(400 msec)) { +reactor Receiver(takeBreakAfter: number = 10, breakInterval: time = 400 msec) { input inp: number - state receivedMessages: number(0) - state totalReceivedMessages: number(0) - state breaks: number(0) + state receivedMessages: number = 0 + state totalReceivedMessages: number = 0 + state breaks: number = 0 timer t(0, 1 msec) // This will impact the performance // but forces the logical time to advance Comment this line for a more diff --git a/test/TypeScript/src/federated/DistributedLoopedPhysicalAction.lf b/test/TypeScript/src/federated/DistributedLoopedPhysicalAction.lf index c747061578..842f13f74c 100644 --- a/test/TypeScript/src/federated/DistributedLoopedPhysicalAction.lf +++ b/test/TypeScript/src/federated/DistributedLoopedPhysicalAction.lf @@ -10,10 +10,10 @@ target TypeScript { timeout: 1 sec } -reactor Sender(takeBreakAfter: number(10), breakInterval: time(550 msec)) { +reactor Sender(takeBreakAfter: number = 10, breakInterval: time = 550 msec) { output out: number physical action act - state sentMessages: number(0) + state sentMessages: number = 0 reaction(startup, act) -> act, out {= // Send a message on out @@ -29,11 +29,11 @@ reactor Sender(takeBreakAfter: number(10), breakInterval: time(550 msec)) { =} } -reactor Receiver(takeBreakAfter: number(10), breakInterval: time(550 msec)) { +reactor Receiver(takeBreakAfter: number = 10, breakInterval: time = 550 msec) { input inp: number - state receivedMessages: number(0) - state totalReceivedMessages: number(0) - state breaks: number(0) + state receivedMessages: number = 0 + state totalReceivedMessages: number = 0 + state breaks: number = 0 timer t(0, 1 msec) // This will impact the performance // but forces the logical time to advance Comment this line for a more diff --git a/test/TypeScript/src/federated/DistributedStop.lf b/test/TypeScript/src/federated/DistributedStop.lf index 2983ff3f7a..c0dd04149a 100644 --- a/test/TypeScript/src/federated/DistributedStop.lf +++ b/test/TypeScript/src/federated/DistributedStop.lf @@ -11,7 +11,7 @@ reactor Sender { output out: number timer t(0, 1 usec) logical action act - state reaction_invoked_correctly: boolean(false) + state reaction_invoked_correctly: boolean = false reaction(t, act) -> out, act {= console.log(`Sending 42 at (${util.getElapsedLogicalTime()}, ` @@ -58,10 +58,10 @@ reactor Sender { } reactor Receiver( - stp_offset: time(10 msec) // Used in the decentralized variant of the test + stp_offset: time = 10 msec // Used in the decentralized variant of the test ) { input in1: number - state reaction_invoked_correctly: boolean(false) + state reaction_invoked_correctly: boolean = false reaction(in1) {= console.log(`Received ${in1} at (${util.getElapsedLogicalTime()}, ` diff --git a/test/TypeScript/src/federated/HelloDistributed.lf b/test/TypeScript/src/federated/HelloDistributed.lf index 12533129e4..1189270ab1 100644 --- a/test/TypeScript/src/federated/HelloDistributed.lf +++ b/test/TypeScript/src/federated/HelloDistributed.lf @@ -20,7 +20,7 @@ reactor Source { reactor Destination { input inp: string - state received: boolean(false) + state received: boolean = false reaction(startup) {= console.log("Destination started."); =} diff --git a/test/TypeScript/src/federated/LoopDistributedCentralized.lf b/test/TypeScript/src/federated/LoopDistributedCentralized.lf index d5fdae2635..bb52679489 100644 --- a/test/TypeScript/src/federated/LoopDistributedCentralized.lf +++ b/test/TypeScript/src/federated/LoopDistributedCentralized.lf @@ -9,11 +9,11 @@ target TypeScript { timeout: 5 sec } -reactor Looper(incr: number(1), delay: time(0 msec)) { +reactor Looper(incr: number = 1, delay: time = 0 msec) { input inp: number output out: number physical action a(delay) - state count: number(0) + state count: number = 0 preamble {= let stop = false; @@ -57,7 +57,7 @@ reactor Looper(incr: number(1), delay: time(0 msec)) { =} } -federated reactor LoopDistributedCentralized(delay: time(0)) { +federated reactor LoopDistributedCentralized(delay: time = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.inp diff --git a/test/TypeScript/src/federated/LoopDistributedDouble.lf b/test/TypeScript/src/federated/LoopDistributedDouble.lf index b86e55feaa..2c791377f7 100644 --- a/test/TypeScript/src/federated/LoopDistributedDouble.lf +++ b/test/TypeScript/src/federated/LoopDistributedDouble.lf @@ -13,13 +13,13 @@ target TypeScript { } } -reactor Looper(incr: number(1), delay: time(0 msec)) { +reactor Looper(incr: number = 1, delay: time = 0 msec) { input inp: number input inp2: number output out: number output out2: number physical action a(delay) - state count: number(0) + state count: number = 0 timer t(0, 1 sec) preamble {= @@ -71,7 +71,7 @@ reactor Looper(incr: number(1), delay: time(0 msec)) { =} } -federated reactor(delay: time(0)) { +federated reactor(delay: time = 0) { left = new Looper() right = new Looper(incr = -1) left.out -> right.inp diff --git a/test/TypeScript/src/federated/PingPongDistributed.lf b/test/TypeScript/src/federated/PingPongDistributed.lf index 0c362b1bf7..34a633aba4 100644 --- a/test/TypeScript/src/federated/PingPongDistributed.lf +++ b/test/TypeScript/src/federated/PingPongDistributed.lf @@ -1,10 +1,10 @@ /** This checks communication between federates */ target TypeScript -reactor Ping(count: number(10)) { +reactor Ping(count: number = 10) { input receive: number output send: number - state pingsLeft: number(count) + state pingsLeft: number = count logical action serve reaction(startup, serve) -> send {= @@ -23,10 +23,10 @@ reactor Ping(count: number(10)) { =} } -reactor Pong(expected: number(10)) { +reactor Pong(expected: number = 10) { input receive: number output send: number - state count: number(0) + state count: number = 0 reaction(receive) -> send {= count += 1; @@ -45,7 +45,7 @@ reactor Pong(expected: number(10)) { =} } -federated reactor PingPongDistributed(count: number(10)) { +federated reactor PingPongDistributed(count: number = 10) { ping = new Ping(count = count) pong = new Pong(expected = count) ping.send -> pong.receive diff --git a/test/TypeScript/src/federated/PingPongDistributedPhysical.lf b/test/TypeScript/src/federated/PingPongDistributedPhysical.lf index 1f33964abc..58f55a4747 100644 --- a/test/TypeScript/src/federated/PingPongDistributedPhysical.lf +++ b/test/TypeScript/src/federated/PingPongDistributedPhysical.lf @@ -27,10 +27,10 @@ */ target TypeScript -reactor Ping(count: number(10)) { +reactor Ping(count: number = 10) { input receive: number output send: number - state pingsLeft: number(count) + state pingsLeft: number = count logical action serve reaction(startup, serve) -> send {= @@ -47,10 +47,10 @@ reactor Ping(count: number(10)) { =} } -reactor Pong(expected: number(10)) { +reactor Pong(expected: number = 10) { input receive: number output send: number - state count: number(0) + state count: number = 0 reaction(receive) -> send {= count++; @@ -69,7 +69,7 @@ reactor Pong(expected: number(10)) { =} } -federated reactor(count: number(10)) { +federated reactor(count: number = 10) { ping = new Ping(count = count) pong = new Pong(expected = count) ping.send ~> pong.receive diff --git a/test/TypeScript/src/federated/TopLevelArtifacts.lf b/test/TypeScript/src/federated/TopLevelArtifacts.lf index 89bdfd9a68..917da033f5 100644 --- a/test/TypeScript/src/federated/TopLevelArtifacts.lf +++ b/test/TypeScript/src/federated/TopLevelArtifacts.lf @@ -16,7 +16,7 @@ import Count from "../lib/Count.lf" import TestCount from "../lib/TestCount.lf" federated reactor { - state successes: number(0) + state successes: number = 0 logical action act timer t(0, 1 sec) diff --git a/test/TypeScript/src/lib/Count.lf b/test/TypeScript/src/lib/Count.lf index b61aaa92e3..d848d8e9d4 100644 --- a/test/TypeScript/src/lib/Count.lf +++ b/test/TypeScript/src/lib/Count.lf @@ -1,9 +1,9 @@ target TypeScript -reactor Count(offset: time(0), period: time(1 sec)) { +reactor Count(offset: time = 0, period: time = 1 sec) { output out: number timer t(offset, period) - state count: number(1) + state count: number = 1 reaction(t) -> out {= out = count++; =} } diff --git a/test/TypeScript/src/lib/InternalDelay.lf b/test/TypeScript/src/lib/InternalDelay.lf index ace3c21c12..0f6f109322 100644 --- a/test/TypeScript/src/lib/InternalDelay.lf +++ b/test/TypeScript/src/lib/InternalDelay.lf @@ -1,7 +1,7 @@ /** @author Youri Su */ target TypeScript -reactor InternalDelay(delay: TimeValue(10 msec)) { +reactor InternalDelay(delay: TimeValue = 10 msec) { input inp: number output out: number logical action d: number diff --git a/test/TypeScript/src/lib/LoopedActionSender.lf b/test/TypeScript/src/lib/LoopedActionSender.lf index f72a427bf1..7da4c133b0 100644 --- a/test/TypeScript/src/lib/LoopedActionSender.lf +++ b/test/TypeScript/src/lib/LoopedActionSender.lf @@ -12,10 +12,10 @@ target TypeScript * @param breakInterval: Determines how long the reactor should take a break * after sending takeBreakAfter messages. */ -reactor Sender(takeBreakAfter: number(10), breakInterval: time(400 msec)) { +reactor Sender(takeBreakAfter: number = 10, breakInterval: time = 400 msec) { output out: number logical action act - state sentMessages: number(0) + state sentMessages: number = 0 reaction(startup, act) -> act, out {= // Send a message on out diff --git a/test/TypeScript/src/lib/TestCount.lf b/test/TypeScript/src/lib/TestCount.lf index 5a4b9067df..822b4c64b1 100644 --- a/test/TypeScript/src/lib/TestCount.lf +++ b/test/TypeScript/src/lib/TestCount.lf @@ -9,9 +9,13 @@ */ target TypeScript -reactor TestCount(start: number(1), stride: number(1), numInputs: number(1)) { - state count: number(start) - state inputsReceived: number(0) +reactor TestCount( + start: number = 1, + stride: number = 1, + numInputs: number = 1 +) { + state count: number = start + state inputsReceived: number = 0 input inp: number reaction(inp) {= diff --git a/test/TypeScript/src/multiport/BankMultiportToReaction.lf b/test/TypeScript/src/multiport/BankMultiportToReaction.lf index 0119785cd5..6516749418 100644 --- a/test/TypeScript/src/multiport/BankMultiportToReaction.lf +++ b/test/TypeScript/src/multiport/BankMultiportToReaction.lf @@ -12,8 +12,8 @@ reactor DoubleCount { } main reactor { - state count: number(1) - state received: boolean(false) + state count: number = 1 + state received: boolean = false s = new[2] DoubleCount() diff --git a/test/TypeScript/src/multiport/BankReactionsInContainer.lf b/test/TypeScript/src/multiport/BankReactionsInContainer.lf index 1628dce060..b14ca33f70 100644 --- a/test/TypeScript/src/multiport/BankReactionsInContainer.lf +++ b/test/TypeScript/src/multiport/BankReactionsInContainer.lf @@ -8,7 +8,7 @@ target TypeScript { reactor R { output[2] out: number input[2] inp: number - state received: boolean(false) + state received: boolean = false reaction(startup) -> out {= for (let i = 0; i < out.length; i++) { @@ -41,7 +41,7 @@ reactor R { main reactor { s = new[2] R() - state received: boolean(false) + state received: boolean = false reaction(startup) -> s.inp {= let count = 0; diff --git a/test/TypeScript/src/multiport/BankSelfBroadcast.lf b/test/TypeScript/src/multiport/BankSelfBroadcast.lf index dcd2aa8dfc..cf5af4bee3 100644 --- a/test/TypeScript/src/multiport/BankSelfBroadcast.lf +++ b/test/TypeScript/src/multiport/BankSelfBroadcast.lf @@ -12,7 +12,7 @@ target TypeScript reactor A { input[4] inp: number output out: number - state received: boolean(false) + state received: boolean = false reaction(startup) -> out {= out = this.getBankIndex(); =} diff --git a/test/TypeScript/src/multiport/BankToBank.lf b/test/TypeScript/src/multiport/BankToBank.lf index 0a4583c65a..5258616899 100644 --- a/test/TypeScript/src/multiport/BankToBank.lf +++ b/test/TypeScript/src/multiport/BankToBank.lf @@ -6,7 +6,7 @@ target TypeScript { reactor Source { timer t(0, 200 msec) output out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= out = s; @@ -15,7 +15,7 @@ reactor Source { } reactor Destination { - state s: number(0) + state s: number = 0 input inp: number reaction(inp) {= @@ -34,7 +34,7 @@ reactor Destination { =} } -main reactor BankToBank(width: number(4)) { +main reactor BankToBank(width: number = 4) { // FIXME: Should set the width to "width" rather than "4". a = new[4] Source() b = new[4] Destination() diff --git a/test/TypeScript/src/multiport/BankToBankMultiport.lf b/test/TypeScript/src/multiport/BankToBankMultiport.lf index ce82471f64..ef58c43ead 100644 --- a/test/TypeScript/src/multiport/BankToBankMultiport.lf +++ b/test/TypeScript/src/multiport/BankToBankMultiport.lf @@ -3,10 +3,10 @@ target TypeScript { timeout: 2 sec } -reactor Source(width: number(1)) { +reactor Source(width: number = 1) { timer t(0, 200 msec) output[width] out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= for(let i = 0; i < out.length; i++) { @@ -15,8 +15,8 @@ reactor Source(width: number(1)) { =} } -reactor Destination(width: number(1)) { - state s: number(6) +reactor Destination(width: number = 1) { + state s: number = 6 input[width] inp: number reaction(inp) {= @@ -40,7 +40,7 @@ reactor Destination(width: number(1)) { =} } -main reactor BankToBankMultiport(bankWidth: number(4)) { +main reactor BankToBankMultiport(bankWidth: number = 4) { a = new[bankWidth] Source(width = 4) b = new[bankWidth] Destination(width = 4) a.out -> b.inp diff --git a/test/TypeScript/src/multiport/BankToBankMultiportAfter.lf b/test/TypeScript/src/multiport/BankToBankMultiportAfter.lf index 8ad676de36..51c095119b 100644 --- a/test/TypeScript/src/multiport/BankToBankMultiportAfter.lf +++ b/test/TypeScript/src/multiport/BankToBankMultiportAfter.lf @@ -3,10 +3,10 @@ target TypeScript { timeout: 2 sec } -reactor Source(width: number(1)) { +reactor Source(width: number = 1) { timer t(0, 200 msec) output[width] out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= for(let i = 0; i < out.length; i++) { @@ -15,8 +15,8 @@ reactor Source(width: number(1)) { =} } -reactor Destination(width: number(1)) { - state s: number(6) +reactor Destination(width: number = 1) { + state s: number = 6 input[width] inp: number reaction(inp) {= @@ -40,7 +40,7 @@ reactor Destination(width: number(1)) { =} } -main reactor(bankWidth: number(4)) { +main reactor(bankWidth: number = 4) { a = new[bankWidth] Source(width = 4) b = new[bankWidth] Destination(width = 4) a.out -> b.inp after 200 msec diff --git a/test/TypeScript/src/multiport/BankToMultiport.lf b/test/TypeScript/src/multiport/BankToMultiport.lf index ecaacb52df..43dc7ee1bb 100644 --- a/test/TypeScript/src/multiport/BankToMultiport.lf +++ b/test/TypeScript/src/multiport/BankToMultiport.lf @@ -9,7 +9,7 @@ reactor Source { reactor Sink { input[4] inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= for (let i = 0; i < inp.length; i++) { diff --git a/test/TypeScript/src/multiport/BankToReaction.lf b/test/TypeScript/src/multiport/BankToReaction.lf index 89b189748d..df65c3063e 100644 --- a/test/TypeScript/src/multiport/BankToReaction.lf +++ b/test/TypeScript/src/multiport/BankToReaction.lf @@ -5,7 +5,7 @@ target TypeScript { import Count from "../lib/Count.lf" main reactor { - state count: number(1) + state count: number = 1 s = new[2] Count() diff --git a/test/TypeScript/src/multiport/BroadcastAfter.lf b/test/TypeScript/src/multiport/BroadcastAfter.lf index 3cf5eb3850..83cf34ddff 100644 --- a/test/TypeScript/src/multiport/BroadcastAfter.lf +++ b/test/TypeScript/src/multiport/BroadcastAfter.lf @@ -10,7 +10,7 @@ reactor Source { reactor Destination { input inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= console.log("Destination " + this.getBankIndex() + " received " + inp + "."); diff --git a/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf b/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf index 598ec737e6..755cf01107 100644 --- a/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf +++ b/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf @@ -2,7 +2,7 @@ target TypeScript { timeout: 2 sec } -reactor Source(value: number(42)) { +reactor Source(value: number = 42) { output out: number reaction(startup) -> out {= out = value; =} @@ -10,7 +10,7 @@ reactor Source(value: number(42)) { reactor Destination { input inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= console.log("Destination " + this.getBankIndex() + " received " + inp + "."); diff --git a/test/TypeScript/src/multiport/FullyConnected.lf b/test/TypeScript/src/multiport/FullyConnected.lf index cf84e92242..e40009ed90 100644 --- a/test/TypeScript/src/multiport/FullyConnected.lf +++ b/test/TypeScript/src/multiport/FullyConnected.lf @@ -1,10 +1,10 @@ target TypeScript -reactor Node(numNodes: number(4)) { +reactor Node(numNodes: number = 4) { input[numNodes] inp: number output out: number - state received: boolean(false) + state received: boolean = false reaction(startup) -> out {= console.log("Hello from node " + this.getBankIndex() + "!"); @@ -36,7 +36,7 @@ reactor Node(numNodes: number(4)) { =} } -main reactor(numNodes: number(4)) { +main reactor(numNodes: number = 4) { nodes = new[numNodes] Node(numNodes = numNodes) (nodes.out)+ -> nodes.inp } diff --git a/test/TypeScript/src/multiport/MultiportFromBank.lf b/test/TypeScript/src/multiport/MultiportFromBank.lf index 489a9df4a8..18896eed2a 100644 --- a/test/TypeScript/src/multiport/MultiportFromBank.lf +++ b/test/TypeScript/src/multiport/MultiportFromBank.lf @@ -10,9 +10,9 @@ reactor Source { reaction(startup) -> out {= out = this.getBankIndex(); =} } -reactor Destination(portWidth: number(3)) { +reactor Destination(portWidth: number = 3) { input[portWidth] inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= for (let i = 0; i < inp.length; i++) { @@ -32,7 +32,7 @@ reactor Destination(portWidth: number(3)) { =} } -main reactor(width: number(4)) { +main reactor(width: number = 4) { a = new[width] Source() b = new Destination(portWidth = width) a.out -> b.inp diff --git a/test/TypeScript/src/multiport/MultiportFromBankHierarchy.lf b/test/TypeScript/src/multiport/MultiportFromBankHierarchy.lf index e050682ab6..ffc394b26c 100644 --- a/test/TypeScript/src/multiport/MultiportFromBankHierarchy.lf +++ b/test/TypeScript/src/multiport/MultiportFromBankHierarchy.lf @@ -6,7 +6,7 @@ target TypeScript { import Source, Destination from "MultiportFromBank.lf" -reactor Container(portWidth: number(3)) { +reactor Container(portWidth: number = 3) { output[portWidth] out: number s = new[portWidth] Source() s.out -> out diff --git a/test/TypeScript/src/multiport/MultiportFromHierarchy.lf b/test/TypeScript/src/multiport/MultiportFromHierarchy.lf index 5922d2f911..5627652b2f 100644 --- a/test/TypeScript/src/multiport/MultiportFromHierarchy.lf +++ b/test/TypeScript/src/multiport/MultiportFromHierarchy.lf @@ -4,10 +4,10 @@ target TypeScript { timeout: 2 sec } -reactor Source(width: number(3)) { +reactor Source(width: number = 3) { timer t(0, 200 msec) output[width] out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= for(let i = 0; i < out.length; i++) { @@ -16,8 +16,8 @@ reactor Source(width: number(3)) { =} } -reactor Destination(width: number(3)) { - state s: number(6) +reactor Destination(width: number = 3) { + state s: number = 6 input[width] inp: number reaction(inp) {= @@ -41,19 +41,19 @@ reactor Destination(width: number(3)) { =} } -reactor Container(width: number(3)) { +reactor Container(width: number = 3) { output[width] out: number src = new InsideContainer(width = width) src.out -> out } -reactor InsideContainer(width: number(3)) { +reactor InsideContainer(width: number = 3) { output[width] out: number src = new Source(width = width) src.out -> out } -main reactor MultiportFromHierarchy(width: number(4)) { +main reactor MultiportFromHierarchy(width: number = 4) { a = new Container(width = width) b = new Destination(width = width) a.out -> b.inp diff --git a/test/TypeScript/src/multiport/MultiportFromReaction.lf b/test/TypeScript/src/multiport/MultiportFromReaction.lf index 6670c2aa5a..ba386bd379 100644 --- a/test/TypeScript/src/multiport/MultiportFromReaction.lf +++ b/test/TypeScript/src/multiport/MultiportFromReaction.lf @@ -3,8 +3,8 @@ target TypeScript { timeout: 2 sec } -reactor Destination(width: number(1)) { - state s: number(6) +reactor Destination(width: number = 1) { + state s: number = 6 input[width] inp: number reaction(inp) {= @@ -28,9 +28,9 @@ reactor Destination(width: number(1)) { =} } -main reactor MultiportFromReaction(width: number(4)) { +main reactor MultiportFromReaction(width: number = 4) { timer t(0, 200 msec) - state s: number(0) + state s: number = 0 b = new Destination(width = width) reaction(t) -> b.inp {= diff --git a/test/TypeScript/src/multiport/MultiportIn.lf b/test/TypeScript/src/multiport/MultiportIn.lf index c409969657..97a36c08d2 100644 --- a/test/TypeScript/src/multiport/MultiportIn.lf +++ b/test/TypeScript/src/multiport/MultiportIn.lf @@ -7,7 +7,7 @@ target TypeScript { reactor Source { timer t(0, 200 msec) output out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= out = s++; =} } @@ -20,7 +20,7 @@ reactor Computation { } reactor Destination { - state s: number(0) + state s: number = 0 input[4] inp: number reaction(inp) {= diff --git a/test/TypeScript/src/multiport/MultiportInParameterized.lf b/test/TypeScript/src/multiport/MultiportInParameterized.lf index c189e56ef4..fb11c5e102 100644 --- a/test/TypeScript/src/multiport/MultiportInParameterized.lf +++ b/test/TypeScript/src/multiport/MultiportInParameterized.lf @@ -7,7 +7,7 @@ target TypeScript { reactor Source { timer t(0, 200 msec) output out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= out = s; @@ -22,8 +22,8 @@ reactor Computation { reaction(inp) -> out {= out = inp; =} } -reactor Destination(width: number(1)) { - state s: number(0) +reactor Destination(width: number = 1) { + state s: number = 0 input[width] inp: number reaction(inp) {= diff --git a/test/TypeScript/src/multiport/MultiportMutableInput.lf b/test/TypeScript/src/multiport/MultiportMutableInput.lf index f04131e411..11120e8d30 100644 --- a/test/TypeScript/src/multiport/MultiportMutableInput.lf +++ b/test/TypeScript/src/multiport/MultiportMutableInput.lf @@ -12,7 +12,7 @@ reactor Source { =} } -reactor Print(scale: number(1)) { // The scale parameter is just for testing. +reactor Print(scale: number = 1) { // The scale parameter is just for testing. input[2] inp: number reaction(inp) {= @@ -27,7 +27,7 @@ reactor Print(scale: number(1)) { // The scale parameter is just for testing. =} } -reactor Scale(scale: number(2)) { +reactor Scale(scale: number = 2) { mutable input[2] inp: number output[2] out: number diff --git a/test/TypeScript/src/multiport/MultiportMutableInputArray.lf b/test/TypeScript/src/multiport/MultiportMutableInputArray.lf index 26b5c3b87c..382f444df7 100644 --- a/test/TypeScript/src/multiport/MultiportMutableInputArray.lf +++ b/test/TypeScript/src/multiport/MultiportMutableInputArray.lf @@ -26,7 +26,7 @@ reactor Source { =} } -reactor Print(scale: number(1)) { // The scale parameter is just for testing. +reactor Print(scale: number = 1) { // The scale parameter is just for testing. input[2] inp: {= Array =} reaction(inp) {= @@ -55,7 +55,7 @@ reactor Print(scale: number(1)) { // The scale parameter is just for testing. =} } -reactor Scale(scale: number(2)) { +reactor Scale(scale: number = 2) { mutable input[2] inp: {= Array =} output[2] out: {= Array =} diff --git a/test/TypeScript/src/multiport/MultiportOut.lf b/test/TypeScript/src/multiport/MultiportOut.lf index 9fa72bf3e2..04588b86b5 100644 --- a/test/TypeScript/src/multiport/MultiportOut.lf +++ b/test/TypeScript/src/multiport/MultiportOut.lf @@ -6,7 +6,7 @@ target TypeScript { reactor Source { timer t(0, 200 msec) output[4] out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= for(let i = 0; i < 4; i++) { @@ -27,7 +27,7 @@ reactor Computation { } reactor Destination { - state s: number(0) + state s: number = 0 input[4] inp: number reaction(inp) {= diff --git a/test/TypeScript/src/multiport/MultiportToBankAfter.lf b/test/TypeScript/src/multiport/MultiportToBankAfter.lf index 357dc1d0e1..ee10df94c6 100644 --- a/test/TypeScript/src/multiport/MultiportToBankAfter.lf +++ b/test/TypeScript/src/multiport/MultiportToBankAfter.lf @@ -4,7 +4,7 @@ target TypeScript { timeout: 2 sec } -reactor Source(width: number(2)) { +reactor Source(width: number = 2) { output[width] out: number reaction(startup) -> out {= @@ -16,7 +16,7 @@ reactor Source(width: number(2)) { reactor Destination { input inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= console.log("Destination " + this.getBankIndex() + " received " + inp + "."); @@ -39,7 +39,7 @@ reactor Destination { =} } -main reactor(width: number(3)) { +main reactor(width: number = 3) { a = new Source(width = width) b = new[width] Destination() a.out -> b.inp after 1 sec // Width of the bank of delays will be inferred. diff --git a/test/TypeScript/src/multiport/MultiportToBankDouble.lf b/test/TypeScript/src/multiport/MultiportToBankDouble.lf index b429a846a4..e618dd898d 100644 --- a/test/TypeScript/src/multiport/MultiportToBankDouble.lf +++ b/test/TypeScript/src/multiport/MultiportToBankDouble.lf @@ -25,7 +25,7 @@ reactor Source { reactor Destination { input inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= console.log("Destination " + this.getBankIndex() + " received " + inp + "."); diff --git a/test/TypeScript/src/multiport/MultiportToBankHierarchy.lf b/test/TypeScript/src/multiport/MultiportToBankHierarchy.lf index 3e5c499d06..7420704ce1 100644 --- a/test/TypeScript/src/multiport/MultiportToBankHierarchy.lf +++ b/test/TypeScript/src/multiport/MultiportToBankHierarchy.lf @@ -16,7 +16,7 @@ reactor Source { reactor Destination { input inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= console.log("Destination " + this.getBankIndex() + " received " + inp + "."); diff --git a/test/TypeScript/src/multiport/MultiportToHierarchy.lf b/test/TypeScript/src/multiport/MultiportToHierarchy.lf index 4673ff1e22..c620000872 100644 --- a/test/TypeScript/src/multiport/MultiportToHierarchy.lf +++ b/test/TypeScript/src/multiport/MultiportToHierarchy.lf @@ -5,10 +5,10 @@ target TypeScript { timeout: 2 sec } -reactor Source(width: number(4)) { +reactor Source(width: number = 4) { timer t(0, 200 msec) output[width] out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= for(let i = 0; i < 4; i++) { @@ -17,8 +17,8 @@ reactor Source(width: number(4)) { =} } -reactor Destination(width: number(4)) { - state s: number(6) +reactor Destination(width: number = 4) { + state s: number = 6 input[width] inp: number reaction(inp) {= @@ -42,13 +42,13 @@ reactor Destination(width: number(4)) { =} } -reactor Container(width: number(4)) { +reactor Container(width: number = 4) { input[width] inp: number dst = new Destination() inp -> dst.inp } -main reactor MultiportToHierarchy(width: number(4)) { +main reactor MultiportToHierarchy(width: number = 4) { a = new Source(width = width) b = new Container(width = width) a.out -> b.inp diff --git a/test/TypeScript/src/multiport/MultiportToMultiport.lf b/test/TypeScript/src/multiport/MultiportToMultiport.lf index 54bac4fbfb..ecd774e7b6 100644 --- a/test/TypeScript/src/multiport/MultiportToMultiport.lf +++ b/test/TypeScript/src/multiport/MultiportToMultiport.lf @@ -12,7 +12,7 @@ reactor Source { reactor Sink { input[4] inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= for (let i = 0; i < inp.length; i++) { diff --git a/test/TypeScript/src/multiport/MultiportToMultiport2.lf b/test/TypeScript/src/multiport/MultiportToMultiport2.lf index 73b3cfedc5..9e9856af63 100644 --- a/test/TypeScript/src/multiport/MultiportToMultiport2.lf +++ b/test/TypeScript/src/multiport/MultiportToMultiport2.lf @@ -1,7 +1,7 @@ // Test multiport to multiport connections. See also MultiportToMultiport. target TypeScript -reactor Source(width: number(2)) { +reactor Source(width: number = 2) { output[width] out: number reaction(startup) -> out {= @@ -11,7 +11,7 @@ reactor Source(width: number(2)) { =} } -reactor Destination(width: number(2)) { +reactor Destination(width: number = 2) { input[width] inp: number reaction(inp) {= diff --git a/test/TypeScript/src/multiport/MultiportToMultiport2After.lf b/test/TypeScript/src/multiport/MultiportToMultiport2After.lf index 1bca78675c..04890686ab 100644 --- a/test/TypeScript/src/multiport/MultiportToMultiport2After.lf +++ b/test/TypeScript/src/multiport/MultiportToMultiport2After.lf @@ -1,7 +1,7 @@ // Test multiport to multiport connections. See also MultiportToMultiport. target TypeScript -reactor Source(width: number(2)) { +reactor Source(width: number = 2) { output[width] out: number reaction(startup) -> out {= @@ -11,7 +11,7 @@ reactor Source(width: number(2)) { =} } -reactor Destination(width: number(2)) { +reactor Destination(width: number = 2) { input[width] inp: number reaction(inp) {= diff --git a/test/TypeScript/src/multiport/MultiportToMultiportArray.lf b/test/TypeScript/src/multiport/MultiportToMultiportArray.lf index 8e08494cad..737603a5bd 100644 --- a/test/TypeScript/src/multiport/MultiportToMultiportArray.lf +++ b/test/TypeScript/src/multiport/MultiportToMultiportArray.lf @@ -7,7 +7,7 @@ target TypeScript { reactor Source { timer t(0, 200 msec) output[2] out: {= Array =} - state s: number(0) + state s: number = 0 reaction(t) -> out {= for(let i = 0; i < 2; i++) { @@ -24,7 +24,7 @@ reactor Source { } reactor Destination { - state s: number(15) + state s: number = 15 input[2] inp: {= Array =} reaction(inp) {= diff --git a/test/TypeScript/src/multiport/MultiportToMultiportParameter.lf b/test/TypeScript/src/multiport/MultiportToMultiportParameter.lf index d1be5397dd..5252783498 100644 --- a/test/TypeScript/src/multiport/MultiportToMultiportParameter.lf +++ b/test/TypeScript/src/multiport/MultiportToMultiportParameter.lf @@ -3,10 +3,10 @@ target TypeScript { timeout: 2 sec } -reactor Source(width: number(1)) { +reactor Source(width: number = 1) { timer t(0, 200 msec) output[width] out: number - state s: number(0) + state s: number = 0 reaction(t) -> out {= for (let i = 0; i < out.length; i++) { @@ -15,8 +15,8 @@ reactor Source(width: number(1)) { =} } -reactor Destination(width: number(1)) { - state s: number(6) +reactor Destination(width: number = 1) { + state s: number = 6 input[width] inp: number // Width is one larger than that of the source. reaction(inp) {= @@ -39,7 +39,7 @@ reactor Destination(width: number(1)) { =} } -main reactor(width: number(4)) { +main reactor(width: number = 4) { a = new Source(width = width) b = new Destination(width = width) a.out -> b.inp diff --git a/test/TypeScript/src/multiport/MultiportToPort.lf b/test/TypeScript/src/multiport/MultiportToPort.lf index a366089fd9..a00d482fe8 100644 --- a/test/TypeScript/src/multiport/MultiportToPort.lf +++ b/test/TypeScript/src/multiport/MultiportToPort.lf @@ -15,9 +15,9 @@ reactor Source { =} } -reactor Destination(expected: number(0)) { +reactor Destination(expected: number = 0) { input inp: number - state received: boolean(false) + state received: boolean = false reaction(inp) {= console.log("Received " + inp); diff --git a/test/TypeScript/src/multiport/MultiportToReaction.lf b/test/TypeScript/src/multiport/MultiportToReaction.lf index 84d33b642f..e7b5af4c00 100644 --- a/test/TypeScript/src/multiport/MultiportToReaction.lf +++ b/test/TypeScript/src/multiport/MultiportToReaction.lf @@ -3,9 +3,9 @@ target TypeScript { timeout: 2 sec } -reactor Source(width: number(1)) { +reactor Source(width: number = 1) { timer t(0, 200 msec) - state s: number(0) + state s: number = 0 output[width] out: number reaction(t) -> out {= @@ -17,7 +17,7 @@ reactor Source(width: number(1)) { } main reactor MultiportToReaction { - state s: number(6) + state s: number = 6 b = new Source(width = 4) reaction(b.out) {= diff --git a/test/TypeScript/src/multiport/NestedBanks.lf b/test/TypeScript/src/multiport/NestedBanks.lf index fea594d1ea..a5c31d38d9 100644 --- a/test/TypeScript/src/multiport/NestedBanks.lf +++ b/test/TypeScript/src/multiport/NestedBanks.lf @@ -20,7 +20,7 @@ reactor A { b.y -> x } -reactor B(aBankIndex: number(0)) { +reactor B(aBankIndex: number = 0) { output[2] y: number reaction(startup) -> y {= @@ -60,7 +60,7 @@ reactor E { =} } -reactor F(cBankIndex: number(0)) { +reactor F(cBankIndex: number = 0) { input w: number reaction(w) {= @@ -71,7 +71,7 @@ reactor F(cBankIndex: number(0)) { =} } -reactor G(cBankIndex: number(0)) { +reactor G(cBankIndex: number = 0) { input s: number reaction(s) {= diff --git a/test/TypeScript/src/multiport/ReactionToContainedBank.lf b/test/TypeScript/src/multiport/ReactionToContainedBank.lf index 9f54a0123b..9b058671cc 100644 --- a/test/TypeScript/src/multiport/ReactionToContainedBank.lf +++ b/test/TypeScript/src/multiport/ReactionToContainedBank.lf @@ -5,9 +5,9 @@ target TypeScript { import TestCount from "../lib/TestCount.lf" -main reactor ReactionToContainedBank(width: number(2)) { +main reactor ReactionToContainedBank(width: number = 2) { timer t(0, 100 msec) - state count: number(1) + state count: number = 1 test = new[width] TestCount(numInputs = 11) diff --git a/test/TypeScript/src/multiport/ReactionsToNested.lf b/test/TypeScript/src/multiport/ReactionsToNested.lf index dbc20ef8ed..055e08d086 100644 --- a/test/TypeScript/src/multiport/ReactionsToNested.lf +++ b/test/TypeScript/src/multiport/ReactionsToNested.lf @@ -4,9 +4,9 @@ target TypeScript { timeout: 1 sec } -reactor T(expected: number(0)) { +reactor T(expected: number = 0) { input z: number - state received: boolean(false) + state received: boolean = false reaction(z) {= console.log("T received " + z); diff --git a/test/TypeScript/src/target/AfterNoTypes.lf b/test/TypeScript/src/target/AfterNoTypes.lf index 2293faf601..b3477bbf55 100644 --- a/test/TypeScript/src/target/AfterNoTypes.lf +++ b/test/TypeScript/src/target/AfterNoTypes.lf @@ -15,7 +15,7 @@ reactor Foo { } reactor Print { - state expected_time: time(10 msec) + state expected_time: time = 10 msec input x reaction(x) {= From 6c227293396b24cc6075716e3ace57cdd92036c1 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 30 Mar 2023 22:23:57 -0700 Subject: [PATCH 058/108] fixed generator issue --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 70f76c282e..255941e8cd 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1527,7 +1527,7 @@ private void recordWatchdogs(ReactorInstance instance) { // temp.pr("#ifdef LF_THREADED"); for (WatchdogInstance watchdog : instance.watchdogs) { temp.pr(" _lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); - temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+GeneratorBase.timeInTargetLanguage(watchdog.getTimeout())+";"); + temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+watchdog.getTimeout()+";"); temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); watchdogCount += 1; foundOne = true; From a4740c35ede3e45fcf1887cf04828ec60b3a3403 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 3 Apr 2023 11:47:53 -0700 Subject: [PATCH 059/108] saving before continuing changes --- org.lflang/src/org/lflang/ASTUtils.java | 7 +++++ .../org/lflang/generator/c/CGenerator.java | 2 +- .../generator/c/CWatchdogGenerator.java | 26 ++++++++++++++++--- test/C/src/Watchdog.lf | 1 + 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 4423363fec..f9767e7e9a 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -586,6 +586,13 @@ public static Iterable allElementsOfClass( //////////////////////////////// //// Utility functions for translating AST nodes into text + // public static Code toCode(String text) { + // Code code = null; + // if (text == null) return code; + // code.setBody(text); + // return code; + // } + /** * Translate the given code into its textual representation * with {@code CodeMap.Correspondence} tags inserted, or diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 1d588aa260..33bcab4b70 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1525,7 +1525,7 @@ private void recordWatchdogs(ReactorInstance instance) { // temp.pr("#ifdef LF_THREADED"); for (WatchdogInstance watchdog : instance.watchdogs) { temp.pr(" _lf_watchdogs[_lf_watchdog_number_count++] = &"+reactorRef+"->_lf_watchdog_"+watchdog.getName()+";"); - temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+watchdog.getTimeout()+";"); + temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".min_expiration = "+CTypes.getInstance().getTargetTimeExpr(watchdog.getTimeout())+";"); temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); watchdogCount += 1; foundOne = true; diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 4193a7c522..0c1ce1f762 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -106,12 +106,32 @@ public static String generateWatchdogFunctionHeader(Watchdog watchdog, * Generate the watchdog function. */ public static String generateWatchdogFunction(Watchdog watchdog, - ReactorDecl decl) { - return CReactionGenerator.generateFunction(generateWatchdogFunctionHeader(watchdog, decl), + ReactorDecl decl) { + return generateFunction(generateWatchdogFunctionHeader(watchdog, decl), generateInitializationForWatchdog(watchdog, decl), - watchdog.getCode()); + 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.prSourceLineNumber(watchdog.getCode()); + function.pr(ASTUtils.toText(watchdog.getCode())); + function.pr("lf_set("+watchdog.getName()+", 1);"); + function.unindent(); + function.pr("}"); + return function.toString(); + } + + /** * Generate watchdog definition in parent struct. */ diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index b44a16f22c..df88d23d73 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -34,6 +34,7 @@ reactor Watcher(timeout:time(150ms)) { =} reaction(poodle) -> d {= + lf_print("Reaction poodle was called."); lf_set(d, 1); =} From 1af9922b0668c2f32891898b595031e797e9f93f Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 3 Apr 2023 12:38:06 -0700 Subject: [PATCH 060/108] added watchdog to body of itself, started making changes to treat watchdog trigger --- .../src/org/lflang/generator/c/CWatchdogGenerator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 0c1ce1f762..3c9568699c 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -75,6 +75,8 @@ public static String generateInitializationForWatchdog(Watchdog watchdog, } } } + // 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()); @@ -125,7 +127,8 @@ public static String generateFunction(String header, String init, Watchdog watch function.pr(init); function.prSourceLineNumber(watchdog.getCode()); function.pr(ASTUtils.toText(watchdog.getCode())); - function.pr("lf_set("+watchdog.getName()+", 1);"); + //FIXME: will need to lf_schedule instead + // function.pr("lf_set("+watchdog.getName()+", 1);"); function.unindent(); function.pr("}"); return function.toString(); From b5d6d5891872e840426f2ba8e9ceb9f475c0466e Mon Sep 17 00:00:00 2001 From: Benichiwa Date: Wed, 5 Apr 2023 12:38:30 -0700 Subject: [PATCH 061/108] create triggers save progress --- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/org/lflang/generator/c/CGenerator.java | 1 + .../src/org/lflang/generator/c/CReactionGenerator.java | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index ff3c84c36b..e24e3bca22 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ff3c84c36b2d7fbcbe09fafa577ec94f75194e83 +Subproject commit e24e3bca22c8e2f85be31ec61a1d3e99a72566e2 diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 33bcab4b70..b54c77f6ab 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -310,6 +310,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * @author Alexander Schulz-Rosengarten * @author Hou Seng Wong * @author Anirudh Rengarajan + * @author Benjamin Asch */ @SuppressWarnings("StaticPseudoFunctionalStyleMethod") public class CGenerator extends GeneratorBase { diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index e7e43a903c..e58d423d99 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -854,6 +854,11 @@ public static void generateReactionAndTriggerStructs( for (Input input : ASTUtils.allInputs(reactor)) { createTriggerT(body, input, triggerMap, constructorCode, types); } + + // Next handle watchdogs. + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + createTriggerT(body, watchdog, triggerMap, constructorCode, types); + } } /** From 34f96e576eec63f573527e9c1d10efe26e04415c Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Wed, 5 Apr 2023 14:36:35 -0700 Subject: [PATCH 062/108] fixed watchdog test inconsistency? define watchdog trigger --- org.lflang/src/lib/c/reactor-c | 2 +- .../src/org/lflang/generator/c/CReactionGenerator.java | 2 +- .../src/org/lflang/generator/c/CWatchdogGenerator.java | 5 +++-- test/C/src/Watchdog.lf | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index e24e3bca22..ff3c84c36b 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit e24e3bca22c8e2f85be31ec61a1d3e99a72566e2 +Subproject commit ff3c84c36b2d7fbcbe09fafa577ec94f75194e83 diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index e58d423d99..0a1df14302 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -866,7 +866,7 @@ public static void generateReactionAndTriggerStructs( * 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 variable The trigger variable (Timer, Watchdog, 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. diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 3c9568699c..ece414ac36 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -128,7 +128,7 @@ public static String generateFunction(String header, String init, Watchdog watch function.prSourceLineNumber(watchdog.getCode()); function.pr(ASTUtils.toText(watchdog.getCode())); //FIXME: will need to lf_schedule instead - // function.pr("lf_set("+watchdog.getName()+", 1);"); + function.pr("_lf_schedule((*"+watchdog.getName()+").trigger, 0, NULL);"); function.unindent(); function.pr("}"); return function.toString(); @@ -173,7 +173,8 @@ public static void generateWatchdogStruct( "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+".watchdog_function = "+watchdogFunctionName+";", + "self->_lf_watchdog_"+watchdogName+".trigger = &(self->_lf__"+watchdogName+");" )); // constructorCode.pr("#endif"); // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index df88d23d73..fdac093c49 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -58,8 +58,8 @@ main reactor { self->count++; =} reaction(shutdown) {= - if (self->count != 4) { - lf_print_error_and_exit("Watchdog produced output %d times. Expected 4.", self->count); + if (self->count != 5) { + lf_print_error_and_exit("Watchdog produced output %d times. Expected 5.", self->count); } =} } From a35208f360e02f4ad4788ba1589d8717ca72f8d8 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 10 Apr 2023 13:52:29 -0700 Subject: [PATCH 063/108] tried removing boolean from self-base_t struct, failed, everything working now --- .../org/lflang/generator/c/CGenerator.java | 5 ++ .../generator/c/CWatchdogGenerator.java | 22 +------ test/Python/src/PyWatchdog.lf | 66 +++++++++++++++++++ 3 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 test/Python/src/PyWatchdog.lf diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index b54c77f6ab..5e94fe55fb 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1243,6 +1243,11 @@ private void generateSelfStruct(ReactorDecl decl, CodeBuilder constructorCode) { // Next, generate fields for modes CModesGenerator.generateDeclarations(reactor, body, constructorCode); + // constructorCode.pr(reactor, String.join("\n", + // "#ifdef LF_THREADED", + // " self->base->watchdog_mutex = NULL;", + // "#endif")); + // 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. diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index ece414ac36..9163883962 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -124,11 +124,9 @@ public static String generateFunction(String header, String init, Watchdog watch var function = new CodeBuilder(); function.pr(header + " {"); function.indent(); - function.pr(init); + function.pr(init);function.pr("_lf_schedule((*"+watchdog.getName()+").trigger, 0, NULL);"); function.prSourceLineNumber(watchdog.getCode()); function.pr(ASTUtils.toText(watchdog.getCode())); - //FIXME: will need to lf_schedule instead - function.pr("_lf_schedule((*"+watchdog.getName()+").trigger, 0, NULL);"); function.unindent(); function.pr("}"); return function.toString(); @@ -147,25 +145,12 @@ public static void generateWatchdogStruct( for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { String watchdogName = watchdog.getName(); - // Create pointer to the watchdog_t struct - // WATCHDOG QUESTION 2: Why need to put object at beginning of - // `.pr` func call? - - // WATCHDOG QUESTION 3: Is the space for this struct automatically allocated - // through `_lf_new_reactor`? `_lf__startup_reaction` is also a pointer in self struct - // but does not seem to have a separate allocation call. - body.pr(watchdog, "watchdog_t _lf_watchdog_"+watchdogName+";"); - // WATCHDOG QUESTION 4: Not sure if this is correct, may need to use - // 'getTargetTime' instead. watchdog timeout is listed as "Expression" - // in the grammar, so I'm not sure if it is reading the timeout as - // a Time class or TimeValue class. - // var min_expiration = GeneratorBase.timeInTargetLanguage(watchdog.getTimeout()); + 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 - // WATCHDOG QUESTION 5: should I be defining these in the constructor of the reactor? //FIXME: update parameters // constructorCode.pr("#ifdef LF_THREADED"); constructorCode.pr(watchdog, String.join("\n", @@ -176,8 +161,7 @@ public static void generateWatchdogStruct( "self->_lf_watchdog_"+watchdogName+".watchdog_function = "+watchdogFunctionName+";", "self->_lf_watchdog_"+watchdogName+".trigger = &(self->_lf__"+watchdogName+");" )); - // constructorCode.pr("#endif"); - // WATCHDOG QUESTION 6: should I be initializing mutex in this constructor? + } } diff --git a/test/Python/src/PyWatchdog.lf b/test/Python/src/PyWatchdog.lf new file mode 100644 index 0000000000..a89cf0d7de --- /dev/null +++ b/test/Python/src/PyWatchdog.lf @@ -0,0 +1,66 @@ +/** + * 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 Python { + timeout: 1100ms, + threading: true +} + +reactor Watcher(timeout(150 ms)) { + timer t(100 ms, 100 ms) // Offset ameliorates startup time. + output d // Produced if watchdog triggers. + state alternating(False) + state count(0) + + watchdog poodle(timeout) {= + p = lf_time_physical_elapsed() - lf_time_logical_elapsed() + lf_print("Watchdog timed out! Lag: "+p+" (too late by "+(p - self.timeout)+" ns)") + self.count += 1 + =} + + reaction(t) -> poodle, d {= + lf_watchdog_start(poodle, 0) + lf_print("Watchdog started at physical time "+lf_time_physical_elapsed()) + lf_print("Will expire at "+lf_time_logical_elapsed() + self.timeout) + if (self.alternating) { + lf_sleep(160 ms) + } + self.alternating = not self.alternating + =} + + reaction(poodle) -> d {= + lf_print("Reaction poodle was called.") + d.set(1) + =} + + reaction(shutdown) -> poodle {= + _lf_watchdog_stop(poodle) + if (self.count != 5) { + lf_print_error_and_exit("Watchdog expired "+self.count+" times. Expected 5.") + } + =} +} + +main reactor { + logical action a + state count(0) + w = new Watcher() + + reaction(w.d) {= + lf_print("*** Watcher reactor produced an output.") + self.count += 1 + =} + + reaction(shutdown) {= + if (self.count != 5) { + lf_print_error_and_exit("Watchdog produced output "+self.count+" times. Expected 5.") + } + =} +} From 5773b8919f46cb2590adb0516ae4e32696314712 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 14 Apr 2023 00:30:20 -0700 Subject: [PATCH 064/108] created check for runtime, and removed local one for each class --- .../org/lflang/generator/GeneratorBase.java | 24 +++++++++++++++++++ .../org/lflang/generator/c/CGenerator.java | 9 ++++++- .../generator/python/PythonGenerator.java | 1 + 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index ba39a94d9f..ac141b9259 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -165,6 +165,12 @@ public abstract class GeneratorBase extends AbstractLFValidator { */ public boolean hasDeadlines = false; + /** + * Indicates whether the program has any watchdogs. + * This is used to check for support. + */ + public boolean hasWatchdogs = false; + // ////////////////////////////////////////// // // Private fields. @@ -293,6 +299,10 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // 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(); } @@ -429,6 +439,20 @@ protected void checkModalReactorSupport(boolean isSupported) { } } + /** + * 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." + ); + } + } + + /** * Finds and transforms connections into forwarding reactions iff the connections have the same destination as other * connections or reaction in mutually exclusive modes. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 5e94fe55fb..07d77cdbd5 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -446,6 +446,7 @@ protected boolean isOSCompatible() { /** Returns false if watchdogs exist and are * unsupported in this context. * Otherwise, return true. + * (DEPRECATED) Alternative implemented in GeneratorBase */ protected boolean isWatchdogCompatible() { if (hasWatchdogs() && !targetConfig.threading) { @@ -454,6 +455,12 @@ protected boolean isWatchdogCompatible() { ); return false; } + if (hasWatchdogs() && CCppMode) { + //FIXME: check to see if watchdogs work in CCppMode cases + errorReporter.reportError( + "Watchdogs are not currently supported in the CCpp target." + ); + } return true; } @@ -471,7 +478,7 @@ 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 - if (!isWatchdogCompatible()) return; + // if (!isWatchdogCompatible()) return; // Perform set up that does not generate code setUpGeneralParameters(); diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 3f82f20016..2767a05b9c 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -62,6 +62,7 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; +import org.lflang.lf.Watchdog; import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; import org.lflang.util.StringUtil; From 9e9aac73566ec2903440900b5049121d9e8aff17 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 14 Apr 2023 09:47:47 -0700 Subject: [PATCH 065/108] saving changes before merging --- org.lflang/src/org/lflang/ASTUtils.java | 3278 +++++++------- org.lflang/src/org/lflang/ast/IsEqual.java | 1203 +++-- org.lflang/src/org/lflang/ast/ToLf.java | 6 +- .../federated/generator/FederateInstance.java | 1121 +++-- .../org/lflang/generator/GeneratorBase.java | 1152 +++-- .../org/lflang/generator/ReactorInstance.java | 2108 +++++---- .../lflang/generator/WatchdogInstance.java | 84 +- .../org/lflang/generator/c/CGenerator.java | 3975 ++++++++--------- .../generator/c/CReactionGenerator.java | 2334 +++++----- .../generator/c/CWatchdogGenerator.java | 410 +- .../generator/python/PythonGenerator.java | 1093 +++-- .../lflang/scoping/LFScopeProviderImpl.java | 437 +- .../org/lflang/validation/LFValidator.java | 3343 +++++++------- test/C/src/file0.lf | 40 - test/C/src/file1.lf | 40 - test/C/src/file2.lf | 40 - 16 files changed, 10269 insertions(+), 10395 deletions(-) delete mode 100644 test/C/src/file0.lf delete mode 100644 test/C/src/file1.lf delete mode 100644 test/C/src/file2.lf diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index f9767e7e9a..46b913eee0 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -25,6 +25,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; @@ -39,7 +42,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; @@ -87,1730 +89,1652 @@ 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(), - 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); - } - } - } - 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)); - } - } - } + /** 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); } + } } - - 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(); + 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 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; + // 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 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()); - } - - /** - * 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()); - } - - /** - * 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()); - } - - /** - * 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)); - } + // 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)); } + } } - - // 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 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()); + } + + /** + * 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()); + } + + /** + * 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()); + } + + /** + * 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 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); - } - } - } - 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 - - // public static Code toCode(String text) { - // Code code = null; - // if (text == null) return code; - // code.setBody(text); - // return code; - // } - - /** - * 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()); + } + } + + // 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(); - } + break; // Insertion index is ok. } + } while (idx > 0); } - 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; - } - } 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; - } + } + } + 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 + + // public static Code toCode(String text) { + // Code code = null; + // if (text == null) return code; + // code.setBody(text); + // return code; + // } + + /** + * 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 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; + 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 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; + } + } 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; } - 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); + if (!ASTUtils.isZero(e)) { + foundNonZero = true; } - 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; - } - 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() + "."; + } + + 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; } - 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/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index e18eea02bd..f4bbc17f6d 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,626 +58,609 @@ 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.Watchdog; 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) - .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( + 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) + .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; + .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; } - - /** - * 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; - } - } + } + 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 be8a68cf7d..2f0ec76d9b 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -881,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() @@ -919,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 c0475ad37d..349ff0fff7 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -1,30 +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 com.google.common.base.Objects; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -34,9 +35,7 @@ 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.TargetConfig; @@ -66,597 +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 ac141b9259..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,598 +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; - - /** - * 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()); + //////////////////////////////////////////// + //// 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); + } } - - /** - * 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); + } + + /** + * 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); + } } - // ////////////////////////////////////////// - // // 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); - } - } + // Configure the command factory + commandFactory.setVerbose(); + if (Objects.equal(context.getMode(), LFGeneratorContext.Mode.STANDALONE) + && context.getArgs().containsKey("quiet")) { + commandFactory.setQuiet(); } - /** - * 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); - - // Check for the existence and support of watchdogs - hasWatchdogs = IterableExtensions.exists(reactors, it -> !it.getWatchdogs().isEmpty()); - checkWatchdogSupport(targetConfig.threading && getTarget() == Target.C); - additionalPostProcessingForModes(); + // 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); } - /** - * 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"); - } - } + // 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"); + } } - - /** - * 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); - } - } + } + + /** + * 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); } + } } - - /** - * 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(); + } + + /** + * 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<>(); } - - /* - * 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); + 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; } - - /** - * 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; - } - if (reactionBankIndices == null) { - reactionBankIndices = new LinkedHashMap<>(); - } - reactionBankIndices.put(reaction, bankIndex); + if (reactionBankIndices == null) { + reactionBankIndices = new LinkedHashMap<>(); } - - /** - * 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); + 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!"); } - - // ////////////////////////////////////////// - // // 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!"); - } + } + + /** + * 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."); } - - /** - * 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) { + } + + /** + * 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( - "Watchdogs are currently only supported for threaded programs in the C target." - ); + 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); + } } + } } - - - /** - * 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); - } - } - } - } - } - /** - * 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"; + } + /** + * 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; } - - /** - * 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; - } - } - - /** - * 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(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 3ab3607b2a..ba85ae8288 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,7 +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.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; @@ -68,1121 +66,1087 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 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. - * - * 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. + * 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. * * @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); - } - - ////////////////////////////////////////////////////// - //// 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); - } - return cachedReactionLoopGraph; + 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; + } } - - /** - * 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; + 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); } - - /** - * 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 : inputs) { + 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 (PortInstance port : outputs) { + port.clearCaches(); } - - /** - * 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; + for (ReactionInstance reaction : reactions) { + reaction.clearCaches(includingRuntimes); } - - /** - * 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); + cachedCycles = 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; - } - - /** - * 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(); + } + cachedCycles.addAll(reactions); + cachedCycles.addAll(ports); } - /** - * @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 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; + } } - - /** - * 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; + } + + /** + * 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"; } - - /** - * 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 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 startup trigger or null if not used in any reaction. - */ - public TriggerInstance getStartupTrigger() { - return builtinTriggers.get(BuiltinTrigger.STARTUP); + 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 the shutdown trigger or null if not used in any reaction. - */ - public TriggerInstance getShutdownTrigger() { - return builtinTriggers.get(BuiltinTrigger.SHUTDOWN); + 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; } - - /** - * 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 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. - * @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 result; + 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(); - /** - * 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); + @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 triggers; + return defaultValue; + } } - - /** - * 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 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 triggers; + } } - - /** - * Return true if the top-level parent of this reactor has causality cycles. - */ - public boolean hasCycles() { - return assignLevels().nodeCount() != 0; + 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(); } - - /** - * 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 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; + } } - - public Expression resolveParameters(Expression e) { - return LfExpressionVisitor.dispatch(e, this, ParameterInliner.INSTANCE); + 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; + } } - - - 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 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 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; + for (PortInstance portInstance : ports) { + if (portInstance.definition == port) { + return portInstance; + } } - - /** - * 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; + } + + /** + * 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; } - - /** - * 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()); + 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()); } - - /** - * 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 false; + } + + /** + * 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; + } } - - /////////////////////////////////////////////////// - //// 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; + 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; + } } - - /** - * 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; + 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; + } } - - /** - * 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()); - } else { - // Handle hierarchical reference - var containerInstance = getChildReactorInstance(reference.getContainer()); - if (containerInstance == null) return null; - return containerInstance.lookupPortInstance((Port) reference.getVariable()); - } - } - - /** - * 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; + 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 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; + } + + /** 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 null; + // 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); + } } - - /** - * 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; + } + + /** 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); + } } - - /** 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; - } + } + + /** 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)); + } + + //////////////////////////////////////// + //// 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; } - return null; - } + } + } while (currentParent != 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); + this.recursive = foundSelfAsParent; + if (recursive) { + reporter.reportError(definition, "Recursive reactor instantiation."); } - - ////////////////////////////////////////////////////// - //// 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); - } - } + + // 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; } - /** - * 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); - } - } + setInitialWidth(); + + // Apply overrides and instantiate parameters for this reactor instance. + for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { + this.parameters.add(new ParameterInstance(parameter, this)); } - /** - * 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)); + // Instantiate inputs for this reactor instance + for (Input inputDecl : ASTUtils.allInputs(reactorDefinition)) { + this.inputs.add(new PortInstance(inputDecl, this, reporter)); } - - //////////////////////////////////////// - //// 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)); + } - // Instantiate outputs for this reactor instance - for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { - this.outputs.add(new PortInstance(outputDecl, this, reporter)); + // 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(); + + // Create the reaction instances in this reactor instance. + createWatchdogInstances(); + + // 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(); + } + } + } + + ////////////////////////////////////////////////////// + //// 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); + } + + /** + * 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); + 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."); } - - // 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(); - - // Create the reaction instances in this reactor instance. - createWatchdogInstances(); - - // 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() { - 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 index 001246cf2f..ca5cb3d192 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -4,64 +4,60 @@ 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. - * + * 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; + /** 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; } - ////////////////////////////////////////////////////// - //// Public methods. + this.name = definition.getName().toString(); + this.definition = definition; + this.reactor = reactor; + } - public String getName() { - return this.name; - } + ////////////////////////////////////////////////////// + //// Public methods. - public Watchdog getDefinition() { - return this.definition; - } + public String getName() { + return this.name; + } - public TimeValue getTimeout() { - return (TimeValue) this.timeout; - } + public Watchdog getDefinition() { + return this.definition; + } - public ReactorInstance getReactor() { - return this.reactor; - } + public TimeValue getTimeout() { + return (TimeValue) this.timeout; + } - @Override - public String toString() { - return "WatchdogInstance " + name + "(" + timeout.toString() + ")"; - } + public ReactorInstance getReactor() { + return this.reactor; + } + + @Override + public String toString() { + return "WatchdogInstance " + name + "(" + timeout.toString() + ")"; + } - ////////////////////////////////////////////////////// - //// Private fields. + ////////////////////////////////////////////////////// + //// Private fields. - private final TimeValue timeout; + private final TimeValue timeout; - private final String name; + private final String name; - private final Watchdog definition; + private final Watchdog definition; - private final ReactorInstance reactor; + 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 07d77cdbd5..0ded0e2bc2 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1,26 +1,26 @@ /************* -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; @@ -36,6 +36,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,44 +47,36 @@ 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.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; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; - -import org.lflang.generator.WatchdogInstance; - 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; @@ -97,209 +91,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.util.ArduinoUtil; import org.lflang.lf.Watchdog; +import org.lflang.util.ArduinoUtil; import org.lflang.util.FileUtil; -import org.lflang.lf.Watchdog; -import org.lflang.util.LFCommand; - -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 @@ -314,1136 +271,1093 @@ 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; - 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)); + // 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; + } } - - /** - * 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; - } - } - } - } + return true; + } + + /** + * Returns false if watchdogs exist and are unsupported in this context. Otherwise, return true. + * (DEPRECATED) Alternative implemented in GeneratorBase + */ + protected boolean isWatchdogCompatible() { + if (hasWatchdogs() && !targetConfig.threading) { + errorReporter.reportError("Watchdogs are not supported for unthreaded programs."); + return false; } - - /** - * 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; + if (hasWatchdogs() && CCppMode) { + // FIXME: check to see if watchdogs work in CCppMode cases + errorReporter.reportError("Watchdogs are not currently supported in the CCpp target."); } + 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 + // if (!isWatchdogCompatible()) return; - /** Returns false if watchdogs exist and are - * unsupported in this context. - * Otherwise, return true. - * (DEPRECATED) Alternative implemented in GeneratorBase - */ - protected boolean isWatchdogCompatible() { - if (hasWatchdogs() && !targetConfig.threading) { - errorReporter.reportError( - "Watchdogs are not supported for unthreaded programs." - ); - return false; - } - if (hasWatchdogs() && CCppMode) { - //FIXME: check to see if watchdogs work in CCppMode cases - errorReporter.reportError( - "Watchdogs are not currently supported in the CCpp target." - ); - } - return true; - } + // Perform set up that does not generate code + setUpGeneralParameters(); - /** - * 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 - // if (!isWatchdogCompatible()) return; - - // Perform set up that does not generate code - setUpGeneralParameters(); - - FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); - FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); - handleProtoFiles(); - - var lfModuleName = fileConfig.name; - generateCodeFor(lfModuleName); - - // Derive target filename from the .lf filename. - var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); - var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; - - try { - - String srcPrefix = targetConfig.platformOptions.platform == Platform.ARDUINO ? "src/" : ""; - - // Copy the core lib - FileUtil.copyDirectoryFromClassPath( - "/lib/c/reactor-c/core", - fileConfig.getSrcGenPath().resolve(srcPrefix + "core"), - true - ); - // Copy the C target files - copyTargetFiles(); - - // 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 - ); - } - - // Write the generated code - code.writeToFile(targetFile); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); + handleProtoFiles(); - // 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); - } - } + var lfModuleName = fileConfig.name; + generateCodeFor(lfModuleName); - // If cmake is requested, generate the CMakeLists.txt - if (targetConfig.platformOptions.platform != Platform.ARDUINO) { - var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; - var cmakeCode = cmakeGenerator.generateCMakeCode( - List.of(cFilename), - lfModuleName, - errorReporter, - CCppMode, - mainDef != null, - cMakeExtras, - targetConfig - ); - try { - cmakeCode.writeToFile(cmakeFile); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - } else { - try { - FileUtil.arduinoDeleteHelper(fileConfig.getSrcGenPath().resolve("src/"), targetConfig.threading); - FileUtil.relativeIncludeHelper(fileConfig.getSrcGenPath().resolve("src/")); - } 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; - } + // Derive target filename from the .lf filename. + var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); + var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; - // 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); - } + try { - // 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); - } + String srcPrefix = targetConfig.platformOptions.platform == Platform.ARDUINO ? "src/" : ""; - } + // Copy the core lib + FileUtil.copyDirectoryFromClassPath( + "/lib/c/reactor-c/core", fileConfig.getSrcGenPath().resolve(srcPrefix + "core"), true); + // Copy the C target files + copyTargetFiles(); - // 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)); - } - - // In case we are in Eclipse, make sure the generated code is visible. - GeneratorUtils.refreshProject(resource, context.getMode()); + // 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); + } + + // Write the generated code + code.writeToFile(targetFile); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); } - private void generateCodeFor( - String lfModuleName - ) { - startTimeStepIsPresentCount = 0; - code.pr(generateDirectives()); - code.pr(generateTopLevelPreambles()); - 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 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. - // needs to be implemented still - 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 - )); - } + // 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); + } } - @Override - public void checkModalReactorSupport(boolean __) { - // Modal reactors are currently only supported for non federated applications - super.checkModalReactorSupport(true); + // If cmake is requested, generate the CMakeLists.txt + if (targetConfig.platformOptions.platform != Platform.ARDUINO) { + var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; + var cmakeCode = + cmakeGenerator.generateCMakeCode( + List.of(cFilename), + lfModuleName, + errorReporter, + CCppMode, + mainDef != null, + cMakeExtras, + targetConfig); + try { + cmakeCode.writeToFile(cmakeFile); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); + } + } else { + try { + FileUtil.arduinoDeleteHelper( + fileConfig.getSrcGenPath().resolve("src/"), targetConfig.threading); + FileUtil.relativeIncludeHelper(fileConfig.getSrcGenPath().resolve("src/")); + } 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; } - @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);" - ); + // 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); } - /** 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; - } - } + // 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); + } } - private boolean hasDeadlines(List reactors) { - for (Reactor reactor : reactors) { - for (Reaction reaction : allReactions(reactor)) { - if (reaction.getDeadline() != null) { - return true; - } - } - } - return false; + // 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)); } - private boolean hasWatchdogs() { - for (Reactor reactor : reactors) { - List watchdogs = ASTUtils.allWatchdogs(reactor); - if (watchdogs != null && !watchdogs.isEmpty()) { - return true; - } - } - return false; + // In case we are in Eclipse, make sure the generated code is visible. + GeneratorUtils.refreshProject(resource, context.getMode()); + } + + private void generateCodeFor(String lfModuleName) { + startTimeStepIsPresentCount = 0; + code.pr(generateDirectives()); + code.pr(generateTopLevelPreambles()); + 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 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. + // needs to be implemented still + 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)); } - - /** - * 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()); - } - // Extract the contents of the imported file for the preambles - var contents = toDefinition(reactor).eResource().getContents(); - var model = (Model) contents.get(0); - // Add the preambles from the imported .lf file - toDefinition(reactor).getPreambles().addAll(model.getPreambles()); + } + + @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; } + } } + } - /** - * 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); - } + private boolean hasDeadlines(List reactors) { + for (Reactor reactor : reactors) { + for (Reaction reaction : allReactions(reactor)) { + if (reaction.getDeadline() != null) { + return true; } + } } + return 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() { - var generatedReactorDecls = new LinkedHashSet(); - if (this.main != null) { - generateReactorChildren(this.main, generatedReactorDecls); - } - - if (this.mainDef != null) { - generateReactorClass(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); - } - } - } + private boolean hasWatchdogs() { + for (Reactor reactor : reactors) { + List watchdogs = ASTUtils.allWatchdogs(reactor); + if (watchdogs != null && !watchdogs.isEmpty()) { + return true; + } } - - /** - * 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 generatedReactorDecls - ) { - for (ReactorInstance r : reactor.children) { - if (r.reactorDeclaration != null && - !generatedReactorDecls.contains(r.reactorDeclaration)) { - generatedReactorDecls.add(r.reactorDeclaration); - generateReactorChildren(r, generatedReactorDecls); - inspectReactorEResource(r.reactorDeclaration); - generateReactorClass(r.reactorDeclaration); - } + 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()); + } + // Extract the contents of the imported file for the preambles + var contents = toDefinition(reactor).eResource().getContents(); + var model = (Model) contents.get(0); + // Add the preambles from the imported .lf file + toDefinition(reactor).getPreambles().addAll(model.getPreambles()); } - - /** - * 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"); - } + } + + /** + * 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); } - - /** - * Copy target-specific header file to the src-gen directory. - */ - protected void copyTargetFiles() throws IOException { - - String srcPrefix = targetConfig.platformOptions.platform == Platform.ARDUINO ? "src/" : ""; - - FileUtil.copyDirectoryFromClassPath( - "/lib/c/reactor-c/include", - fileConfig.getSrcGenPath().resolve(srcPrefix + "include"), - false - ); - FileUtil.copyDirectoryFromClassPath( - "/lib/c/reactor-c/lib", - fileConfig.getSrcGenPath().resolve(srcPrefix + "lib"), - false - ); + 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); + } } - //////////////////////////////////////////// - //// 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(ReactorDecl reactor) { - // FIXME: Currently we're not reusing definitions for declarations that point to the same definition. - - Reactor defn = ASTUtils.toDefinition(reactor); - - if (reactor instanceof Reactor) { - code.pr("// =============== START reactor class " + reactor.getName()); - } else { - code.pr("// =============== START reactor class " + defn.getName() + " as " + reactor.getName()); - } - - // Preamble code contains state declarations with static initializers. - generateUserPreamblesForReactor(defn); - - // 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(reactor); - generateSelfStruct(reactor, constructorCode); - generateMethods(reactor); - - generateWatchdogs(reactor); - generateReactions(reactor); - generateConstructor(reactor, constructorCode); + 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); + } + } - code.pr("// =============== END reactor class " + reactor.getName()); - code.pr(""); + 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 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() { + var generatedReactorDecls = new LinkedHashSet(); + if (this.main != null) { + generateReactorChildren(this.main, generatedReactorDecls); } - /** - * Generate methods for {@code reactor}. - */ - protected void generateMethods(ReactorDecl reactor) { - CMethodGenerator.generateMethods(reactor, code, types); + if (this.mainDef != null) { + generateReactorClass(this.mainDef.getReactorClass()); } - /** - * Generates preambles defined by user for a given reactor - * @param reactor The given reactor - */ - protected void generateUserPreamblesForReactor(Reactor reactor) { - for (Preamble p : convertToEmptyListIfNull(reactor.getPreambles())) { - code.pr("// *********** From the preamble, verbatim:"); - code.prSourceLineNumber(p.getCode()); - code.pr(toText(p.getCode())); - code.pr("\n// *********** End of preamble."); + 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); } + } } - - /** - * 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( - ReactorDecl reactor, CodeBuilder constructorCode - ) { - code.pr(CConstructorGenerator.generateConstructor( - reactor, - constructorCode.toString() - )); + } + + /** + * 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 generatedReactorDecls) { + for (ReactorInstance r : reactor.children) { + if (r.reactorDeclaration != null && !generatedReactorDecls.contains(r.reactorDeclaration)) { + generatedReactorDecls.add(r.reactorDeclaration); + generateReactorChildren(r, generatedReactorDecls); + inspectReactorEResource(r.reactorDeclaration); + generateReactorClass(r.reactorDeclaration); + } + } + } + + /** + * 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"); + } + } + + /** Copy target-specific header file to the src-gen directory. */ + protected void copyTargetFiles() throws IOException { + + String srcPrefix = targetConfig.platformOptions.platform == Platform.ARDUINO ? "src/" : ""; + + FileUtil.copyDirectoryFromClassPath( + "/lib/c/reactor-c/include", + fileConfig.getSrcGenPath().resolve(srcPrefix + "include"), + false); + FileUtil.copyDirectoryFromClassPath( + "/lib/c/reactor-c/lib", fileConfig.getSrcGenPath().resolve(srcPrefix + "lib"), false); + } + + //////////////////////////////////////////// + //// 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(ReactorDecl reactor) { + // FIXME: Currently we're not reusing definitions for declarations that point to the same + // definition. + + Reactor defn = ASTUtils.toDefinition(reactor); + + if (reactor instanceof Reactor) { + code.pr("// =============== START reactor class " + reactor.getName()); + } else { + code.pr( + "// =============== START reactor class " + defn.getName() + " as " + reactor.getName()); } - /** - * Generate the struct type definitions for inputs, outputs, and - * actions of the specified reactor. - * @param decl The parsed reactor data structure. - */ - protected void generateAuxiliaryStructs(ReactorDecl decl) { - var reactor = ASTUtils.toDefinition(decl); - // 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(""" + // Preamble code contains state declarations with static initializers. + generateUserPreamblesForReactor(defn); + + // 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(reactor); + generateSelfStruct(reactor, constructorCode); + generateMethods(reactor); + + generateWatchdogs(reactor); + generateReactions(reactor); + generateConstructor(reactor, constructorCode); + + code.pr("// =============== END reactor class " + reactor.getName()); + code.pr(""); + } + + /** Generate methods for {@code reactor}. */ + protected void generateMethods(ReactorDecl reactor) { + CMethodGenerator.generateMethods(reactor, code, types); + } + + /** + * Generates preambles defined by user for a given reactor + * + * @param reactor The given reactor + */ + protected void generateUserPreamblesForReactor(Reactor reactor) { + for (Preamble p : convertToEmptyListIfNull(reactor.getPreambles())) { + code.pr("// *********** From the preamble, verbatim:"); + code.prSourceLineNumber(p.getCode()); + code.pr(toText(p.getCode())); + code.pr("\n// *********** End of preamble."); + } + } + + /** + * 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(ReactorDecl reactor, CodeBuilder constructorCode) { + code.pr(CConstructorGenerator.generateConstructor(reactor, constructorCode.toString())); + } + + /** + * Generate the struct type definitions for inputs, outputs, and actions of the specified reactor. + * + * @param decl The parsed reactor data structure. + */ + protected void generateAuxiliaryStructs(ReactorDecl decl) { + var reactor = ASTUtils.toDefinition(decl); + // 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()) - ); - // First, handle inputs. - for (Input input : allInputs(reactor)) { - code.pr(CPortGenerator.generateAuxiliaryStruct( - decl, - input, - getTarget(), - errorReporter, - types, - federatedExtension - )); - } - // Next, handle outputs. - for (Output output : allOutputs(reactor)) { - code.pr(CPortGenerator.generateAuxiliaryStruct( - decl, - output, - getTarget(), - errorReporter, - types, - federatedExtension - )); - } - // Finally, handle actions. - // 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(reactor)) { - code.pr(CActionGenerator.generateAuxiliaryStruct( - decl, - action, - getTarget(), - types, - federatedExtension - )); - } + """ + .formatted(types.getTargetTagType(), types.getTargetTimeType())); + // First, handle inputs. + for (Input input : allInputs(reactor)) { + code.pr( + CPortGenerator.generateAuxiliaryStruct( + decl, input, getTarget(), errorReporter, types, federatedExtension)); } - - /** - * 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(ReactorDecl decl, CodeBuilder constructorCode) { - var reactor = toDefinition(decl); - var selfType = CUtil.selfType(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, decl, 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, - decl, - constructorCode, - types - ); - - // Generate the fields needed for each watchdog. - CWatchdogGenerator.generateWatchdogStruct( - body, - decl, - constructorCode - ); - - // Next, generate fields for modes - CModesGenerator.generateDeclarations(reactor, body, constructorCode); - - // constructorCode.pr(reactor, String.join("\n", - // "#ifdef LF_THREADED", - // " self->base->watchdog_mutex = NULL;", - // "#endif")); - - // 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. - code.pr("typedef struct {"); - code.indent(); - code.pr("struct self_base_t base;"); - code.pr(body.toString()); - code.unindent(); - code.pr("} " + selfType + ";"); + // Next, handle outputs. + for (Output output : allOutputs(reactor)) { + code.pr( + CPortGenerator.generateAuxiliaryStruct( + decl, output, getTarget(), errorReporter, types, federatedExtension)); } - - /** - * 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()) { - // 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, containedReactor.getReactorClass())+" "+port.getName()+";"); - } else { - // Is a multiport. - // Memory will be malloc'd in initialization. - body.pr(port, String.join("\n", - variableStructType(port, containedReactor.getReactorClass())+"** "+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, containedReactor.getReactorClass())+"* "+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, containedReactor.getReactorClass())+"** "+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("}"); - } - } + // Finally, handle actions. + // 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(reactor)) { + code.pr( + CActionGenerator.generateAuxiliaryStruct( + decl, action, getTarget(), types, federatedExtension)); + } + } + + /** + * 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(ReactorDecl decl, CodeBuilder constructorCode) { + var reactor = toDefinition(decl); + var selfType = CUtil.selfType(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, decl, 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, decl, constructorCode, types); + + // Generate the fields needed for each watchdog. + CWatchdogGenerator.generateWatchdogStruct(body, decl, constructorCode); + + // Next, generate fields for modes + CModesGenerator.generateDeclarations(reactor, body, constructorCode); + + // constructorCode.pr(reactor, String.join("\n", + // "#ifdef LF_THREADED", + // " self->base->watchdog_mutex = NULL;", + // "#endif")); + + // 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. + code.pr("typedef struct {"); + code.indent(); + code.pr("struct self_base_t base;"); + code.pr(body.toString()); + code.unindent(); + code.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()) { + // 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, containedReactor.getReactorClass()) + + " " + + port.getName() + + ";"); + } else { + // Is a multiport. + // Memory will be malloc'd in initialization. + body.pr( + port, + String.join( + "\n", + variableStructType(port, containedReactor.getReactorClass()) + + "** " + + 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, containedReactor.getReactorClass()) + + "* " + + 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, containedReactor.getReactorClass()) + + "** " + + 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 + } + + /** + * 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 decl The reactor. + */ + public void generateReactions(ReactorDecl decl) { + var reactionIndex = 0; + var reactor = ASTUtils.toDefinition(decl); + for (Reaction reaction : allReactions(reactor)) { + generateReaction(reaction, decl, reactionIndex); + // Increment reaction index even if the reaction is not in the federate + // so that across federates, the reaction indices are consistent. + reactionIndex++; } - - /** 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 decl The reactor. - */ - public void generateReactions(ReactorDecl decl) { - var reactionIndex = 0; - var reactor = ASTUtils.toDefinition(decl); - for (Reaction reaction : allReactions(reactor)) { - generateReaction(reaction, decl, 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 decl The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { - - code.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 decl The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { + + code.pr( + CReactionGenerator.generateReaction( reaction, decl, reactionIndex, @@ -1451,793 +1365,834 @@ protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactio 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(ReactorDecl decl) { + // WATCHDOG QUESTION: A similar question is asked somewhere else for a + // different function - Do we need to check if this federate contains the + // watchdog? This is done in the code generation for reactions. + var reactor = ASTUtils.toDefinition(decl); + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + generateWatchdog(watchdog, decl); } - - /** 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(ReactorDecl decl) { - // WATCHDOG QUESTION: A similar question is asked somewhere else for a - // different function - Do we need to check if this federate contains the - // watchdog? This is done in the code generation for reactions. - var reactor = ASTUtils.toDefinition(decl); - for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - generateWatchdog(watchdog, decl); + } + + /** + * 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(Watchdog watchdog, ReactorDecl decl) { + code.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 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(Watchdog watchdog, ReactorDecl decl) { - code.pr(CWatchdogGenerator.generateWatchdog( - watchdog, - decl - )); + } + + private void recordWatchdogs(ReactorInstance instance) { + var foundOne = false; + var temp = new CodeBuilder(); + var reactorRef = CUtil.reactorRef(instance); + // temp.pr("#ifdef LF_THREADED"); + for (WatchdogInstance watchdog : instance.watchdogs) { + 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(watchdog.getTimeout()) + + ";"); + temp.pr(" " + reactorRef + "->_lf_watchdog_" + watchdog.getName() + ".thread_id;"); + watchdogCount += 1; + foundOne = true; } - - /** - * 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()); - } + // temp.pr("#endif"); + if (foundOne) { + initializeTriggerObjects.pr(temp.toString()); } - - private void recordWatchdogs(ReactorInstance instance) { - var foundOne = false; - var temp = new CodeBuilder(); - var reactorRef = CUtil.reactorRef(instance); - // temp.pr("#ifdef LF_THREADED"); - for (WatchdogInstance watchdog : instance.watchdogs) { - 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(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; + + 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; + // Next, set up the table to mark each output of each contained reactor absent. + for (ReactorInstance child : instance.children) { + if (child.outputs.size() > 0) { - /** - * 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; + temp.startScopedBlock(); + temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); + temp.startScopedBlock(child); - for (ActionInstance action : instance.actions) { + var channelCount = 0; + for (PortInstance output : child.outputs) { + if (!output.getDependsOnReactions().isEmpty()) { 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("// 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(); + 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()); - 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(); - } - } - 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)}. - * @param variable The variable. - * @param reactor The reactor class. - * @return The name of the self struct. - */ - public static String variableStructType(Variable variable, ReactorDecl reactor) { - return reactor.getName().toLowerCase()+"_"+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, ReactorDecl)}. - * @param portOrAction The port or action instance. - * @return The name of the self struct. - */ - public static String variableStructType(TriggerInstance portOrAction) { - return portOrAction.getParent().reactorDeclaration.getName().toLowerCase()+"_"+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)}. + * + * @param variable The variable. + * @param reactor The reactor class. + * @return The name of the self struct. + */ + public static String variableStructType(Variable variable, ReactorDecl reactor) { + return reactor.getName().toLowerCase() + "_" + 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, + * ReactorDecl)}. + * + * @param portOrAction The port or action instance. + * @return The name of the self struct. + */ + public static String variableStructType(TriggerInstance portOrAction) { + return portOrAction.getParent().reactorDeclaration.getName().toLowerCase() + + "_" + + 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 = 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_" + + reactorClass.getName() + + "();"); + // 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 = 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_"+reactorClass.getName()+"();"); - // 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(); + // 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 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 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 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 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 initialize modes. - * @param instance The reactor instance. - */ - private void generateModeStructure(ReactorInstance instance) { - CModesGenerator.generateModeStructure(instance, initializeTriggerObjects); - if (!instance.modes.isEmpty()) { - modalReactorCount += instance.getTotalWidth(); - } + } + + /** + * 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 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 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 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 - )); - } + } + + /** + * 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)); } - - /** - * 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 - )); - } + } + + @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"); } - - @Override - public TargetTypes getTargetTypes() { - return types; + 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; } - /** - * - * @param context - * @return - */ - protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { - return new CDockerGenerator(context); + 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; } - - // ////////////////////////////////////////// - // // 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(); + 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)); } - - -// // Perform set up that does not generate code -// protected void setUpFederateSpecificParameters(FederateInstance federate, CodeBuilder commonCode) { -// currentFederate = federate; -// if (isFederated) { -// // Reset the cmake-includes and files, to be repopulated for each federate individually. -// // This is done to enable support for separately -// // adding cmake-includes/files for different federates to prevent linking and mixing -// // all federates' supporting libraries/files together. -// targetConfig.cmakeIncludes.clear(); -// targetConfig.cmakeIncludesWithoutPath.clear(); -// targetConfig.fileNames.clear(); -// targetConfig.filesNamesWithoutPath.clear(); -// -// // Re-apply the cmake-include target property of the main .lf file. -// var target = GeneratorUtils.findTarget(mainDef.getReactorClass().eResource()); -// if (target.getConfig() != null) { -// // Update the cmake-include -// TargetProperty.updateOne( -// this.targetConfig, -// TargetProperty.CMAKE_INCLUDE, -// convertToEmptyListIfNull(target.getConfig().getPairs()), -// errorReporter -// ); -// // Update the files -// TargetProperty.updateOne( -// this.targetConfig, -// TargetProperty.FILES, -// convertToEmptyListIfNull(target.getConfig().getPairs()), -// errorReporter -// ); -// } -// // Clear out previously generated code. -// code = new CodeBuilder(commonCode); -// initializeTriggerObjects = new CodeBuilder(); -// // Enable clock synchronization if the federate -// // is not local and clock-sync is enabled -// initializeClockSynchronization(); -// startTimeStep = new CodeBuilder(); -// } -// } - - protected void handleProtoFiles() { - // Handle .proto files. - for (String file : targetConfig.protoFiles) { - this.processProtoFile(file); - var dotIndex = file.lastIndexOf("."); - var rootFilename = file; - if (dotIndex > 0) { - rootFilename = file.substring(0, dotIndex); - } - code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); - } + pickCompilePlatform(); + } + + // // Perform set up that does not generate code + // protected void setUpFederateSpecificParameters(FederateInstance federate, CodeBuilder + // commonCode) { + // currentFederate = federate; + // if (isFederated) { + // // Reset the cmake-includes and files, to be repopulated for each federate + // individually. + // // This is done to enable support for separately + // // adding cmake-includes/files for different federates to prevent linking and mixing + // // all federates' supporting libraries/files together. + // targetConfig.cmakeIncludes.clear(); + // targetConfig.cmakeIncludesWithoutPath.clear(); + // targetConfig.fileNames.clear(); + // targetConfig.filesNamesWithoutPath.clear(); + // + // // Re-apply the cmake-include target property of the main .lf file. + // var target = GeneratorUtils.findTarget(mainDef.getReactorClass().eResource()); + // if (target.getConfig() != null) { + // // Update the cmake-include + // TargetProperty.updateOne( + // this.targetConfig, + // TargetProperty.CMAKE_INCLUDE, + // convertToEmptyListIfNull(target.getConfig().getPairs()), + // errorReporter + // ); + // // Update the files + // TargetProperty.updateOne( + // this.targetConfig, + // TargetProperty.FILES, + // convertToEmptyListIfNull(target.getConfig().getPairs()), + // errorReporter + // ); + // } + // // Clear out previously generated code. + // code = new CodeBuilder(commonCode); + // initializeTriggerObjects = new CodeBuilder(); + // // Enable clock synchronization if the federate + // // is not local and clock-sync is enabled + // initializeClockSynchronization(); + // startTimeStep = new CodeBuilder(); + // } + // } + + protected void handleProtoFiles() { + // Handle .proto files. + for (String file : targetConfig.protoFiles) { + this.processProtoFile(file); + var dotIndex = file.lastIndexOf("."); + var rootFilename = file; + if (dotIndex > 0) { + rootFilename = file.substring(0, dotIndex); + } + code.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(); + } + + /** + * 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() { + CodeBuilder code = new CodeBuilder(); + + // preamble for federated execution setup + if (targetConfig.fedSetupPreamble != null) { + if (targetLanguageIsCpp()) code.pr("extern \"C\" {"); + code.pr("#include \"" + targetConfig.fedSetupPreamble + "\""); + if (targetLanguageIsCpp()) code.pr("}"); } - /** - * Generate top-level preamble code. - */ - protected String generateTopLevelPreambles() { - CodeBuilder code = new CodeBuilder(); - - // preamble for federated execution setup - if (targetConfig.fedSetupPreamble != null) { - if (targetLanguageIsCpp()) code.pr("extern \"C\" {"); - code.pr("#include \"" + targetConfig.fedSetupPreamble + "\""); - if (targetLanguageIsCpp()) code.pr("}"); - } - - // user preambles - if (this.mainDef != null) { - var mainModel = (Model) toDefinition(mainDef.getReactorClass()).eContainer(); - for (Preamble p : mainModel.getPreambles()) { - code.pr(toText(p.getCode())); - } - } - return code.toString(); + // user preambles + if (this.mainDef != null) { + var mainModel = (Model) toDefinition(mainDef.getReactorClass()).eContainer(); + for (Preamble p : mainModel.getPreambles()) { + code.pr(toText(p.getCode())); + } } - - protected boolean targetLanguageIsCpp() { - return CCppMode; + return code.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; } - - /** 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; + 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; } - 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()) - ); - } - } + if (hasDeadlines) { + this.main.assignDeadlines(); } - - } - - /** - * 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); + // 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); } + } } diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 0a1df14302..b7f72e216a 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; @@ -38,1158 +37,1335 @@ import org.lflang.util.StringUtil; public class CReactionGenerator { - protected static String DISABLE_REACTION_INITIALIZATION_MARKER - = "// **** Do not include initialization code in this reaction."; + protected static String DISABLE_REACTION_INITIALIZATION_MARKER = + "// **** Do not include initialization code in this reaction."; - /** - * 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, - ReactorDecl decl, - int reactionIndex, - CTypes types, - ErrorReporter errorReporter, - Instantiation mainDef, - boolean requiresTypes) { - Reactor reactor = ASTUtils.toDefinition(decl); + /** + * 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, + ReactorDecl decl, + int reactionIndex, + CTypes types, + ErrorReporter errorReporter, + Instantiation mainDef, + boolean requiresTypes) { + Reactor reactor = ASTUtils.toDefinition(decl); - // Construct the reactionInitialization code to go into - // the body of the function before the verbatim code. - CodeBuilder reactionInitialization = new CodeBuilder(); + // Construct the reactionInitialization code to go into + // the body of the function before the verbatim code. + CodeBuilder reactionInitialization = new CodeBuilder(); - CodeBuilder code = new CodeBuilder(); + CodeBuilder code = 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);" - )); - } + // 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);")); + } - // 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(); - } + // 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(); + } - // 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<>(); + // 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<>(); - // 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<>(); + // 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<>(); - // 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()); - } + // 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()); } - - // 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)+"* "+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." - ); - } - } - } - } - // 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) + + "* " + + 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, definition.getReactorClass()); - 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. - * @param port The port. - * @param decl The reactor or import statement. - */ - private static void generatePortVariablesInReaction( - CodeBuilder builder, - Map structs, - VarRef port, - ReactorDecl decl, - CTypes types - ) { - if (port.getVariable() instanceof Input) { - builder.pr(generateInputVariablesInReaction((Input) port.getVariable(), decl, types)); - } else { - // port is an output of a contained reactor. - Output output = (Output) port.getVariable(); - String portStructType = CGenerator.variableStructType(output, port.getContainer().getReactorClass()); + /** + * 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. - * @param decl The reactor. - */ - private static String generateActionVariablesInReaction( - Action action, - ReactorDecl decl, - CTypes types - ) { - String structType = CGenerator.variableStructType(action, decl); - // 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, definition.getReactorClass()); + 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 decl The reactor. - */ - private static String generateInputVariablesInReaction( - Input input, - ReactorDecl decl, - CTypes types - ) { - String structType = CGenerator.variableStructType(input, decl); - InferredType inputType = ASTUtils.getInferredType(input); - CodeBuilder builder = new CodeBuilder(); - String inputName = input.getName(); - String inputWidth = generateWidthVariable(inputName); - - // 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(); - } + /** + * 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. + * @param port The port. + * @param decl The reactor or import statement. + */ + private static void generatePortVariablesInReaction( + CodeBuilder builder, + Map structs, + VarRef port, + ReactorDecl decl, + CTypes types) { + if (port.getVariable() instanceof Input) { + builder.pr(generateInputVariablesInReaction((Input) port.getVariable(), decl, types)); + } else { + // port is an output of a contained reactor. + Output output = (Output) port.getVariable(); + String portStructType = + CGenerator.variableStructType(output, port.getContainer().getReactorClass()); - /** - * 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 decl The reactor containing the reaction or the import statement. - */ - public static String generateOutputVariablesInReaction( - VarRef effect, - ReactorDecl decl, - 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, decl) - : - CGenerator.variableStructType(output, effect.getContainer().getReactorClass()); - 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;" - ); + 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 + + ";"); } + } } + } - /** - * 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(); + /** + * Generate action variables for a reaction. + * + * @param action The action. + * @param decl The reactor. + */ + private static String generateActionVariablesInReaction( + Action action, ReactorDecl decl, CTypes types) { + String structType = CGenerator.variableStructType(action, decl); + // 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(); - return String.join("\n", List.of( - "watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");" - )); + 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 decl The reactor. - * @param constructorCode The place to put the constructor code. - */ - public static void generateReactionAndTriggerStructs( - CodeBuilder body, - ReactorDecl decl, - CodeBuilder constructorCode, - CTypes types - ) { - var reactionCount = 0; - var reactor = ASTUtils.toDefinition(decl); - // 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(); - // WATCHDOG QUESTION: Why need to grab all reactions from reactor only to check - // if it exists in currentFederate? - 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 decl The reactor. + */ + private static String generateInputVariablesInReaction( + Input input, ReactorDecl decl, CTypes types) { + String structType = CGenerator.variableStructType(input, decl); + 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(decl, 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 decl The reactor containing the reaction or the import statement. + */ + public static String generateOutputVariablesInReaction( + VarRef effect, ReactorDecl decl, 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, decl) + : CGenerator.variableStructType(output, effect.getContainer().getReactorClass()); + 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(decl, 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(decl, 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 decl The reactor. + * @param constructorCode The place to put the constructor code. + */ + public static void generateReactionAndTriggerStructs( + CodeBuilder body, ReactorDecl decl, CodeBuilder constructorCode, CTypes types) { + var reactionCount = 0; + var reactor = ASTUtils.toDefinition(decl); + // 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(); + // WATCHDOG QUESTION: Why need to grab all reactions from reactor only to check + // if it exists in currentFederate? + 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(decl, 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 + ";" - )); - } - - // Next handle inputs. - for (Input input : ASTUtils.allInputs(reactor)) { - createTriggerT(body, input, triggerMap, constructorCode, types); - } + // Assign the STP handler + var STPFunctionPointer = "NULL"; + if (reaction.getStp() != null) { + // The following has to match the name chosen in generateReactions + var STPFunctionName = generateStpFunctionName(decl, reactionCount); + STPFunctionPointer = "&" + STPFunctionName; + } - // Next handle watchdogs. - for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - createTriggerT(body, watchdog, 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(decl, 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, Watchdog, 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); + } + if (resetReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(resetReactions, "reset", 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(); + // 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_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 inputs. + for (Input input : ASTUtils.allInputs(reactor)) { + createTriggerT(body, input, triggerMap, constructorCode, types); } - /** - * 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 watchdogs. + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + createTriggerT(body, watchdog, 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 decl The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - public static String generateReaction( - Reaction reaction, - ReactorDecl decl, - int reactionIndex, - Instantiation mainDef, - ErrorReporter errorReporter, - CTypes types, - TargetConfig targetConfig, - boolean requiresType - ) { - var code = new CodeBuilder(); - var body = ASTUtils.toText(reaction.getCode()); - String init = generateInitializationForReaction( - body, reaction, decl, reactionIndex, - types, errorReporter, mainDef, - requiresType); - - String srcPrefix = targetConfig.platformOptions.platform == Platform.ARDUINO ? "src/" : ""; - code.pr( - "#include " + StringUtil.addDoubleQuotes( - srcPrefix + CCoreFilesUtils.getCTargetSetHeader())); - - CMethodGenerator.generateMacrosForMethods(ASTUtils.toDefinition(decl), code); - code.pr(generateFunction( - generateReactionFunctionHeader(decl, reactionIndex), - init, reaction.getCode() - )); - // 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(decl, reactionIndex), - init, reaction.getStp().getCode())); - } + /** + * 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, Watchdog, 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};")); - // Now generate code for the deadline violation function, if there is one. - if (reaction.getDeadline() != null) { - code.pr(generateFunction( - generateDeadlineFunctionHeader(decl, reactionIndex), - init, reaction.getDeadline().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 + ";")); - CMethodGenerator.generateMacroUndefsForMethods(ASTUtils.toDefinition(decl), code); - code.pr( - "#include " + StringUtil.addDoubleQuotes( - srcPrefix + 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 + ")"; - 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(); + constructorCode.pr("self->_lf__" + varName + ".tmplt.type.element_size = " + size + ";"); + body.pr( + CExtensionUtils.surroundWithIfFederated( + CExtensionUtils.createPortStatusFieldForInput((Input) variable))); } + } - /** - * Returns the name of the deadline function for reaction. - * @param decl The reactor with the deadline - * @param reactionIndex The number assigned to this reaction deadline - */ - public static String generateDeadlineFunctionName(ReactorDecl decl, int reactionIndex) { - return CUtil.getName(decl).toLowerCase() + "_deadline_function" + reactionIndex; + 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;")); + } - /** - * 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(ReactorDecl reactor, int reactionIndex) { - return CUtil.getName(reactor).toLowerCase() + "reaction_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 + ";")); + } - /** - * Returns the name of the stp function for reaction. - * @param decl The reactor with the stp - * @param reactionIndex The number assigned to this reaction deadline - */ - public static String generateStpFunctionName(ReactorDecl decl, int reactionIndex) { - return CUtil.getName(decl).toLowerCase() + "_STP_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(); + } - /** Return the top level C function header for the deadline function numbered "reactionIndex" in "decl" - * @param decl The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the deadline function. - */ - public static String generateDeadlineFunctionHeader(ReactorDecl decl, - int reactionIndex) { - String functionName = generateDeadlineFunctionName(decl, reactionIndex); - return generateFunctionHeader(functionName); + /** 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 reaction numbered "reactionIndex" in "decl" - * @param decl The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateReactionFunctionHeader(ReactorDecl decl, - int reactionIndex) { - String functionName = generateReactionFunctionName(decl, 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 ""; + } + 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 decl The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + public static String generateReaction( + Reaction reaction, + ReactorDecl decl, + int reactionIndex, + Instantiation mainDef, + ErrorReporter errorReporter, + CTypes types, + TargetConfig targetConfig, + boolean requiresType) { + var code = new CodeBuilder(); + var body = ASTUtils.toText(reaction.getCode()); + String init = + generateInitializationForReaction( + body, reaction, decl, reactionIndex, types, errorReporter, mainDef, requiresType); + + String srcPrefix = targetConfig.platformOptions.platform == Platform.ARDUINO ? "src/" : ""; + code.pr( + "#include " + + StringUtil.addDoubleQuotes(srcPrefix + CCoreFilesUtils.getCTargetSetHeader())); - public static String generateStpFunctionHeader(ReactorDecl decl, - int reactionIndex) { - String functionName = generateStpFunctionName(decl, reactionIndex); - return generateFunctionHeader(functionName); + CMethodGenerator.generateMacrosForMethods(ASTUtils.toDefinition(decl), code); + code.pr( + generateFunction( + generateReactionFunctionHeader(decl, reactionIndex), init, reaction.getCode())); + // 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(decl, reactionIndex), init, reaction.getStp().getCode())); } - public 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(decl, reactionIndex), + init, + reaction.getDeadline().getCode())); } + + CMethodGenerator.generateMacroUndefsForMethods(ASTUtils.toDefinition(decl), code); + code.pr( + "#include " + + StringUtil.addDoubleQuotes(srcPrefix + CCoreFilesUtils.getCTargetSetUndefHeader())); + return code.toString(); + } + + 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 decl The reactor with the deadline + * @param reactionIndex The number assigned to this reaction deadline + */ + public static String generateDeadlineFunctionName(ReactorDecl decl, int reactionIndex) { + return CUtil.getName(decl).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(ReactorDecl reactor, int reactionIndex) { + return CUtil.getName(reactor).toLowerCase() + "reaction_function_" + reactionIndex; + } + + /** + * Returns the name of the stp function for reaction. + * + * @param decl The reactor with the stp + * @param reactionIndex The number assigned to this reaction deadline + */ + public static String generateStpFunctionName(ReactorDecl decl, int reactionIndex) { + return CUtil.getName(decl).toLowerCase() + "_STP_function" + reactionIndex; + } + + /** + * Return the top level C function header for the deadline function numbered "reactionIndex" in + * "decl" + * + * @param decl The reactor declaration + * @param reactionIndex The reaction index. + * @return The function name for the deadline function. + */ + public static String generateDeadlineFunctionHeader(ReactorDecl decl, int reactionIndex) { + String functionName = generateDeadlineFunctionName(decl, reactionIndex); + return generateFunctionHeader(functionName); + } + + /** + * Return the top level C function header for the reaction numbered "reactionIndex" in "decl" + * + * @param decl The reactor declaration + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateReactionFunctionHeader(ReactorDecl decl, int reactionIndex) { + String functionName = generateReactionFunctionName(decl, reactionIndex); + return generateFunctionHeader(functionName); + } + + public static String generateStpFunctionHeader(ReactorDecl decl, int reactionIndex) { + String functionName = generateStpFunctionName(decl, 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 index 9163883962..6ad7f405eb 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -1,19 +1,14 @@ 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.Reactor; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; -import org.lflang.lf.Mode; -import org.lflang.lf.ModeTransition; -import org.lflang.lf.Watchdog; -import org.lflang.generator.GeneratorBase; -import org.lflang.generator.c.CReactionGenerator; -import java.util.List; import org.lflang.lf.Watchdog; /** @@ -21,202 +16,223 @@ * * @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(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);" - )); - } - - // 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(); + /** + * 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(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);")); } - /** - * 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+");" - )); - - + // 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." + // ); + // } } + } } - - /** 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(); + // 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 + + ");")); } - - public static String generateBuiltinTriggersTable(int count, String name) { - return String.join("\n", List.of( - "// Array of pointers to "+name+" triggers.", + } + + /** + * 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(); + (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 2767a05b9c..442afd79f3 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -34,17 +34,14 @@ import java.util.Set; 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.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; @@ -62,541 +59,517 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.Watchdog; import org.lflang.util.FileUtil; 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)); - code.pr(PythonPreambleGenerator.generateCIncludeStatements( + code.pr( + PythonPreambleGenerator.generateCIncludeStatements( targetConfig, targetLanguageIsCpp(), hasModalReactors)); - return code.toString(); + 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() { + // 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 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() { - // 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())); - } - - // C preamble for federated execution setup - String ret = ""; - if (targetConfig.fedSetupPreamble != null) { - ret = "#include \"" + targetConfig.fedSetupPreamble + "\""; - } - return ret; + // Add the main reactor if it is defined + if (this.mainDef != null) { + models.add((Model) ASTUtils.toDefinition(this.mainDef.getReactorClass()).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); - } + for (Model m : models) { + pythonPreamble.pr(PythonPreambleGenerator.generatePythonPreambles(m.getPreambles())); } - /** - * 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); - } + // C preamble for federated execution setup + String ret = ""; + if (targetConfig.fedSetupPreamble != null) { + ret = "#include \"" + targetConfig.fedSetupPreamble + "\""; } - - /** - * Generate the aliases for inputs, outputs, and struct type definitions for - * actions of the specified reactor in the specified federate. - * @param decl The parsed reactor data structure. - */ - @Override - public void generateAuxiliaryStructs( - ReactorDecl decl - ) { - Reactor reactor = ASTUtils.toDefinition(decl); - // First, handle inputs. - for (Input input : ASTUtils.allInputs(reactor)) { - generateAuxiliaryStructsForPort(decl, input); - } - // Next, handle outputs. - for (Output output : ASTUtils.allOutputs(reactor)) { - generateAuxiliaryStructsForPort(decl, output); - } - // Finally, handle actions. - for (Action action : ASTUtils.allActions(reactor)) { - generateAuxiliaryStructsForAction(decl, action); - } + return ret; + } + + @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(ReactorDecl decl, - Port port) { - boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(port), types); - code.pr(port, - PythonPortGenerator.generateAliasTypeDef(decl, 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(ReactorDecl decl, - Action action) { - code.pr(action, PythonActionGenerator.generateAliasTypeDef(decl, 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 decl The parsed reactor data structure. + */ + @Override + public void generateAuxiliaryStructs(ReactorDecl decl) { + Reactor reactor = ASTUtils.toDefinition(decl); + // First, handle inputs. + for (Input input : ASTUtils.allInputs(reactor)) { + generateAuxiliaryStructsForPort(decl, 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; - 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); - } + // Next, handle outputs. + for (Output output : ASTUtils.allOutputs(reactor)) { + generateAuxiliaryStructsForPort(decl, output); } - - @Override - protected PythonDockerGenerator getDockerGenerator(LFGeneratorContext context) { - return new PythonDockerGenerator(context); + // Finally, handle actions. + for (Action action : ASTUtils.allActions(reactor)) { + generateAuxiliaryStructsForAction(decl, 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 decl The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - @Override - protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { - Reactor reactor = ASTUtils.toDefinition(decl); - - // Reactions marked with a `@_c_body` attribute are generated in C - if (AttributeUtils.hasCBody(reaction)) { - super.generateReaction(reaction, decl, reactionIndex); - return; - } - code.pr(PythonReactionGenerator.generateCReaction(reaction, decl, reactionIndex, mainDef, errorReporter, types)); + } + + private void generateAuxiliaryStructsForPort(ReactorDecl decl, Port port) { + boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(port), types); + code.pr( + port, PythonPortGenerator.generateAliasTypeDef(decl, port, isTokenType, genericPortType)); + } + + private void generateAuxiliaryStructsForAction(ReactorDecl decl, Action action) { + code.pr(action, PythonActionGenerator.generateAliasTypeDef(decl, 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; + 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(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) { - // 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 decl The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + @Override + protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { + Reactor reactor = ASTUtils.toDefinition(decl); + + // Reactions marked with a `@_c_body` attribute are generated in C + if (AttributeUtils.hasCBody(reaction)) { + super.generateReaction(reaction, decl, 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" - ); + code.pr( + PythonReactionGenerator.generateCReaction( + reaction, decl, 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(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) { + // 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) @@ -621,71 +594,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 a770235b31..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,256 +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, - WATCHDOG, - 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)); - candidates.addAll(allWatchdogs(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)); - candidates.addAll(allWatchdogs(reactor)); - return Scopes.scopeFor(candidates); - } - case WATCHDOG: - return Scopes.scopeFor(allWatchdogs(reactor)); - 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 2ea579b753..2d0d4f332b 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -2,17 +2,17 @@ /************* * 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 +28,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 +43,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 +69,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; @@ -114,15 +110,12 @@ import org.lflang.lf.Visibility; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; -import org.lflang.lf.Watchdog; 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 @@ -133,1750 +126,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) { - - // 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); - } - } - } - } - } + @Check(CheckType.FAST) + public void checkConnection(Connection connection) { - // 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); - } - } - } - } - } + // 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; - // 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; - } + 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; + } } - 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 ((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); + } + } + } + } + } - 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: 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 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); - } - } - } - } - } + // 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 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); - } - } + 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(CheckType.FAST) - public void checkDeadline(Deadline deadline) { - if (isCBasedTarget() && - this.info.overflowingDeadlines.contains(deadline)) { + 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( - "Deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.DEADLINE__DELAY); + "Cannot connect: Port named '" + + effect.getVariable().getName() + + "' is already effect of a reaction.", + Literals.CONNECTION__RIGHT_PORTS); + } } - 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 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); + } + } + } + } } - @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; - } - - // FIXME: report error if resource cannot be resolved. - for (ImportedReactor reactor : imp.getReactorClasses()) { - if (!isUnused(reactor)) { - return; - } - } - warning("Unused import.", Literals.IMPORT__IMPORT_URI); + // 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 - 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(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); } - - @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); - } - } - - // 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 - ); - } - - // 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); - } + 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 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 - ); - } + @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; + } - // 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 - ); - } - } + // FIXME: report error if resource cannot be resolved. + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (!isUnused(reactor)) { + return; + } } + warning("Unused import.", Literals.IMPORT__IMPORT_URI); + } - /** 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); - } + @Check + public void checkImportedReactor(ImportedReactor reactor) { + if (isUnused(reactor)) { + warning("Unused reactor class.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } - // 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(); - } + 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(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.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(CheckType.NORMAL) - public void updateModelInfo(Model model) { - info.update(model, errorReporter); + // 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); } - @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); - } - } + // 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); + } - // 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); - } + // 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 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.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); - } - + 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 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) { + 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( - String.format("The %s qualifier has no meaning for the %s target. It should be removed.", - preamble.getVisibility(), this.target.name()), - Literals.PREAMBLE__VISIBILITY - ); - } + "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); } + } - @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); - } - } - } - } + @Check(CheckType.FAST) + public void checkReaction(Reaction reaction) { - // 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); - } - } - } - - // // 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; - } - } - 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. - } - // FIXME: improve error message. + if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { + warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS); } - - @Check(CheckType.FAST) - public void checkReactor(Reactor reactor) throws IOException { - Set superClasses = ASTUtils.superClasses(reactor); - if (superClasses == null) { + 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( - "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")) { + 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( - "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 - ); - } + String.format( + "Cannot have an output of this reactor as a trigger: %s", + triggerVarRef.getVariable().getName()), + Literals.REACTION__TRIGGERS); + } } + } + } - 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); - } - } + // 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); + } + } + } - // 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 - ); - } - } + // 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; + } + } + 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. + } + // 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<>(); + } + 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; - } - } - - if (!isValidSerializer) { - error( - "Serializer can be " + Arrays.asList(SupportedSerializers.values()), - Literals.SERIALIZER__TYPE - ); - } + // 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); } - @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()); - } - } - } - } - 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(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)); + } + } + } + } + // 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 usafe to instatiate 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 usafe to instatiate 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 specifiy 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 specifiy 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 \"__\": "; + 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())); + } - private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; - + // 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/file0.lf b/test/C/src/file0.lf deleted file mode 100644 index 645242845a..0000000000 --- a/test/C/src/file0.lf +++ /dev/null @@ -1,40 +0,0 @@ -target C - -reactor Watcher { - input x: int - // Produced if the - // deadline is - // violated. - output d: int - - reaction(x) -> - d, poodle {= - lf_watchdog_start(poodle, 0); - printf("Normal reaction.\n"); - =} -} - -main reactor { - logical action a - w = new Watcher( - - ) - - reaction( - startup - ) -> - w.x, a {= - lf_set(w.x, 0); - lf_schedule(a, 0); - =} - - reaction(a) -> - w.x {= - lf_set(w.x, 0); - lf_nanosleep(MSEC(40)); - =} - - reaction(w.d) {= - printf("Deadline reactor produced an output.\n"); - =} -} diff --git a/test/C/src/file1.lf b/test/C/src/file1.lf deleted file mode 100644 index 1183364928..0000000000 --- a/test/C/src/file1.lf +++ /dev/null @@ -1,40 +0,0 @@ -target C - -reactor Watcher { - input x: int - // Produced if the - // deadline is - // violated. - output d: int - - reaction(x) -> - poodle, d {= - lf_watchdog_start(poodle, 0); - printf("Normal reaction.\n"); - =} -} - -main reactor { - logical action a - w = new Watcher( - - ) - - reaction( - startup - ) -> - w.x, a {= - lf_set(w.x, 0); - lf_schedule(a, 0); - =} - - reaction(a) -> - w.x {= - lf_set(w.x, 0); - lf_nanosleep(MSEC(40)); - =} - - reaction(w.d) {= - printf("Deadline reactor produced an output.\n"); - =} -} diff --git a/test/C/src/file2.lf b/test/C/src/file2.lf deleted file mode 100644 index 1183364928..0000000000 --- a/test/C/src/file2.lf +++ /dev/null @@ -1,40 +0,0 @@ -target C - -reactor Watcher { - input x: int - // Produced if the - // deadline is - // violated. - output d: int - - reaction(x) -> - poodle, d {= - lf_watchdog_start(poodle, 0); - printf("Normal reaction.\n"); - =} -} - -main reactor { - logical action a - w = new Watcher( - - ) - - reaction( - startup - ) -> - w.x, a {= - lf_set(w.x, 0); - lf_schedule(a, 0); - =} - - reaction(a) -> - w.x {= - lf_set(w.x, 0); - lf_nanosleep(MSEC(40)); - =} - - reaction(w.d) {= - printf("Deadline reactor produced an output.\n"); - =} -} From 755ff0984af939c4b9263342639c4de9a2e4ea43 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Mon, 17 Apr 2023 20:07:09 -0700 Subject: [PATCH 066/108] save before reattempting the merge --- .github/actions/prepare-build-env/action.yml | 14 +- .github/workflows/c-arduino-tests.yml | 9 +- .github/workflows/c-zephyr-tests.yml | 7 + .github/workflows/ci.yml | 2 + .github/workflows/cli-tests.yml | 15 +- .github/workflows/unit-tests.yml | 1 + .../org/lflang/tests/cli/issue490.stderr | 24 +- .../src/org/lflang/tests/TestRegistry.java | 70 +- .../src/org/lflang/tests/cli/LfcCliTest.java | 7 +- .../src/org/lflang/tests/cli/LffCliTest.java | 7 +- .../compiler/LinguaFrancaValidationTest.java | 10 +- .../lflang/tests/compiler/RoundTripTests.java | 1 + .../lflang/tests/lsp/MockReportProgress.java | 2 +- .../org/lflang/tests/runtime/CCppTest.java | 1 + org.lflang/src/org/lflang/ASTUtils.java | 332 ++++++ org.lflang/src/org/lflang/LinguaFranca.xtext | 74 +- org.lflang/src/org/lflang/ast/IsEqual.java | 27 +- org.lflang/src/org/lflang/ast/ToLf.java | 12 +- org.lflang/src/org/lflang/cli/CliBase.java | 8 +- .../federated/extensions/CExtension.java | 30 +- .../federated/extensions/TSExtension.java | 31 +- .../federated/generator/FedGenerator.java | 2 +- .../generator/FedPreambleEmitter.java | 9 +- .../generator/ReactionInstanceGraph.java | 76 +- .../org/lflang/generator/ReactorInstance.java | 966 +++++++++++++++ .../src/org/lflang/generator/Validator.java | 2 +- .../lflang/generator/c/CActionGenerator.java | 10 +- .../lflang/generator/c/CCmakeGenerator.java | 1 + .../generator/c/CConstructorGenerator.java | 11 +- .../org/lflang/generator/c/CFileConfig.java | 14 +- .../org/lflang/generator/c/CGenerator.java | 1048 ++++++++++++++++- .../lflang/generator/c/CMethodGenerator.java | 4 +- .../lflang/generator/c/CPortGenerator.java | 34 +- .../generator/c/CPreambleGenerator.java | 34 +- .../generator/c/CReactionGenerator.java | 767 +++++++++++- .../c/CReactorHeaderFileGenerator.java | 208 ++++ .../src/org/lflang/generator/c/CUtil.java | 20 +- .../cpp/CppAssembleMethodGenerator.kt | 16 +- .../org/lflang/generator/cpp/CppExtensions.kt | 2 +- .../generator/cpp/CppReactionGenerator.kt | 24 +- .../python/PythonActionGenerator.java | 6 +- .../generator/python/PythonGenerator.java | 283 +++++ .../generator/python/PythonPortGenerator.java | 5 +- .../python/PythonPreambleGenerator.java | 2 +- .../python/PythonReactionGenerator.java | 47 +- .../python/PythonReactorGenerator.java | 4 +- org.lflang/src/org/lflang/util/FileUtil.java | 13 +- .../org/lflang/validation/LFValidator.java | 944 +++++++++++++++ test/C/.gitignore | 1 + .../bank_multiport_to_reaction_no_inlining.c | 16 + ...nk_multiport_to_reaction_no_inlining.cmake | 1 + test/C/c/bank_to_reaction_no_inlining.c | 11 + test/C/c/bank_to_reaction_no_inlining.cmake | 1 + test/C/c/count.c | 15 + test/C/c/count.cmake | 1 + test/C/c/count_hierarchy.c | 15 + test/C/c/count_hierarchy.cmake | 1 + test/C/c/multiport_to_reaction_no_inlining.c | 14 + .../c/multiport_to_reaction_no_inlining.cmake | 1 + test/C/c/sendreceive.c | 17 + test/C/c/sendreceive.cmake | 1 + test/C/src/Deadline.lf | 12 +- test/C/src/DeadlineHandledAbove.lf | 10 + test/C/src/DeadlineInherited.lf | 3 +- test/C/src/DeadlinePriority.lf | 3 +- test/C/src/DeadlineWithAfterDelay.lf | 4 +- test/C/src/DeadlineWithBanks.lf | 4 +- test/C/src/DelayString.lf | 10 + test/C/src/DelayStruct.lf | 1 + test/C/src/Hello.lf | 4 + test/C/src/ScheduleValue.lf | 11 + test/C/src/SimpleDeadline.lf | 10 + test/C/src/StructAsState.lf | 13 +- test/C/src/TestForPreviousOutput.lf | 10 + test/C/src/Watchdog.lf | 29 +- test/C/src/concurrent/AsyncCallback.lf | 13 +- test/C/src/concurrent/AsyncCallbackDrop.lf | 10 + test/C/src/concurrent/AsyncCallbackReplace.lf | 10 + .../DeadlineHandledAboveThreaded.lf | 12 +- test/C/src/concurrent/DeadlineThreaded.lf | 12 +- test/C/src/concurrent/HelloThreaded.lf | 4 + test/C/src/concurrent/ScheduleAt.lf | 9 + test/C/src/concurrent/Tracing.lf | 4 + .../src/federated/DistributedNetworkOrder.lf | 10 + .../DistributedPhysicalActionUpstream.lf | 32 +- .../DistributedPhysicalActionUpstreamLong.lf | 31 +- test/C/src/federated/HelloDistributed.lf | 4 + ...oopDistributedCentralizedPhysicalAction.lf | 24 +- .../federated/LoopDistributedDecentralized.lf | 24 +- test/C/src/federated/LoopDistributedDouble.lf | 24 +- test/C/src/include/hello.h | 4 +- .../modal_models/BanksCount3ModesComplex.lf | 4 +- .../modal_models/BanksCount3ModesSimple.lf | 4 +- .../src/modal_models/BanksModalStateReset.lf | 4 +- test/C/src/modal_models/ConvertCaseTest.lf | 8 +- test/C/src/modal_models/ModalActions.lf | 4 +- test/C/src/modal_models/ModalAfter.lf | 4 +- test/C/src/modal_models/ModalCycleBreaker.lf | 4 +- .../src/modal_models/ModalStartupShutdown.lf | 4 +- test/C/src/modal_models/ModalStateReset.lf | 4 +- .../C/src/modal_models/ModalStateResetAuto.lf | 4 +- test/C/src/modal_models/ModalTimers.lf | 4 +- .../MultipleOutputFeeder_2Connections.lf | 4 +- ...ultipleOutputFeeder_ReactionConnections.lf | 4 +- test/C/src/modal_models/util/TraceTesting.lf | 4 - test/C/src/multiport/BankIndexInitializer.lf | 4 +- .../BankMultiportToReactionNoInlining.lf | 30 + .../no_inlining/BankToReactionNoInlining.lf | 16 + test/C/src/no_inlining/Count.lf | 16 + test/C/src/no_inlining/CountHierarchy.lf | 23 + test/C/src/no_inlining/IntPrint.lf | 22 + .../MultiportToReactionNoInlining.lf | 35 + test/C/src/token/include/array.h | 6 +- test/Python/.gitignore | 1 + util/tracing/.gitignore | 2 + util/tracing/trace_to_chrome.c | 25 +- util/tracing/trace_to_csv.c | 10 +- util/tracing/trace_to_influxdb.c | 33 +- util/tracing/trace_util.c | 7 +- 119 files changed, 5503 insertions(+), 469 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/c/CReactorHeaderFileGenerator.java create mode 100644 test/C/.gitignore create mode 100644 test/C/c/bank_multiport_to_reaction_no_inlining.c create mode 100644 test/C/c/bank_multiport_to_reaction_no_inlining.cmake create mode 100644 test/C/c/bank_to_reaction_no_inlining.c create mode 100644 test/C/c/bank_to_reaction_no_inlining.cmake create mode 100644 test/C/c/count.c create mode 100644 test/C/c/count.cmake create mode 100644 test/C/c/count_hierarchy.c create mode 100644 test/C/c/count_hierarchy.cmake create mode 100644 test/C/c/multiport_to_reaction_no_inlining.c create mode 100644 test/C/c/multiport_to_reaction_no_inlining.cmake create mode 100644 test/C/c/sendreceive.c create mode 100644 test/C/c/sendreceive.cmake create mode 100644 test/C/src/no_inlining/BankMultiportToReactionNoInlining.lf create mode 100644 test/C/src/no_inlining/BankToReactionNoInlining.lf create mode 100644 test/C/src/no_inlining/Count.lf create mode 100644 test/C/src/no_inlining/CountHierarchy.lf create mode 100644 test/C/src/no_inlining/IntPrint.lf create mode 100644 test/C/src/no_inlining/MultiportToReactionNoInlining.lf create mode 100644 test/Python/.gitignore create mode 100644 util/tracing/.gitignore diff --git a/.github/actions/prepare-build-env/action.yml b/.github/actions/prepare-build-env/action.yml index 16cfc3c410..2d0c82c78f 100644 --- a/.github/actions/prepare-build-env/action.yml +++ b/.github/actions/prepare-build-env/action.yml @@ -15,9 +15,15 @@ runs: cat gradle.properties echo $JAVA_HOME shell: bash - - name: Create hash of Gradle configuration + - name: Create hash of Gradle configuration (macOS only) + run: | + echo "gradle-hash"="$(find . -type f \( -name "gradle.properties" -o -name "gradle-wrapper.properties" \) -exec cat {} + | shasum -a 256 | cut -d ' ' -f 1)" >> $GITHUB_ENV + if: ${{ runner.os == 'macOS' }} + shell: bash + - name: Create hash of Gradle configuration (Linux and Windows only) run: | echo "gradle-hash"="$(find . -type f \( -name "gradle.properties" -o -name "gradle-wrapper.properties" \) -exec cat {} + | sha256sum | cut -d ' ' -f 1)" >> $GITHUB_ENV + if: ${{ runner.os == 'Windows' || runner.os == 'Linux' }} shell: bash - name: Cache uses: actions/cache@v3 @@ -28,3 +34,9 @@ runs: key: gradle-${{ runner.os }}-${{ env.gradle-hash }} # restore-keys: | # ${{ runner.os }}-gradle- + - name: Bring down Gradle daemon (Windows) + uses: webiny/action-post-run@3.0.0 + id: post-run-command + with: + run: ./gradlew --stop + if: ${{ runner.os == 'Windows' }} diff --git a/.github/workflows/c-arduino-tests.yml b/.github/workflows/c-arduino-tests.yml index f0f52bd93f..167e8f6f1c 100644 --- a/.github/workflows/c-arduino-tests.yml +++ b/.github/workflows/c-arduino-tests.yml @@ -59,4 +59,11 @@ jobs: arduino-cli core install arduino:sam arduino-cli core install arduino:mbed - name: Perform Arduino tests for C target with default scheduler - run: ./gradlew test --tests org.lflang.tests.runtime.CArduinoTest.buildArduinoTests \ No newline at end of file + run: ./gradlew test --tests org.lflang.tests.runtime.CArduinoTest.buildArduinoTests + - name: Report to CodeCov + uses: codecov/codecov-action@v3.1.1 + with: + file: org.lflang.tests/build/reports/xml/jacoco + fail_ci_if_error: false + verbose: true + if: ${{ !inputs.runtime-ref && runner.os == 'Linux' }} # i.e., if this is part of the main repo's CI diff --git a/.github/workflows/c-zephyr-tests.yml b/.github/workflows/c-zephyr-tests.yml index 87d48f37f1..8af72a2f39 100644 --- a/.github/workflows/c-zephyr-tests.yml +++ b/.github/workflows/c-zephyr-tests.yml @@ -61,3 +61,10 @@ jobs: run: | ./gradlew test --tests org.lflang.tests.runtime.CZephyrTest.build* util/RunZephyrTests.sh test/C/src-gen + - name: Report to CodeCov + uses: codecov/codecov-action@v3.1.1 + with: + file: org.lflang.tests/build/reports/xml/jacoco + fail_ci_if_error: false + verbose: true + if: ${{ !inputs.runtime-ref && runner.os == 'Linux' }} # i.e., if this is part of the main repo's CI diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48110ac926..f6dd56ae40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,8 @@ jobs: uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main with: target: 'C' + benchmarks-ref: main + compiler-ref: master needs: cancel # Run language server tests. diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml index 9aaced4aba..056e32c04c 100644 --- a/.github/workflows/cli-tests.yml +++ b/.github/workflows/cli-tests.yml @@ -16,10 +16,6 @@ jobs: fetch-depth: 0 - name: Prepare build environment uses: ./.github/actions/prepare-build-env - # FIXME: reenable once the cli test is fixed - # - name: Run standalone cli tests - # run: | - # ./gradlew :org.lflang.cli:test --stacktrace - name: Test build bash scripts (Linux and macOS only) run: | .github/scripts/test-build.sh @@ -37,3 +33,14 @@ jobs: ./gradlew buildAll bin/lfc.ps1 --help if: ${{ runner.os == 'Windows' }} + - name: Run standalone cli tests + run: | + ./gradlew test --tests org.lflang.tests.cli.* --stacktrace +# NOTE: do not put other invocations for gradlew in between these steps, or coverage reporting will break. + - name: Report to CodeCov + uses: codecov/codecov-action@v3.1.1 + with: + file: org.lflang.tests/build/reports/xml/jacoco + fail_ci_if_error: false + verbose: true + if: ${{ runner.os == 'Linux' }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 6c2704629e..1caec5c1ac 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -12,6 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 with: + submodules: true fetch-depth: 0 - name: Prepare build environment uses: ./.github/actions/prepare-build-env diff --git a/org.lflang.tests/resources/org/lflang/tests/cli/issue490.stderr b/org.lflang.tests/resources/org/lflang/tests/cli/issue490.stderr index edf3254755..fdaa4f8e0e 100644 --- a/org.lflang.tests/resources/org/lflang/tests/cli/issue490.stderr +++ b/org.lflang.tests/resources/org/lflang/tests/cli/issue490.stderr @@ -6,23 +6,19 @@ lfc: error: Name of main reactor must match the file name (or be omitted). | ^ Name of main reactor must match the file name (or be omitted). | 5 | state liss(2, 3); - -lfc: error: missing '{=' at '{' +lfc: error: no viable alternative at input '{' --> %%%PATH.lf%%%:6:22 | 5 | state liss(2, 3); 6 | reaction (startup) { - | ^ missing '{=' at '{' + | ^ no viable alternative at input '{' | 7 | print(self.liss) - -lfc: error: mismatched input '' expecting '=}' - --> %%%PATH.lf%%%:9:2 - | - 8 | } - | >>>>>>>>>>>>>> - 9 | } -10 | - | < mismatched input '' expecting '=}' -11 | - +lfc: error: no viable alternative at input '(' +--> %%%PATH.lf%%%:7:5 + | +6 | reaction (startup) { +7 | print(self.liss) + | ^^^^^ no viable alternative at input '(' + | +8 | } diff --git a/org.lflang.tests/src/org/lflang/tests/TestRegistry.java b/org.lflang.tests/src/org/lflang/tests/TestRegistry.java index 232e735641..10fa68acaf 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestRegistry.java +++ b/org.lflang.tests/src/org/lflang/tests/TestRegistry.java @@ -32,14 +32,14 @@ /** * A registry to retrieve tests from, organized by target and category. - * + * * @author Marten Lohstroh */ public class TestRegistry { - + static class TestMap { /** - * Registry that maps targets to maps from categories to sets of tests. + * Registry that maps targets to maps from categories to sets of tests. */ protected final Map>> map = new HashMap<>(); @@ -57,7 +57,7 @@ public TestMap() { map.put(target, categories); } } - + /** * Return a set of tests given a target and test category. * @param t The target. @@ -68,26 +68,26 @@ public Set getTests(Target t, TestCategory c) { return this.map.get(t).get(c); } } - + /** * List of directories that should be skipped when indexing test files. Any * test file that has a directory in its path that matches an entry in this * array will not be discovered. */ public static final String[] IGNORED_DIRECTORIES = {"failing", "knownfailed", "failed", "fed-gen"}; - + /** * Path to the root of the repository. */ public static final Path LF_REPO_PATH = Paths.get("").toAbsolutePath(); - + /** * Path to the test directory in the repository. */ public static final Path LF_TEST_PATH = LF_REPO_PATH.resolve("test"); /** - * Internal data structure that stores registered tests. + * Internal data structure that stores registered tests. */ protected static final TestMap registered = new TestMap(); @@ -96,31 +96,31 @@ public Set getTests(Target t, TestCategory c) { * source files with no main reactor are indexed here. */ protected static final TestMap ignored = new TestMap(); - + /** * A map from each test category to a set of tests that is the union of * all registered tests in that category across all targets. */ protected static final Map> allTargets = new HashMap<>(); - + /** * Enumeration of test categories, used to map tests to categories. The * nearest containing directory that matches any of the categories will * determine the category that the test is mapped to. Matching is case * insensitive. - * + * * For example, the following files will all map to THREADED: * - C/threaded/Foo.lf - * - C/THREADED/Foo.lf + * - C/THREADED/Foo.lf * - C/Threaded/Foo.lf - * - C/foo/threaded/Bar.lf - * - C/foo/bar/threaded/Threaded.lf + * - C/foo/threaded/Bar.lf + * - C/foo/bar/threaded/Threaded.lf * - C/federated/threaded/bar.lf - * but the following will not: + * but the following will not: * - C/Foo.lf (maps to COMMON) * - C/Threaded.lf (maps to COMMON) * - C/threaded/federated/foo.lf (maps to FEDERATED) - * + * * @author Marten Lohstroh */ public enum TestCategory { @@ -140,7 +140,7 @@ public enum TestCategory { PROPERTIES(true), /** Tests concerning modal reactors */ MODAL_MODELS(true), - + NO_INLINING(false), // non-shared tests DOCKER(true), DOCKER_FEDERATED(true, "docker" + File.separator + "federated"), @@ -155,7 +155,7 @@ public enum TestCategory { public final boolean isCommon; public final String path; public final TestLevel level ; - + /** * Create a new test category. */ @@ -189,14 +189,14 @@ public String getPath() { /** * Return a header associated with the category. - * + * * @return A header to print in the test report. */ public String getHeader() { return TestBase.THICK_LINE + "Category: " + this.name(); } } - + // Static code that performs the file system traversal and discovers // all .lf files to be included in the registry. static { @@ -220,7 +220,7 @@ public String getHeader() { } else { System.out.println("WARNING: No test directory for target " + target + "\n"); } - + } catch (IOException e) { System.err.println( "ERROR: Caught exception while indexing tests for target " + target); @@ -231,7 +231,7 @@ public String getHeader() { c -> allTargets.get(c).addAll(getRegisteredTests(target, c, false))); } } - + /** * Calling this function forces the lazy initialization of the static code * that indexes all files. It is advisable to do this prior to executing @@ -239,10 +239,10 @@ public String getHeader() { * printed while indexing are printed first. */ public static void initialize() {} - + /** * Return the tests that were indexed for a given target and category. - * + * * @param target The target to get indexed tests for. * @param category The category of tests to include in the returned tests. * @param copy Whether to return copies of the indexed tests instead of the indexed tests themselves. @@ -260,7 +260,7 @@ public static Set getRegisteredTests(Target target, return registered.getTests(target, category); } } - + /** * Return the test that were found but not indexed because they did not * have a main reactor. @@ -275,11 +275,11 @@ public static String getCoverageReport(Target target, TestCategory category) { s.append(TestBase.THIN_LINE); s.append("Ignored: ").append(ignored.size()).append("\n"); s.append(TestBase.THIN_LINE); - + for (LFTest test : ignored) { s.append("No main reactor in: ").append(test).append("\n"); } - + Set own = getRegisteredTests(target, category, false); if (category.isCommon) { Set all = allTargets.get(category); @@ -301,17 +301,17 @@ public static String getCoverageReport(Target target, TestCategory category) { /** * FileVisitor implementation that maintains a stack to map found tests to - * the appropriate category and excludes directories that are listed as + * the appropriate category and excludes directories that are listed as * "ignored" from walks. - * + * * Specifically, when a directory is encountered that matches a category, * this category is pushed onto the stack. Similarly, when the DFS leaves * such a directory, its corresponding category is popped from the stack. * Any test (*.lf) file that is encountered will be mapped to the category - * that is on top of the stack. Initially, the stack has one element that + * that is on top of the stack. Initially, the stack has one element that * is TestCategory.COMMON, meaning that test files in the top-level test * directory for a given target will be mapped to that category. - * + * * @author Marten Lohstroh */ public static class TestDirVisitor extends SimpleFileVisitor { @@ -325,7 +325,7 @@ public static class TestDirVisitor extends SimpleFileVisitor { * The target that all encountered tests belong to. */ protected Target target; - + protected ResourceSet rs; protected Path srcBasePath; @@ -342,7 +342,7 @@ public TestDirVisitor(ResourceSet rs, Target target, Path srcBasePath) { this.target = target; this.srcBasePath = srcBasePath; } - + /** * Push categories onto the stack as appropriate and skip directories * that should be ignored. @@ -363,7 +363,7 @@ public FileVisitResult preVisitDirectory(Path dir, } return CONTINUE; } - + /** * Pop categories from the stack as appropriate. */ @@ -377,7 +377,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) { } return CONTINUE; } - + /** * Add test files to the registry if they end with ".lf", but only if they have a main reactor. */ diff --git a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java index 477bd26c32..7143ba9375 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java @@ -34,6 +34,7 @@ import com.google.inject.Injector; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.Properties; @@ -125,7 +126,8 @@ public void testVersion() { .verify(result -> { result.checkOk(); result.checkNoErrorOutput(); - result.checkStdOut(equalTo("lfc " + LocalStrings.VERSION + "\n")); + result.checkStdOut(equalTo( + "lfc " + LocalStrings.VERSION + System.lineSeparator())); }); } @@ -227,7 +229,8 @@ public void verifyGeneratorArgs(Path tempDir, String[] args) { assertEquals(properties.getProperty(BuildParm.LINT.getKey()), "true"); assertEquals(properties.getProperty(BuildParm.NO_COMPILE.getKey()), "true"); assertEquals(properties.getProperty(BuildParm.QUIET.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.RTI.getKey()), "path/to/rti"); + assertEquals(properties.getProperty(BuildParm.RTI.getKey()), + "path" + File.separator + "to" + File.separator + "rti"); assertEquals(properties.getProperty(BuildParm.RUNTIME_VERSION.getKey()), "rs"); assertEquals(properties.getProperty(BuildParm.SCHEDULER.getKey()), "GEDF_NP"); assertEquals(properties.getProperty(BuildParm.THREADING.getKey()), "false"); diff --git a/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java b/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java index 3a34e9cb81..16a4e9b32d 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java @@ -30,6 +30,7 @@ import static org.lflang.tests.TestUtils.TempDirBuilder.dirBuilder; import static org.lflang.tests.TestUtils.TempDirChecker.dirChecker; +import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -76,7 +77,8 @@ public void testVersion() { ExecutionResult result = lffTester.run("--version"); result.checkOk(); result.checkNoErrorOutput(); - result.checkStdOut(equalTo("lff " + LocalStrings.VERSION + "\n")); + result.checkStdOut( + equalTo("lff " + LocalStrings.VERSION + System.lineSeparator())); } @@ -119,7 +121,8 @@ public void testFormatDirectoryVerbose(@TempDir Path tempDir) throws IOException result.checkOk(); - result.checkStdOut(containsString("Formatted src/File.lf")); + result.checkStdOut(containsString( + "Formatted src" + File.separator + "File.lf")); dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT)); } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index c5ead40cb7..847a364193 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1150,7 +1150,7 @@ private List synthesizeExamples(UnionType type, boolean correct) { */ private List synthesizeExamples(DictionaryType type, boolean correct) { List examples = new LinkedList<>(); - // Produce a set of singleton dictionaries. + // Produce a set of singleton dictionaries. // If incorrect examples are wanted, garble the key. for (DictionaryElement option : type.options) { synthesizeExamples(option.getType(), correct).forEach(it -> examples.add( @@ -1629,7 +1629,7 @@ public void testMainReactorHasHost() throws Exception { """; // TODO: Uncomment and fix test // List issues = validator.validate(parseWithoutError(testCase)); - // Assertions.assertTrue(issues.size() == 1 && + // Assertions.assertTrue(issues.size() == 1 && // issues.get(0).getMessage().contains("Cannot assign a host to reactor '") && // issues.get(0).getMessage().contains("' because it is not federated.")); } @@ -1810,7 +1810,7 @@ public void testMissingModeStateResetInstance() throws Exception { "This reactor contains state variables that are not reset upon mode entry: " + "s in R" + ".\nThe state variables are neither marked for automatic reset nor have a dedicated reset reaction. " - + "It is usafe to instatiate this reactor inside a mode entered with reset."); + + "It is unsafe to instantiate this reactor inside a mode entered with reset."); } @Test @@ -1841,11 +1841,9 @@ public void testUnspecifiedTransitionType() throws Exception { } """; validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getReaction(), null, - "You should specifiy a transition type! " + "You should specify a transition type! " + "Reset and history transitions have different effects on this target mode. " + "Currently, a reset type is implicitly assumed."); } } - - diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java index 24cb3554ff..801211be23 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java @@ -45,6 +45,7 @@ public void roundTripTest() { private void run(Path file) throws Exception { Model originalModel = LfParsingUtil.parse(file); + System.out.println(file); assertThat(originalModel.eResource().getErrors(), equalTo(emptyList())); // TODO: Check that the output is a fixed point final int smallLineLength = 20; diff --git a/org.lflang.tests/src/org/lflang/tests/lsp/MockReportProgress.java b/org.lflang.tests/src/org/lflang/tests/lsp/MockReportProgress.java index 901f0e6154..014fcc7276 100644 --- a/org.lflang.tests/src/org/lflang/tests/lsp/MockReportProgress.java +++ b/org.lflang.tests/src/org/lflang/tests/lsp/MockReportProgress.java @@ -17,7 +17,7 @@ public MockReportProgress() { @Override public void apply(String message, Integer percentage) { - System.out.printf("%s [%d -> %d]%n", message, previousPercentProgress, percentage); + System.out.printf("MockReportProgress: %s [%d -> %d]%n", message, previousPercentProgress, percentage); if (percentage == null) return; if (percentage < previousPercentProgress || percentage < 0 || percentage > 100) failed = true; previousPercentProgress = percentage; diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CCppTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CCppTest.java index db0ee3431f..17fd386ad8 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CCppTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CCppTest.java @@ -46,6 +46,7 @@ private static boolean isExcludedFromCCpp(TestCategory category) { excluded |= isMac() && (category == TestCategory.DOCKER_FEDERATED || category == TestCategory.DOCKER); excluded |= category == TestCategory.ZEPHYR; excluded |= category == TestCategory.ARDUINO; + excluded |= category == TestCategory.NO_INLINING; return !excluded; } } diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 46b913eee0..0fd819e0e0 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -59,6 +59,7 @@ import org.lflang.ast.ToText; import org.lflang.generator.CodeMap; import org.lflang.generator.InvalidSourceException; +import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.Assignment; import org.lflang.lf.Code; @@ -801,6 +802,7 @@ public static Element toElement(boolean val) { return toElement(Boolean.toString(val), false); } +<<<<<<< HEAD public static Element toElement(int val) { return toElement(Integer.toString(val), false); } @@ -819,6 +821,336 @@ public static String baseType(Type type) { } else { if (type.isTime()) { return "time"; +======= + /** 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); + } + } + } + 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; +>>>>>>> origin } else { StringBuilder result = new StringBuilder(type.getId()); diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 9ee2cb520d..4e1645e9db 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -13,22 +13,22 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************/ -/** +/** * Grammar for Lingua Franca. * A note of caution: extending this grammar with productions that introduce - * new terminals (i.e., anything written between quotes), will require those + * new terminals (i.e., anything written between quotes), will require those * terminals to also be added to the Token production at the bottom of this * file. Failing to do so will cause a parse error whenever a terminal not * listed in the Token production is featured in a segment of target-language @@ -44,7 +44,7 @@ import "http://www.eclipse.org/emf/2002/Ecore" as ecore // Use the package name "lf" for generated files defining the classes // in the metamodel (i.e., the classes representing nodes in the -// abstract syntax tree (AST), i.e., the eCore model). +// abstract syntax tree (AST), i.e., the eCore model). generate lf "https://lf-lang.org" /////////// Overall file @@ -106,7 +106,7 @@ TypeExpr: /** * Specification of the target language. Target properties can be specified in - * YAML format to pass on configuration details to the runtime environment. + * YAML format to pass on configuration details to the runtime environment. */ TargetDecl: 'target' name=ID (config=KeyValuePairs)? ';'?; @@ -115,11 +115,11 @@ TargetDecl: /////////// Statements /** - * Declaration of a state variable. Types are optional, but may be required - * during validation (depending on the target language). Initialization is also + * Declaration of a state variable. Types are optional, but may be required + * during validation (depending on the target language). Initialization is also * optional. A state variable can be initialized by assigning a `Expression` or list * of these. Note that a `Expression` may also be a reference to a parameter. - * The following checks must be carried out during validation: + * The following checks must be carried out during validation: * - if the list of initialization expressions has more than one element in it, a * type must be specified; * - if the `time` type is specified, there can only be a single initialization @@ -182,10 +182,10 @@ Mode: )* '}'; // Action that has either a physical or logical origin. -// +// // If the origin is logical, the minDelay is a minimum logical delay // after the logical time at which schedule() is called that the -// action will occur. If the origin is physical, then the +// action will occur. If the origin is physical, then the // minDelay is a minimum logical delay after the physical time // at which schedule() is called that the action will occur. // @@ -200,12 +200,11 @@ Action: Reaction: (attributes+=Attribute)* (('reaction') | mutation ?= 'mutation') - ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')')? + ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')') (sources+=VarRef (',' sources+=VarRef)*)? ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? - code=Code - (stp=STP)? - (deadline=Deadline)?; + ((('named' name=ID)? code=Code) | 'named' name=ID)(stp=STP)?(deadline=Deadline)? + ; TriggerRef: BuiltinTriggerRef | VarRef; @@ -237,7 +236,7 @@ Instantiation: Connection: ((leftPorts += VarRef (',' leftPorts += VarRef)*) | ( '(' leftPorts += VarRef (',' leftPorts += VarRef)* ')' iterated ?= '+'?)) - ('->' | physical?='~>') + ('->' | physical?='~>') rightPorts += VarRef (',' rightPorts += VarRef)* ('after' delay=Expression)? (serializer=Serializer)? @@ -271,7 +270,7 @@ Array: // todo allow empty array in grammar, replace with validator error Element: keyvalue=KeyValuePairs | array=Array - | literal=Literal + | literal=Literal | (time=INT unit=TimeUnit) | id=Path; @@ -284,8 +283,8 @@ Variable: TypedVariable | Timer | Mode | Watchdog; VarRef: - variable=[Variable] | container=[Instantiation] '.' variable=[Variable] - | interleaved?='interleaved' '(' (variable=[Variable] | container=[Instantiation] '.' variable=[Variable]) ')' + (variable=[Variable] | container=[Instantiation] '.' variable=[Variable] + | interleaved?='interleaved' '(' (variable=[Variable] | container=[Instantiation] '.' variable=[Variable]) ')') ('as' (alias=ID))? ; VarRefOrModeTransition returns VarRef: VarRef | transition=ModeTransition '(' variable=[Mode] ')'; @@ -338,7 +337,7 @@ Time: Port: Input | Output; - + // A type is in the target language, hence either an ID or target code. Type: time?='time' (arraySpec=ArraySpec)? @@ -435,24 +434,24 @@ IPV4Addr: ; IPV6Seg: - // NOTE: This rule is too permissive by design. + // NOTE: This rule is too permissive by design. // Further checking is done during validation. (INT | (INT? ID)) ; IPV6Addr: - // NOTE: This rule is too permissive by design. - // Further checking is done during validation. + // NOTE: This rule is too permissive by design. + // Further checking is done during validation. // IPV6 with truncation. - '::' | ('::' (IPV6Seg (':'))* IPV6Seg) | ((IPV6Seg (':'|'::'))+ IPV6Seg?) | - + '::' | ('::' (IPV6Seg (':'))* IPV6Seg) | ((IPV6Seg (':'|'::'))+ IPV6Seg?) | + // (Link-local IPv6 addresses with zone index) "fe80::7:8%1" (ID '::' IPV6Seg (':' IPV6Seg)* '%' (INT | ID)+) | // IPv4-mapped IPv6 addresses and IPv4-translated addresses ('::' IPV4Addr) | ('::' ID ':' (INT ':')? IPV4Addr) | - + // IPv4-Embedded IPv6 Address (((IPV6Seg (':' IPV6Seg)* '::') | (IPV6Seg (':' IPV6Seg)*) ':') IPV4Addr) ; @@ -469,7 +468,7 @@ Code: {Code} '{=' body=Body '=}' ; -FSName: +FSName: (ID | '.' | '_')+ ; // Absolute or relative directory path in Windows, Linux, or MacOS. @@ -508,11 +507,12 @@ Token: // Non-constant terminals ID | INT | FLOAT_EXP_SUFFIX | LT_ANNOT | STRING | CHAR_LIT | ML_COMMENT | SL_COMMENT | WS | ANY_OTHER | // Keywords - 'target' | 'import' | 'main' | 'realtime' | 'reactor' | 'state' | 'time' | - 'mutable' | 'input' | 'output' | 'timer' | 'action' | 'reaction' | + 'target' | 'import' | 'main' | 'realtime' | 'reactor' | 'state' | 'time' | + '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' | 'watchdog' | + 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' | 'named' | + // Other terminals NEGINT | TRUE | FALSE | // Action origins @@ -532,7 +532,7 @@ Token: // Underscore '_' | // Arrow - '->' | + '->' | // Assignment '=' | // Percentage diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index f4bbc17f6d..3a7a5fb2cd 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -275,19 +275,20 @@ public Boolean caseAttrParm(AttrParm object) { .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) - .equivalent(Reaction::getCode) - .equivalent(Reaction::getStp) - .equivalent(Reaction::getDeadline) - .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) { diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 2f0ec76d9b..ce1cdca9fe 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -598,13 +598,12 @@ public MalleableString caseAction(Action object) { @Override public MalleableString caseReaction(Reaction object) { - // ('reaction') - // ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')')? + // (attributes+=Attribute)* + // (('reaction') | mutation ?= 'mutation') + // ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')') // (sources+=VarRef (',' sources+=VarRef)*)? // ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? - // code=Code - // (stp=STP)? - // (deadline=Deadline)? + // ((('named' name=ID)? code=Code) | 'named' name=ID)(stp=STP)?(deadline=Deadline)? Builder msb = new Builder(); addAttributes(msb, object::getAttributes); if (object.isMutation()) { @@ -633,7 +632,8 @@ public MalleableString caseReaction(Reaction object) { : doSwitch(varRef)) .collect(new Joiner(", "))); } - msb.append(" ").append(doSwitch(object.getCode())); + if (object.getName() != null) msb.append(" named ").append(object.getName()); + if (object.getCode() != null) msb.append(" ").append(doSwitch(object.getCode())); if (object.getStp() != null) msb.append(" ").append(doSwitch(object.getStp())); if (object.getDeadline() != null) msb.append(" ").append(doSwitch(object.getDeadline())); return msb.get(); diff --git a/org.lflang/src/org/lflang/cli/CliBase.java b/org.lflang/src/org/lflang/cli/CliBase.java index 4dea30b865..2f4a9d849c 100644 --- a/org.lflang/src/org/lflang/cli/CliBase.java +++ b/org.lflang/src/org/lflang/cli/CliBase.java @@ -157,16 +157,14 @@ public void run() { "No such file: " + topLevelArg.jsonFile); } } - // If args are given in a json string, (1) unpack them into an args - // array, and (2) call cmd.execute on them, which assigns them to their - // correct instance variables, then (3) recurses into run(). + // If args are given in a json string, unpack them and re-run + // picocli argument validation. if (topLevelArg.jsonString != null) { // Unpack args from json string. String[] args = jsonStringToArgs(topLevelArg.jsonString); // Execute application on unpacked args. CommandLine cmd = spec.commandLine(); - int exitCode = cmd.execute(args); - io.callSystemExit(exitCode); + cmd.execute(args); // If args are already unpacked, invoke tool-specific logic. } else { doRun(); diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index a2ac48216f..4698c2eb84 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -38,6 +38,7 @@ import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; @@ -99,7 +100,7 @@ public void initializeTargetConfig( federate.targetConfig.setByUser.add(TargetProperty.THREADING); // Include the fed setup file for this federate in the target property - String relPath = "include" + File.separator + "_" + federate.name + "_preamble.h"; + String relPath = getPreamblePath(federate); federate.targetConfig.fedSetupPreamble = relPath; federate.targetConfig.setByUser.add(TargetProperty.FED_SETUP); } @@ -471,7 +472,7 @@ public String getNetworkBufferType() { } /** - * Add preamble to a separate file `include/_federateName_preamble.h` to set up federated execution. + * Add preamble to a separate file to set up federated execution. * Return an empty string since no code generated needs to go in the source. */ @Override @@ -483,14 +484,29 @@ public String generatePreamble( ) throws IOException { // Put the C preamble in a `include/_federate.name + _preamble.h` file String cPreamble = makePreamble(federate, fileConfig, rtiConfig, errorReporter); - String relPath = "include" + File.separator + "_" + federate.name + "_preamble.h"; + String relPath = getPreamblePath(federate); Path fedPreamblePath = fileConfig.getSrcPath().resolve(relPath); Files.createDirectories(fedPreamblePath.getParent()); try (var writer = Files.newBufferedWriter(fedPreamblePath)) { writer.write(cPreamble); } + var includes = new CodeBuilder(); + if (federate.targetConfig.target != Target.Python) { + includes.pr("#ifdef __cplusplus\n" + + "extern \"C\" {\n" + + "#endif"); + includes.pr("#include \"core/federated/federate.h\""); + includes.pr("#include \"core/federated/net_common.h\""); + includes.pr("#include \"core/federated/net_util.h\""); + includes.pr("#include \"core/threaded/reactor_threaded.h\""); + includes.pr("#include \"core/utils/util.h\""); + includes.pr("extern federate_instance_t _fed;"); + includes.pr("#ifdef __cplusplus\n" + + "}\n" + + "#endif"); + } - return ""; + return includes.toString(); } /** @@ -558,7 +574,7 @@ private String generateInitializeTriggers(FederateInstance federate, ErrorReport code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main)); code.pr(CExtensionUtils.initializeTriggerForControlReactions(main, main, federate)); federatedReactor.setName(oldFederatedReactorName); - + return """ #define initialize_triggers_for_federate() \\ do { \\ @@ -744,5 +760,7 @@ private String generateCodeForPhysicalActions(FederateInstance federate, ErrorRe } return code.getCode(); } - + private String getPreamblePath(FederateInstance f) { + return "include" + File.separator + "_" + f.name + "_preamble.h"; + } } diff --git a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java index e438b1b866..77d6203a93 100644 --- a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java @@ -96,22 +96,21 @@ public String generatePreamble(FederateInstance federate, FedFileConfig fileConf var upstreamConnectionDelays = getUpstreamConnectionDelays(federate); return """ - preamble {= - const defaultFederateConfig: __FederateConfig = { - dependsOn: [%s], - executionTimeout: undefined, - fast: false, - federateID: %d, - federationID: "Unidentified Federation", - keepAlive: false, - minOutputDelay: %s, - networkMessageActions: [%s], - rtiHost: "%s", - rtiPort: %d, - sendsTo: [%s], - upstreamConnectionDelays: [%s] - } - =}""".formatted( + const defaultFederateConfig: __FederateConfig = { + dependsOn: [%s], + executionTimeout: undefined, + fast: false, + federateID: %d, + federationID: "Unidentified Federation", + keepAlive: false, + minOutputDelay: %s, + networkMessageActions: [%s], + rtiHost: "%s", + rtiPort: %d, + sendsTo: [%s], + upstreamConnectionDelays: [%s] + } + """.formatted( federate.dependsOn.keySet().stream() .map(e->String.valueOf(e.id)) .collect(Collectors.joining(",")), diff --git a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java index 18d5770165..cd9c2e95d0 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java +++ b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java @@ -288,7 +288,7 @@ private Map compileFederates( Resource res = rs.getResource(URI.createFileURI( fileConfig.getSrcPath().resolve(fed.name + ".lf").toAbsolutePath().toString() ), true); - FileConfig subFileConfig = LFGenerator.createFileConfig(res, fileConfig.getSrcGenPath(), false); + FileConfig subFileConfig = LFGenerator.createFileConfig(res, fileConfig.getSrcGenPath(), true); ErrorReporter subContextErrorReporter = new LineAdjustingErrorReporter(threadSafeErrorReporter, lf2lfCodeMapMap); var props = new Properties(); diff --git a/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java index 20771e7848..354cd33044 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java @@ -39,8 +39,13 @@ String generatePreamble(FederateInstance federate, FedFileConfig fileConfig, Rti )); } - preambleCode.pr(FedTargetExtensionFactory.getExtension(federate.targetConfig.target).generatePreamble( - federate, fileConfig, rtiConfig, errorReporter)); + preambleCode.pr(""" + preamble {= + %s + =}""".formatted(FedTargetExtensionFactory.getExtension(federate.targetConfig.target).generatePreamble( + federate, fileConfig, rtiConfig, errorReporter + )) + ); return preambleCode.getCode(); } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 116cbf3046..3422ee0d50 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -44,23 +44,23 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * In the worst case, of these runtime instances may have distinct dependencies, * and hence distinct levels in the graph. Moreover, some of these instances * may be involved in cycles while others are not. - * + * * Upon construction of this class, the runtime instances are created if necessary, * stored each ReactionInstance, and assigned levels (maximum number of * upstream reaction instances), deadlines, and single dominating reactions. - * + * * After creation, the resulting graph will be empty unless there are causality * cycles, in which case, the resulting graph is a graph of runtime reaction * instances that form cycles. - * + * * @author Marten Lohstroh * @author Edward A. Lee */ public class ReactionInstanceGraph extends PrecedenceGraph { - + /** - * Create a new graph by traversing the maps in the named instances - * embedded in the hierarchy of the program. + * Create a new graph by traversing the maps in the named instances + * embedded in the hierarchy of the program. */ public ReactionInstanceGraph(ReactorInstance main) { this.main = main; @@ -74,12 +74,12 @@ public ReactionInstanceGraph(ReactorInstance main) { * The main reactor instance that this graph is associated with. */ public final ReactorInstance main; - + /////////////////////////////////////////////////////////// //// Public methods - + /** - * Rebuild this graph by clearing and repeating the traversal that + * Rebuild this graph by clearing and repeating the traversal that * adds all the nodes and edges. */ public void rebuild() { @@ -89,7 +89,7 @@ public void rebuild() { // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. // System.out.println(toDOT()); - // Assign a level to each reaction. + // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. assignLevels(); if (nodeCount() != 0) { @@ -109,9 +109,9 @@ public void rebuildAndAssignDeadlines() { assignInferredDeadlines(); this.clear(); } - + /* - * Get an array of non-negative integers representing the number of reactions + * Get an array of non-negative integers representing the number of reactions * per each level, where levels are indices of the array. */ public Integer[] getNumReactionsPerLevel() { @@ -133,7 +133,7 @@ public int getBreadth() { /////////////////////////////////////////////////////////// //// Protected methods - + /** * Add to the graph edges between the given reaction and all the reactions * that depend on the specified port. @@ -144,19 +144,19 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti // Use mixed-radix numbers to increment over the ranges. List srcRuntimes = reaction.getRuntimeInstances(); List eventualDestinations = port.eventualDestinations(); - + int srcDepth = (port.isInput())? 2 : 1; - + for (SendRange sendRange : eventualDestinations) { for (RuntimeRange dstRange : sendRange.destinations) { - + int dstDepth = (dstRange.instance.isOutput())? 2 : 1; MixedRadixInt dstRangePosition = dstRange.startMR(); int dstRangeCount = 0; MixedRadixInt sendRangePosition = sendRange.startMR(); int sendRangeCount = 0; - + while (dstRangeCount++ < dstRange.width) { int srcIndex = sendRangePosition.get(srcDepth); int dstIndex = dstRangePosition.get(dstDepth); @@ -177,7 +177,7 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { srcRuntime.deadline = dstRuntime.deadline; } - + // If this seems to be a single dominating reaction, set it. // If another upstream reaction shows up, then this will be // reset to null. @@ -203,7 +203,7 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti } /** - * Build the graph by adding nodes and edges based on the given reactor + * Build the graph by adding nodes and edges based on the given reactor * instance. * @param reactor The reactor on the basis of which to add nodes and edges. */ @@ -211,12 +211,12 @@ protected void addNodesAndEdges(ReactorInstance reactor) { ReactionInstance previousReaction = null; for (ReactionInstance reaction : reactor.reactions) { List runtimes = reaction.getRuntimeInstances(); - + // Add reactions of this reactor. for (Runtime runtime : runtimes) { - this.addNode(runtime); + this.addNode(runtime); } - + // If this is not an unordered reaction, then create a dependency // on any previously defined reaction. if (!reaction.isUnordered) { @@ -251,12 +251,12 @@ protected void addNodesAndEdges(ReactorInstance reactor) { addNodesAndEdges(child); } } - + /////////////////////////////////////////////////////////// //// Private fields - + /** - * Number of reactions per level, represented as a list of + * Number of reactions per level, represented as a list of * integers where the indices are the levels. */ private List numReactionsPerLevel = new ArrayList<>(List.of(0)); @@ -268,7 +268,7 @@ protected void addNodesAndEdges(ReactorInstance reactor) { * Analyze the dependencies between reactions and assign each reaction * instance a level. This method removes nodes from this graph as it * assigns levels. Any remaining nodes are part of causality cycles. - * + * * This procedure is based on Kahn's algorithm for topological sorting. * Rather than establishing a total order, we establish a partial order. * In this order, the level of each reaction is the least upper bound of @@ -276,13 +276,13 @@ protected void addNodesAndEdges(ReactorInstance reactor) { */ private void assignLevels() { List start = new ArrayList<>(rootNodes()); - + // All root nodes start with level 0. for (Runtime origin : start) { origin.level = 0; } - // No need to do any of this if there are no root nodes; + // No need to do any of this if there are no root nodes; // the graph must be cyclic. while (!start.isEmpty()) { Runtime origin = start.remove(0); @@ -293,7 +293,7 @@ private void assignLevels() { for (Runtime effect : downstreamAdjacentNodes) { // Stage edge between origin and effect for removal. toRemove.add(effect); - + // Update level of downstream node. effect.level = origin.level + 1; } @@ -306,7 +306,7 @@ private void assignLevels() { start.add(effect); } } - + // Remove visited origin. removeNode(origin); @@ -314,16 +314,16 @@ private void assignLevels() { adjustNumReactionsPerLevel(origin.level, 1); } } - + /** * This function assigns inferred deadlines to all the reactions in the graph. * It is modeled after `assignLevels` but it starts at the leaf nodes and uses * Kahns algorithm to build a reverse topologically sorted graph - * + * */ private void assignInferredDeadlines() { List start = new ArrayList<>(leafNodes()); - + // All leaf nodes have deadline initialized to their declared deadline or MAX_VALUE while (!start.isEmpty()) { Runtime origin = start.remove(0); @@ -334,7 +334,7 @@ private void assignInferredDeadlines() { for (Runtime upstream : upstreamAdjacentNodes) { // Stage edge between origin and upstream for removal. toRemove.add(upstream); - + // Update deadline of upstream node if origins deadline is earlier. if (origin.deadline.isEarlierThan(upstream.deadline)) { upstream.deadline = origin.deadline; @@ -349,12 +349,12 @@ private void assignInferredDeadlines() { start.add(upstream); } } - + // Remove visited origin. removeNode(origin); } } - + /** * Adjust {@link #numReactionsPerLevel} at index level by * adding to the previously recorded number valueToAdd. @@ -411,7 +411,7 @@ public String toDOT() { var currentLevelNodes = groupedNodes.get(level); for (var node: currentLevelNodes) { // Draw the node - var label = CUtil.getName(node.getReaction().getParent().reactorDeclaration) + "." + node.getReaction().getName(); + var label = CUtil.getName(node.getReaction().getParent().reactorDefinition) + "." + node.getReaction().getName(); // Need a positive number to name the nodes in GraphViz var labelHashCode = label.hashCode() & 0xfffffff; dotRepresentation.pr(" node_" + labelHashCode + " [label=\""+ label +"\"];"); @@ -419,7 +419,7 @@ public String toDOT() { // Draw the edges var downstreamNodes = getDownstreamAdjacentNodes(node); for (var downstreamNode: downstreamNodes) { - var downstreamLabel = CUtil.getName(downstreamNode.getReaction().getParent().reactorDeclaration) + "." + downstreamNode.getReaction().getName(); + var downstreamLabel = CUtil.getName(downstreamNode.getReaction().getParent().reactorDefinition) + "." + downstreamNode.getReaction().getName(); edges.append(" node_" + labelHashCode + " -> node_" + (downstreamLabel.hashCode() & 0xfffffff) + ";\n" ); diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index ba85ae8288..cb8f67b073 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -66,6 +66,7 @@ import org.lflang.lf.WidthSpec; /** +<<<<<<< HEAD * 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 @@ -78,6 +79,23 @@ * 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. +======= + * 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. +>>>>>>> origin * * @author Marten Lohstroh * @author Edward A. Lee @@ -247,6 +265,7 @@ public void clearCaches(boolean includingRuntimes) { cachedCycles = null; } +<<<<<<< HEAD /** * 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 @@ -268,6 +287,64 @@ public Set> getCycles() { for (var cycle : cycleNodes) { for (ReactionInstance.Runtime runtime : cycle) { reactions.add(runtime.getReaction()); +======= + /** + * 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); +>>>>>>> origin } } // Need to figure out which ports are involved in the cycles. @@ -283,6 +360,7 @@ public Set> getCycles() { cachedCycles.addAll(ports); } +<<<<<<< HEAD return cachedCycles; } @@ -296,6 +374,107 @@ public PortInstance getInput(String name) { if (port.getName().equals(name)) { return port; } +======= + /** + * 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; + } + + /** + * 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; + } + + /** + * 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); + } + for (PortInstance port : inputs) { + port.clearCaches(); + } + for (PortInstance port : outputs) { + port.clearCaches(); + } + for (ReactionInstance reaction : reactions) { + reaction.clearCaches(includingRuntimes); + } + cachedCycles = 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()); + } + } + // 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 cachedCycles; +>>>>>>> origin } return null; } @@ -321,6 +500,7 @@ public String uniqueID() { if (this.isMainOrFederated()) { return super.uniqueID() + "_main"; } +<<<<<<< HEAD return super.uniqueID(); } @@ -449,6 +629,14 @@ private static final class ParameterInliner extends LfExpressionVisitor.LfExpressionDeepCopyVisitor { static final ParameterInliner INSTANCE = new ParameterInliner(); +======= + + /** + * 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. + */ +>>>>>>> origin @Override public Expression visitParameterRef(ParameterReference expr, ReactorInstance instance) { if (!ASTUtils.belongsTo(expr.getParameter(), instance.definition)) { @@ -483,6 +671,7 @@ public Expression visitParameterRef(ParameterReference expr, ReactorInstance ins return defaultValue; } } +<<<<<<< HEAD } /** @@ -501,6 +690,28 @@ public List instantiations() { _instantiations.addAll(parent.instantiations()); } } +======= + + /** + * 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); +>>>>>>> origin } return _instantiations; } @@ -536,6 +747,7 @@ public boolean isParent(ReactorInstance r) { if (p == r) return true; p = p.getParent(); } +<<<<<<< HEAD return false; } @@ -738,18 +950,65 @@ protected void createReactionInstances() { for (Reaction reaction : reactions) { if (AttributeUtils.isUnordered(reaction)) { unorderedReactions.add(reaction); +======= + + /** + * 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; +>>>>>>> origin } // Create the reaction instance. var reactionInstance = new ReactionInstance(reaction, this, unorderedReactions.contains(reaction), count++); +<<<<<<< HEAD // Add the reaction instance to the map of reactions for this // reactor. this.reactions.add(reactionInstance); } +======= + /** + * 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; +>>>>>>> origin } } +<<<<<<< HEAD /** Create all the watchdog instances of this reactor instance. */ protected void createWatchdogInstances() { List watchdogs = ASTUtils.allWatchdogs(reactorDefinition); @@ -757,6 +1016,46 @@ protected void createWatchdogInstances() { for (Watchdog watchdog : watchdogs) { // Create the watchdog instance. var watchdogInstance = new WatchdogInstance(watchdog, this); +======= + /** + * 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()); + } +>>>>>>> origin // Add the watchdog instance to the list of watchdogs for this // reactor. @@ -791,6 +1090,7 @@ private ReactorInstance( this.reactorDeclaration = definition.getReactorClass(); this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); +<<<<<<< HEAD // check for recursive instantiation var currentParent = parent; var foundSelfAsParent = false; @@ -799,6 +1099,158 @@ private ReactorInstance( if (currentParent.reactorDefinition == this.reactorDefinition) { foundSelfAsParent = true; currentParent = null; // break +======= + 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 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; + } + + /** + * 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(); + } + 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; + } + + /** + * 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; + } + + /** + * 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()); +>>>>>>> origin } else { currentParent = currentParent.parent; } @@ -810,6 +1262,7 @@ private ReactorInstance( reporter.reportError(definition, "Recursive reactor instantiation."); } +<<<<<<< HEAD // If the reactor definition is null, give up here. Otherwise, diagram generation // will fail an NPE. if (reactorDefinition == null) { @@ -916,6 +1369,381 @@ private void establishPortConnections() { if (!srcRanges.hasNext()) { if (dstRanges.hasNext()) { reporter.reportWarning(connection, "No sources to provide inputs."); +======= + /** + * 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; + } + + /** + * 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; + } + + /** + * 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; + } + + /** 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; + } + + /** + * 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); + } + // 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); + } + } + } + + /** + * 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)); + } + + //////////////////////////////////////// + //// 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)); + } + + // 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(); + } + } + } + + ////////////////////////////////////////////////////// + //// 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); + } + + /** + * 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(); + } + } + } + } + + /** + * 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; +>>>>>>> origin } return; } else if (!dstRanges.hasNext()) { @@ -1035,6 +1863,7 @@ private List> listPortInstances( if (!(portRef.getVariable() instanceof Port)) { reporter.reportError(portRef, "Not a port."); return result; +<<<<<<< HEAD } // First, figure out which reactor we are dealing with. // The reactor we want is the container of the port. @@ -1047,6 +1876,118 @@ private List> listPortInstances( // Skip this portRef so that diagram synthesis can complete. if (reactor != null) { PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); +======= + } + + /** + * 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); + } + } + // 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; + } + return result; + } +>>>>>>> origin Set interleaved = new LinkedHashSet<>(); if (portRef.isInterleaved()) { @@ -1055,6 +1996,7 @@ private List> listPortInstances( // contained reactors. interleaved.add(portInstance.parent); } +<<<<<<< HEAD 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 @@ -1073,16 +2015,40 @@ private List> listPortInstances( portParentWidth = 1; } int widthBound = portWidth * portParentWidth; +======= + } + + ////////////////////////////////////////////////////// + //// Private fields. +>>>>>>> origin // If either of these widths cannot be determined, assume infinite. if (portWidth < 0) widthBound = Integer.MAX_VALUE; if (portParentWidth < 0) widthBound = Integer.MAX_VALUE; +<<<<<<< HEAD if (widthBound < range.width) { // Need to split the range. tails.add(range.tail(widthBound)); range = range.head(widthBound); } +======= + /** + * 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; +>>>>>>> origin } result.add(range); } diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index bc11061f0d..7a9b85c453 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -161,7 +161,7 @@ private List> getValidationStrategies() { */ private Pair getValidationStrategy(Path generatedFile) { List sorted = getPossibleStrategies().stream() - .sorted(Comparator.comparingInt(vs -> -vs.getPriority())).collect(Collectors.toList()); + .sorted(Comparator.comparingInt(vs -> -vs.getPriority())).toList(); for (ValidationStrategy strategy : sorted) { LFCommand validateCommand = strategy.getCommand(generatedFile); if (validateCommand != null) { diff --git a/org.lflang/src/org/lflang/generator/c/CActionGenerator.java b/org.lflang/src/org/lflang/generator/c/CActionGenerator.java index 1c4a1b45e8..af41440641 100644 --- a/org.lflang/src/org/lflang/generator/c/CActionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CActionGenerator.java @@ -97,13 +97,12 @@ public static String generateTokenInitializer( */ public static void generateDeclarations( Reactor reactor, - ReactorDecl decl, CodeBuilder body, CodeBuilder constructorCode ) { for (Action action : ASTUtils.allActions(reactor)) { var actionName = action.getName(); - body.pr(action, CGenerator.variableStructType(action, decl)+" _lf_"+actionName+";"); + body.pr(action, CGenerator.variableStructType(action, reactor, false)+" _lf_"+actionName+";"); // Initialize the trigger pointer in the action. constructorCode.pr(action, "self->_lf_"+actionName+".trigger = &self->_lf__"+actionName+";"); } @@ -121,11 +120,12 @@ public static void generateDeclarations( * @return The auxiliary struct for the port as a string */ public static String generateAuxiliaryStruct( - ReactorDecl decl, + Reactor r, Action action, Target target, CTypes types, - CodeBuilder federatedExtension + CodeBuilder federatedExtension, + boolean userFacing ) { var code = new CodeBuilder(); code.pr("typedef struct {"); @@ -145,7 +145,7 @@ public static String generateAuxiliaryStruct( code.pr(valueDeclaration(action, target, types)); code.pr(federatedExtension.toString()); code.unindent(); - code.pr("} " + variableStructType(action, decl) + ";"); + code.pr("} " + variableStructType(action, r, userFacing) + ";"); return code.toString(); } diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java index 6710a0eb68..3f5eb4a5b7 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java @@ -212,6 +212,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE core)"); + cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC .)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/api)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core)"); diff --git a/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java b/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java index 0cc2199cae..8944fd0796 100644 --- a/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java @@ -1,8 +1,7 @@ package org.lflang.generator.c; -import org.lflang.federated.generator.FederateInstance; import org.lflang.generator.CodeBuilder; -import org.lflang.lf.ReactorDecl; +import org.lflang.lf.Reactor; /** * Generates C constructor code for a reactor. @@ -16,12 +15,12 @@ public class CConstructorGenerator { * go into the constructor. */ public static String generateConstructor( - ReactorDecl reactor, + Reactor reactor, String constructorCode ) { var structType = CUtil.selfType(reactor); var code = new CodeBuilder(); - code.pr(structType+"* new_"+reactor.getName()+"() {"); + code.pr(structType+"* new_"+CUtil.getName(reactor)+"() {"); code.indent(); code.pr(structType+"* self = ("+structType+"*)_lf_new_reactor(sizeof("+structType+"));"); code.pr(constructorCode); @@ -30,4 +29,8 @@ public static String generateConstructor( code.pr("}"); return code.toString(); } + + public static String generateConstructorPrototype(Reactor reactor) { + return CUtil.selfType(reactor)+"* new_"+CUtil.getName(reactor)+"();"; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CFileConfig.java b/org.lflang/src/org/lflang/generator/c/CFileConfig.java index 8bd70b74c3..362b8645b5 100644 --- a/org.lflang/src/org/lflang/generator/c/CFileConfig.java +++ b/org.lflang/src/org/lflang/generator/c/CFileConfig.java @@ -8,12 +8,18 @@ import org.lflang.FileConfig; public class CFileConfig extends FileConfig { + private final Path includePath; public CFileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) throws IOException { super(resource, srcGenBasePath, useHierarchicalBin); + var includeDir = getOutPath().resolve("include"); + includePath = !useHierarchicalBin ? includeDir : includeDir.resolve(getOutPath().relativize(srcPath)).resolve(srcFile.getFileName().toString().split("\\.")[0]); } -} - - - + public Path getIncludePath() { + return includePath; + } + public String getRuntimeIncludePath() { + return "/lib/c/reactor-c/include"; + } +} diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 0ded0e2bc2..2ed77b27e1 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -25,8 +25,7 @@ package org.lflang.generator.c; import static org.lflang.ASTUtils.allActions; -import static org.lflang.ASTUtils.allInputs; -import static org.lflang.ASTUtils.allOutputs; +import static org.lflang.ASTUtils.allPorts; import static org.lflang.ASTUtils.allReactions; import static org.lflang.ASTUtils.allStateVars; import static org.lflang.ASTUtils.convertToEmptyListIfNull; @@ -36,12 +35,16 @@ import static org.lflang.ASTUtils.toText; import static org.lflang.util.StringUtil.addDoubleQuotes; +<<<<<<< HEAD import com.google.common.base.Objects; import com.google.common.collect.Iterables; +======= +>>>>>>> origin import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.regex.Pattern; @@ -83,7 +86,6 @@ import org.lflang.lf.Instantiation; import org.lflang.lf.Mode; import org.lflang.lf.Model; -import org.lflang.lf.Output; import org.lflang.lf.Port; import org.lflang.lf.Preamble; import org.lflang.lf.Reaction; @@ -94,6 +96,12 @@ import org.lflang.lf.Watchdog; import org.lflang.util.ArduinoUtil; import org.lflang.util.FileUtil; +<<<<<<< HEAD +======= + +import com.google.common.base.Objects; +import com.google.common.collect.Iterables; +>>>>>>> origin /** * Generator for C target. This class generates C code defining each reactor class given in the @@ -584,6 +592,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // ); // FIXME: Move to FedGenerator // Create the compiler to be used later +<<<<<<< HEAD var cCompiler = new CCompiler(targetConfig, threadFileConfig, errorReporter, CppMode); try { if (!cCompiler.runCCompiler(generator, context)) { @@ -592,6 +601,167 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // 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(); +======= + 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 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); +>>>>>>> origin } else { context.finish(GeneratorResult.Status.COMPILED, null); } @@ -601,6 +771,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { } } +<<<<<<< HEAD // If a build directive has been given, invoke it now. // Note that the code does not get cleaned in this case. if (!targetConfig.noCompile) { @@ -618,6 +789,16 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { } else { context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); } +======= + private void generateCodeFor( + String lfModuleName + ) throws IOException { + startTimeStepIsPresentCount = 0; + code.pr(generateDirectives()); + code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); + // Generate code for each reactor. + generateReactorDefinitions(); +>>>>>>> origin // In case we are in Eclipse, make sure the generated code is visible. GeneratorUtils.refreshProject(resource, context.getMode()); @@ -631,6 +812,7 @@ private void generateCodeFor(String lfModuleName) { // Generate code for each reactor. generateReactorDefinitions(); +<<<<<<< HEAD // Generate main instance, if there is one. // Note that any main reactors in imported files are ignored. // Skip generation if there are cycles. @@ -653,6 +835,16 @@ private void generateCodeFor(String lfModuleName) { // Add counters for modal initialization initializeTriggerObjects.pr( CModesGenerator.generateModalInitalizationCounters(hasModalReactors)); +======= + 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)); +>>>>>>> origin // Create an array of arrays to store all self structs. // This is needed because connections cannot be established until @@ -897,6 +1089,7 @@ private void generateReactorDefinitions() { generateReactorClass(this.mainDef.getReactorClass()); } +<<<<<<< HEAD if (mainDef == null) { // Generate code for each reactor that was not instantiated in main or its children. for (Reactor r : reactors) { @@ -908,6 +1101,33 @@ private void generateReactorDefinitions() { // so that reaction bodies are checked). if (declarations.isEmpty()) { generateReactorClass(r); +======= + /** + * 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()); + } +>>>>>>> origin } } } @@ -1029,6 +1249,7 @@ protected void generateUserPreamblesForReactor(Reactor reactor) { } } +<<<<<<< HEAD /** * Generate a constructor for the specified reactor in the specified federate. * @@ -1056,12 +1277,290 @@ protected void generateAuxiliaryStructs(ReactorDecl decl) { var federatedExtension = new CodeBuilder(); federatedExtension.pr( """ +======= + /** + * 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); + } + } + } + } + + /** 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 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); + } + } + } + + /** + * 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"); + } + } + + + /** + * 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 + ); + } + } + + //////////////////////////////////////////// + //// 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\" {"); + } + 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); + } + + 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); + } + + /** + * 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 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(""" +>>>>>>> origin #ifdef FEDERATED #ifdef FEDERATED_DECENTRALIZED %s intended_tag; #endif %s physical_time_of_arrival; #endif +<<<<<<< HEAD """ .formatted(types.getTargetTagType(), types.getTargetTimeType())); // First, handle inputs. @@ -1075,6 +1574,96 @@ protected void generateAuxiliaryStructs(ReactorDecl decl) { code.pr( CPortGenerator.generateAuxiliaryStruct( decl, output, getTarget(), errorReporter, types, federatedExtension)); +======= + """.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 + )); + } + } + + /** + * 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 + ";"); +>>>>>>> origin } // Finally, handle actions. // The very first item on this struct needs to be @@ -1087,6 +1676,7 @@ protected void generateAuxiliaryStructs(ReactorDecl decl) { } } +<<<<<<< HEAD /** * Generate the self struct type definition for the specified reactor in the specified federate. * @@ -1278,6 +1868,43 @@ private void generateInteractingContainedReactors( + "] = &self->_lf__reaction_" + index + ";"); +======= + /** + * 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 + "]"; +>>>>>>> origin } constructorCode.pr( port, portOnSelf + "_trigger.reactions = " + portOnSelf + "_reactions;"); @@ -1293,11 +1920,56 @@ private void generateInteractingContainedReactors( portOnSelf + "_trigger.last = NULL;", portOnSelf + "_trigger.number_of_reactions = " + triggered.size() + ";")); +<<<<<<< HEAD // Set the physical_time_of_arrival constructorCode.pr( port, CExtensionUtils.surroundWithIfFederated( portOnSelf + "_trigger.physical_time_of_arrival = NEVER;")); +======= + // 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(); +>>>>>>> origin if (containedReactor.getWidthSpec() != null) { constructorCode.unindent(); @@ -1345,6 +2017,7 @@ public void generateReactions(ReactorDecl decl) { } } +<<<<<<< HEAD /** * 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 @@ -1358,8 +2031,37 @@ protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactio code.pr( CReactionGenerator.generateReaction( +======= + /** 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( +>>>>>>> origin reaction, - decl, + r, reactionIndex, mainDef, errorReporter, @@ -1820,12 +2522,80 @@ private void generateInitializeActionToken(ReactorInstance reactor) { } } +<<<<<<< HEAD var selfStruct = CUtil.reactorRef(action.getParent()); initializeTriggerObjects.pr( CActionGenerator.generateTokenInitializer(selfStruct, action.getName(), payloadSize)); } } } +======= + 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 + * 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) + ); + } + } + + /** + * 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); +>>>>>>> origin /** * Generate code that is executed while the reactor instance is being initialized. This is @@ -2182,6 +2952,7 @@ private void createMainReactorInstance() { } } +<<<<<<< HEAD /** * Generate an array of self structs for the reactor and one for each of its children. * @@ -2193,6 +2964,275 @@ private void generateSelfStructs(ReactorInstance r) { initializeTriggerObjects.pr("SUPPRESS_UNUSED_WARNING(" + CUtil.reactorRefName(r) + ");"); for (ReactorInstance child : r.children) { generateSelfStructs(child); +======= + /** + * 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 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 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 + )); + } + } + + /** + * 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 + )); + } + } + + @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"); + } + 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); + } + } + + /** + * 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")); + } + 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; + } + 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()) + ); + } + } + } + + } + + /** + * 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); + } +>>>>>>> origin } } } diff --git a/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java b/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java index febac3362c..8228ce320c 100644 --- a/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java @@ -72,7 +72,7 @@ public static String generateMethod( code.indent(); // Define the "self" struct. - String structType = CUtil.selfType(decl); + String structType = CUtil.selfType(ASTUtils.toDefinition(decl)); // A null structType means there are no inputs, state, // or anything else. No need to declare it. if (structType != null) { @@ -139,7 +139,7 @@ public static void signatures( * @return The function name for the method. */ private static String methodFunctionName(ReactorDecl reactor, Method method) { - return reactor.getName().toLowerCase() + "_method_" + method.getName(); + return CUtil.getName(ASTUtils.toDefinition(reactor)) + "_method_" + method.getName(); } /** diff --git a/org.lflang/src/org/lflang/generator/c/CPortGenerator.java b/org.lflang/src/org/lflang/generator/c/CPortGenerator.java index 0db76a16ff..1b22b41f9a 100644 --- a/org.lflang/src/org/lflang/generator/c/CPortGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CPortGenerator.java @@ -44,22 +44,28 @@ public static void generateDeclarations( * Generate the struct type definitions for the port of the * reactor * - * @param decl The reactor declaration + * @param r The reactor * @param port The port to generate the struct * @param target The target of the code generation (C, CCpp or Python) * @param errorReporter The error reporter * @param types The helper object for types related stuff * @param federatedExtension The code needed to support federated execution + * @param userFacing Whether this struct is to be presented in a user-facing header + * @param decl The reactorDecl if this struct is for the header of this reactor's container; + * null otherwise * @return The auxiliary struct for the port as a string */ public static String generateAuxiliaryStruct( - ReactorDecl decl, + Reactor r, Port port, Target target, ErrorReporter errorReporter, CTypes types, - CodeBuilder federatedExtension + CodeBuilder federatedExtension, + boolean userFacing, + ReactorDecl decl ) { + assert decl == null || userFacing; var code = new CodeBuilder(); code.pr("typedef struct {"); code.indent(); @@ -79,10 +85,16 @@ public static String generateAuxiliaryStruct( code.pr(valueDeclaration(port, target, errorReporter, types)); code.pr(federatedExtension.toString()); code.unindent(); - code.pr("} "+variableStructType(port, decl)+";"); + var name = decl != null ? localPortName(decl, port.getName()) + : variableStructType(port, r, userFacing); + code.pr("} " + name + ";"); return code.toString(); } + public static String localPortName(ReactorDecl decl, String portName) { + return decl.getName().toLowerCase() + "_" + portName + "_t"; + } + /** * Allocate memory for the input port. * @param input The input port @@ -210,21 +222,21 @@ private static void generateInputDeclarations( if (ASTUtils.isMultiport(input)) { body.pr(input, String.join("\n", "// Multiport input array will be malloc'd later.", - variableStructType(input, decl)+"** _lf_"+inputName+";", + variableStructType(input, reactor, false)+"** _lf_"+inputName+";", "int _lf_"+inputName+"_width;", "// Default input (in case it does not get connected)", - variableStructType(input, decl)+" _lf_default__"+inputName+";", + variableStructType(input, reactor, false)+" _lf_default__"+inputName+";", "// Struct to support efficiently reading sparse inputs.", "lf_sparse_io_record_t* _lf_"+inputName+"__sparse;" )); } else { // input is not a multiport. body.pr(input, String.join("\n", - variableStructType(input, decl)+"* _lf_"+inputName+";", + variableStructType(input, reactor, false)+"* _lf_"+inputName+";", "// width of -2 indicates that it is not a multiport.", "int _lf_"+inputName+"_width;", "// Default input (in case it does not get connected)", - variableStructType(input, decl)+" _lf_default__"+inputName+";" + variableStructType(input, reactor, false)+" _lf_default__"+inputName+";" )); constructorCode.pr(input, String.join("\n", @@ -256,7 +268,7 @@ private static void generateOutputDeclarations( if (ASTUtils.isMultiport(output)) { body.pr(output, String.join("\n", "// Array of output ports.", - variableStructType(output, decl)+"* _lf_"+outputName+";", + variableStructType(output, reactor, false)+"* _lf_"+outputName+";", "int _lf_"+outputName+"_width;", "// An array of pointers to the individual ports. Useful", "// for the lf_set macros to work out-of-the-box for", @@ -264,11 +276,11 @@ private static void generateOutputDeclarations( "// value can be accessed via a -> operator (e.g.,foo[i]->value).", "// So we have to handle multiports specially here a construct that", "// array of pointers.", - variableStructType(output, decl)+"** _lf_"+outputName+"_pointers;" + variableStructType(output, reactor, false)+"** _lf_"+outputName+"_pointers;" )); } else { body.pr(output, String.join("\n", - variableStructType(output, decl)+" _lf_"+outputName+";", + variableStructType(output, reactor, false)+" _lf_"+outputName+";", "int _lf_"+outputName+"_width;" )); } diff --git a/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java b/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java index d78456e4ae..8ca0404d75 100644 --- a/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java @@ -36,34 +36,26 @@ public static String generateIncludeStatements( if (cppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) { code.pr("extern \"C\" {"); } - - String relPathHeader = ""; - if (targetConfig.platformOptions.platform == Platform.ARDUINO) { - relPathHeader = "src/include/"; - - CCoreFilesUtils.getCTargetHeader().forEach( - it -> code.pr("#include " + StringUtil.addDoubleQuotes("src/" + it)) - ); - } else { - CCoreFilesUtils.getCTargetHeader().forEach( - it -> code.pr("#include " + StringUtil.addDoubleQuotes(it)) - ); - } - code.pr("#include \"" + relPathHeader + "core/reactor.h\""); - code.pr("#include \"" + relPathHeader + "core/reactor_common.h\""); + code.pr("#include "); + code.pr("#include \"include/core/platform.h\""); + CCoreFilesUtils.getCTargetHeader().forEach( + it -> code.pr("#include " + StringUtil.addDoubleQuotes(it)) + ); + code.pr("#include \"include/core/reactor.h\""); + code.pr("#include \"include/core/reactor_common.h\""); if (targetConfig.threading) { - code.pr("#include \"" + relPathHeader + "core/threaded/scheduler.h\""); + code.pr("#include \"include/core/threaded/scheduler.h\""); } if (tracing != null) { - code.pr("#include \"" + relPathHeader + "core/trace.h\""); + code.pr("#include \"include/core/trace.h\""); } - code.pr("#include \"" + relPathHeader + "core/mixed_radix.h\""); - code.pr("#include \"" + relPathHeader + "core/port.h\""); + code.pr("#include \"include/core/mixed_radix.h\""); + code.pr("#include \"include/core/port.h\""); code.pr("int lf_reactor_c_main(int argc, const char* argv[]);"); if(targetConfig.fedSetupPreamble != null) { - code.pr("#include \"" + relPathHeader + "core/federated/federate.h\""); - code.pr("#include \"" + relPathHeader + "core/federated/net_common.h\""); + code.pr("#include \"include/core/federated/federate.h\""); + code.pr("#include \"include/core/federated/net_common.h\""); } if (cppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) { code.pr("}"); diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index b7f72e216a..9625122e2b 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -13,7 +13,6 @@ import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetConfig; -import org.lflang.TargetProperty.Platform; import org.lflang.federated.extensions.CExtensionUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Action; @@ -22,6 +21,7 @@ import org.lflang.lf.Code; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; +import org.lflang.lf.LfFactory; import org.lflang.lf.Mode; import org.lflang.lf.ModeTransition; import org.lflang.lf.Output; @@ -37,6 +37,7 @@ import org.lflang.util.StringUtil; public class CReactionGenerator { +<<<<<<< HEAD protected static String DISABLE_REACTION_INITIALIZATION_MARKER = "// **** Do not include initialization code in this reaction."; @@ -61,6 +62,27 @@ public static String generateInitializationForReaction( 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) + + /** + * 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); +>>>>>>> origin // Construct the reactionInitialization code to go into // the body of the function before the verbatim code. @@ -68,6 +90,7 @@ public static String generateInitializationForReaction( CodeBuilder code = new CodeBuilder(); +<<<<<<< HEAD // Define the "self" struct. String structType = CUtil.selfType(decl); // A null structType means there are no inputs, state, @@ -80,6 +103,169 @@ public static String generateInitializationForReaction( + "* self = (" + structType + "*)instance_args; SUPPRESS_UNUSED_WARNING(self);")); +======= + // 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);" + )); + } + + // 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(); + } + + // 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<>(); + + // 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<>(); + + // 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()); + } + } + + // 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." + ); + } + } + } + } + // 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(); +>>>>>>> origin } // Do not generate the initialization code if the body is marked @@ -129,6 +315,7 @@ public static String generateInitializationForReaction( (Action) triggerAsVarRef.getVariable(), decl, types)); actionsAsTriggers.add((Action) triggerAsVarRef.getVariable()); } +<<<<<<< HEAD } } if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { @@ -194,6 +381,27 @@ public static String generateInitializationForReaction( errorReporter.reportError( reaction, "In generateReaction(): " + name + " not a valid mode of this reactor."); } +======= + 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+");"); + } +>>>>>>> origin } else { if (variable instanceof Output) { reactionInitialization.pr( @@ -238,6 +446,7 @@ public static String generateInitializationForReaction( return code.toString(); } +<<<<<<< HEAD /** * 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 @@ -291,6 +500,78 @@ public static int maxContainedReactorBankWidth( int candidate = ASTUtils.width(containedReactor.getWidthSpec(), nestedBreadcrumbs); if (candidate > result) { result = candidate; +======= + /** + * 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); + + 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+";"); + } + } +>>>>>>> origin } } else { // Found some other instantiation, not the parent. @@ -308,6 +589,7 @@ public static int maxContainedReactorBankWidth( return result; } +<<<<<<< HEAD /** * Generate code for the body of a reaction that takes an input and schedules an action with the * value of that input. @@ -327,6 +609,22 @@ public static String generateDelayBody(String ref, String actionName, boolean is "}") : "lf_schedule_copy(" + actionName + ", 0, &" + ref + "->value, 1); // Length is 1."; } +======= + /** 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(); +>>>>>>> origin public static String generateForwardBody( String outputName, String targetType, String actionName, boolean isTokenType) { @@ -382,6 +680,7 @@ private static void generateVariablesForSendingToContainedReactors( if (definition.getWidthSpec() != null) { // Contained reactor is a bank. builder.pr( +<<<<<<< HEAD String.join( "\n", "for (int bankIndex = 0; bankIndex < self->_lf_" + defWidth + "; bankIndex++) {", @@ -419,6 +718,335 @@ private static void generateVariablesForSendingToContainedReactors( + defName + "[_i]." + inputName +======= + 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 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 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(); + } + + /** + * 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 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+";"); + + // 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()); + } + } + + var deadlineFunctionPointer = "NULL"; + if (reaction.getDeadline() != null) { + // The following has to match the name chosen in generateReactions + var deadlineFunctionName = generateDeadlineFunctionName(reactor, reactionCount); + deadlineFunctionPointer = "&" + deadlineFunctionName; + } + + // 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; + } + + // 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++; + } + + // 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};")); + } + + // Handle builtin triggers. + if (startupReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(startupReactions, "startup", body, constructorCode); + } + // Handle shutdown triggers. + if (shutdownReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(shutdownReactions, "shutdown", body, constructorCode); + } + if (resetReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(resetReactions, "reset", body, constructorCode); + } + + // 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 +>>>>>>> origin + ";", " " + defName @@ -1276,6 +1904,7 @@ public static String generateReaction( generateStpFunctionHeader(decl, reactionIndex), init, reaction.getStp().getCode())); } +<<<<<<< HEAD // Now generate code for the deadline violation function, if there is one. if (reaction.getDeadline() != null) { code.pr( @@ -1359,6 +1988,142 @@ public static String generateReactionFunctionHeader(ReactorDecl decl, int reacti String functionName = generateReactionFunctionName(decl, reactionIndex); return generateFunctionHeader(functionName); } +======= + /** 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())); + + 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())); + } + + // 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); + } +>>>>>>> origin public static String generateStpFunctionHeader(ReactorDecl decl, int reactionIndex) { String functionName = generateStpFunctionName(decl, reactionIndex); diff --git a/org.lflang/src/org/lflang/generator/c/CReactorHeaderFileGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactorHeaderFileGenerator.java new file mode 100644 index 0000000000..68b05f656a --- /dev/null +++ b/org.lflang/src/org/lflang/generator/c/CReactorHeaderFileGenerator.java @@ -0,0 +1,208 @@ +package org.lflang.generator.c; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.lflang.ASTUtils; +import org.lflang.generator.CodeBuilder; +import org.lflang.lf.Instantiation; +import org.lflang.lf.LfFactory; +import org.lflang.lf.Parameter; +import org.lflang.lf.Reaction; +import org.lflang.lf.Reactor; +import org.lflang.lf.StateVar; +import org.lflang.lf.TriggerRef; +import org.lflang.lf.TypedVariable; +import org.lflang.lf.VarRef; +import org.lflang.util.FileUtil; + +/** Generate user-visible header files. */ +public class CReactorHeaderFileGenerator { + + /** Functional interface for generating auxiliary structs such as port structs. */ + public interface GenerateAuxiliaryStructs { + void generate(CodeBuilder b, Reactor r, boolean userFacing); + } + + /** Return the path to the user-visible header file that would be generated for {@code r}. */ + public static Path outputPath(Reactor r) { + return Path.of(Path.of(r.eResource().getURI().toFileString()) + .getFileName().toString().replaceFirst("[.][^.]+$", "")) + .resolve(r.getName() + ".h"); + } + + /** Generate the user-visible header file for {@code r}. */ + public static void doGenerate(CTypes types, Reactor r, CFileConfig fileConfig, GenerateAuxiliaryStructs generator, Function topLevelPreamble) throws IOException { + String contents = generateHeaderFile(types, r, generator, topLevelPreamble.apply(r)); + FileUtil.writeToFile(contents, fileConfig.getIncludePath().resolve(outputPath(r))); + } + private static String generateHeaderFile(CTypes types, Reactor r, GenerateAuxiliaryStructs generator, String topLevelPreamble) { + CodeBuilder builder = new CodeBuilder(); + appendIncludeGuard(builder, r); + builder.pr(topLevelPreamble); + appendPoundIncludes(builder); + appendSelfStruct(builder, types, r); + generator.generate(builder, r, true); + for (Reaction reaction : r.getReactions()) { + appendSignature(builder, reaction, r); + } + builder.pr("#endif"); + return builder.getCode(); + } + + private static void appendIncludeGuard(CodeBuilder builder, Reactor r) { + String macro = CUtil.getName(r) + "_H"; + builder.pr("#ifndef " + macro); + builder.pr("#define " + macro); + } + private static void appendPoundIncludes(CodeBuilder builder) { + builder.pr(""" + #ifdef __cplusplus + extern "C" { + #endif + #include "../include/api/api.h" + #include "../include/api/set.h" + #include "../include/core/reactor.h" + #ifdef __cplusplus + } + #endif + """); + } + + /** The type name of the user-facing version of the self struct. */ + private static String userFacingSelfType(Reactor r) { + return r.getName().toLowerCase() + "_self_t"; + } + + private static void appendSelfStruct(CodeBuilder builder, CTypes types, Reactor r) { + builder.pr("typedef struct " + userFacingSelfType(r) + "{"); + for (Parameter p : r.getParameters()) { + builder.pr(types.getTargetType(p) + " " + p.getName() + ";"); + } + for (StateVar s : r.getStateVars()) { + builder.pr(types.getTargetType(s) + " " + s.getName() + ";"); + } + builder.pr("int end[0]; // placeholder; MSVC does not compile empty structs"); + builder.pr("} " + userFacingSelfType(r) + ";"); + } + + /** Generate the signature of the reaction function of {@code r}. */ + private static void appendSignature(CodeBuilder builder, Reaction r, Reactor reactor) { + if (r.getName() != null) builder.pr("void " + r.getName() + "(" + reactionParameters(r, reactor) + ");"); + } + /** Return a string representation of the parameters of the reaction function of {@code r}. */ + private static String reactionParameters(Reaction r, Reactor reactor) { + return Stream.concat(Stream.of(userFacingSelfType(reactor) + "* self"), portVariableStream(r, reactor) + .map(it -> it.getType(true) + " " + it.getName())) + .collect(Collectors.joining(", ")); + } + + /** Generate initialization code that is needed if {@code r} is not inlined. */ + public static String nonInlineInitialization(Reaction r, Reactor reactor) { + var mainDef = LfFactory.eINSTANCE.createInstantiation(); + mainDef.setName(reactor.getName()); + mainDef.setReactorClass(ASTUtils.findMainReactor(reactor.eResource())); + return portVariableStream(r, reactor) + .map(it -> it.container == null ? "" : it.getWidth() == null ? + String.format("%s %s = (%s) %s;", it.getType(false), it.getAlias(), it.getType(false), it.getRvalue()) + : String.format(""" + %s %s[%s]; + for (int i = 0; i < %s; i++) { + %s[i] = (%s) self->_lf_%s[i].%s; + } + """, + it.getType(true).replaceFirst("\\*", ""), + it.getAlias(), + CReactionGenerator.maxContainedReactorBankWidth( + reactor.getInstantiations().stream() + .filter(instantiation -> ASTUtils.toDefinition(instantiation.getReactorClass()).equals(it.r)) + .findAny().orElseThrow(), + null, 0, mainDef), + "self->_lf_"+it.container.getName()+"_width", + it.getAlias(), + it.getType(true).replaceFirst("\\*", ""), + it.container.getName(), + it.getName())) + .collect(Collectors.joining("\n")); + } + + /** Return a string representation of the arguments passed to the function for {@code r}. */ + public static String reactionArguments(Reaction r, Reactor reactor) { + return Stream.concat(Stream.of(getApiSelfStruct(reactor)), portVariableStream(r, reactor) + .map(it -> String.format("((%s) %s)", it.getType(true), it.getAlias()))) + .collect(Collectors.joining(", ")); + } + + /** Return code for extracting the user-facing part of the self struct from the self struct. */ + private static String getApiSelfStruct(Reactor reactor) { + return "(" + userFacingSelfType(reactor) + "*) (((char*) self) + sizeof(self_base_t))"; + } + + /** Return a stream of all ports referenced by the signature of {@code r}. */ + private static Stream portVariableStream(Reaction r, Reactor defaultReactorClass) { + return varRefStream(r) + .map(it -> it.getVariable() instanceof TypedVariable tv ? + new PortVariable( + tv, + ASTUtils.toDefinition(it.getContainer() == null ? defaultReactorClass : it.getContainer().getReactorClass()), + it.getContainer()) + : null) + .filter(Objects::nonNull); + } + + /** + * A variable that refers to a port. + * @param tv The variable of the variable reference. + * @param r The reactor that contains the port. + * @param container The {@code Instantiation} referenced in the obtaining of {@code tv}, if + * applicable; {@code null} otherwise. + */ + private record PortVariable(TypedVariable tv, Reactor r, Instantiation container) { + String getType(boolean userFacing) { + var typeName = container == null ? + CGenerator.variableStructType(tv, r, userFacing) + : CPortGenerator.localPortName(container.getReactorClass(), getName()); + var isMultiport = ASTUtils.isMultiport(ASTUtils.allPorts(r).stream() + .filter(it -> it.getName().equals(tv.getName())) + .findAny().orElseThrow()); + return typeName + "*" + (getWidth() != null ? "*" : "") + (isMultiport ? "*" : ""); + } + /** The name of the variable as it appears in the LF source. */ + String getName() { + return tv.getName(); + } + /** The alias of the variable that should be used in code generation. */ + String getAlias() { + return getName(); // TODO: avoid naming conflicts + } + /** The width of the container, if applicable. */ + String getWidth() { + return container == null || container.getWidthSpec() == null ? null : "self->_lf_"+r.getName()+"_width"; + } + /** The representation of this port as used by the LF programmer. */ + String getRvalue() { + return container == null ? getName() : container.getName() + "." + getName(); + } + } + + private static Stream inputVarRefStream(Reaction reaction) { + return varRefStream(Stream.concat(reaction.getTriggers().stream(), reaction.getSources().stream())); + } + + private static Stream varRefStream(Stream toFilter) { + return toFilter.map(it -> it instanceof VarRef v ? v : null) + .filter(Objects::nonNull); + } + + private static Stream outputVarRefStream(Reaction reaction) { + return reaction.getEffects().stream(); + } + + private static Stream varRefStream(Reaction reaction) { + return Stream.concat(inputVarRefStream(reaction), outputVarRefStream(reaction)); + } +} diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 411a0a464b..69e9ec30ae 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -38,6 +38,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Objects; import java.util.stream.Collectors; +import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.InferredType; @@ -146,11 +147,12 @@ public static String channelIndexName(PortInstance port) { * reactor is main (to allow for instantiations that have the same name as * the main reactor or the .lf file). */ - public static String getName(ReactorDecl reactor) { - if (reactor instanceof Reactor r && r.isMain()) { - return reactor.getName() + "_main"; + public static String getName(Reactor reactor) { + String name = reactor.getName().toLowerCase() + reactor.hashCode(); + if (reactor.isMain()) { + return name + "_main"; } - return reactor.getName(); + return name; } /** @@ -514,16 +516,16 @@ public static String runtimeIndex(ReactorInstance reactor) { * @param reactor The reactor class. * @return The type of a self struct for the specified reactor class. */ - public static String selfType(ReactorDecl reactor) { - if (reactor instanceof Reactor r && r.isMain()) { - return reactor.getName().toLowerCase() + "_main_self_t"; + public static String selfType(Reactor reactor) { + if (reactor.isMain()) { + return "_" + CUtil.getName(reactor) + "_main_self_t"; } - return reactor.getName().toLowerCase() + "_self_t"; + return "_" + CUtil.getName(reactor) + "_self_t"; } /** Construct a unique type for the "self" struct of the class of the given reactor. */ public static String selfType(ReactorInstance instance) { - return selfType(instance.getDefinition().getReactorClass()); + return selfType(ASTUtils.toDefinition(instance.getDefinition().getReactorClass())); } /** diff --git a/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 45f6f10b0a..fb4f8605d2 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -100,41 +100,41 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { private fun declareTrigger(reaction: Reaction, trigger: TriggerRef): String = if (trigger is VarRef && trigger.variable is Port) { // if the trigger is a port, then it could be a multiport or contained in a bank - iterateOverAllPortsAndApply(trigger) { port: String -> "${reaction.name}.declare_trigger(&$port);" } + iterateOverAllPortsAndApply(trigger) { port: String -> "${reaction.codeName}.declare_trigger(&$port);" } } else { // treat as single trigger otherwise - "${reaction.name}.declare_trigger(&${trigger.name});" + "${reaction.codeName}.declare_trigger(&${trigger.name});" } private fun declareDependency(reaction: Reaction, dependency: VarRef): String { assert(dependency.variable is Port) // if the trigger is a port, then it could be a multiport or contained in a bank - return iterateOverAllPortsAndApply(dependency) { port: String -> "${reaction.name}.declare_dependency(&$port);" } + return iterateOverAllPortsAndApply(dependency) { port: String -> "${reaction.codeName}.declare_dependency(&$port);" } } private fun declareAntidependency(reaction: Reaction, antidependency: VarRef): String { val variable = antidependency.variable return if (variable is Port) { // if the trigger is a port, then it could be a multiport or contained in a bank - iterateOverAllPortsAndApply(antidependency) { port: String -> "${reaction.name}.declare_antidependency(&$port);" } + iterateOverAllPortsAndApply(antidependency) { port: String -> "${reaction.codeName}.declare_antidependency(&$port);" } } else { // treat as single antidependency otherwise - if (variable is Action) "${reaction.name}.declare_schedulable_action(&${antidependency.name});" - else "${reaction.name}.declare_antidependency(&${antidependency.name});" + if (variable is Action) "${reaction.codeName}.declare_schedulable_action(&${antidependency.name});" + else "${reaction.codeName}.declare_antidependency(&${antidependency.name});" } } private fun setDeadline(reaction: Reaction): String { val delay = reaction.deadline.delay val value = if (delay is ParameterReference) "__lf_inner.${delay.parameter.name}" else delay.toCppTime() - return "${reaction.name}.set_deadline($value, [this]() { ${reaction.name}_deadline_handler(); });" + return "${reaction.codeName}.set_deadline($value, [this]() { ${reaction.codeName}_deadline_handler(); });" } private fun assembleReaction(reaction: Reaction): String { val sources = reaction.sources.filter { it.variable is Port } return with(PrependOperator) { """ - |// ${reaction.name} + |// ${reaction.codeName} ${" |"..reaction.triggers.joinToString(separator = "\n") { declareTrigger(reaction, it) }} ${" |"..sources.joinToString(separator = "\n") { declareDependency(reaction, it) }} ${" |"..reaction.effects.joinToString(separator = "\n") { declareAntidependency(reaction, it) }} diff --git a/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt b/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt index f2386bf96a..2944cd3b67 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt @@ -45,7 +45,7 @@ import org.lflang.lf.WidthSpec */ /** Get the "name" a reaction is represented with in target code.*/ -val Reaction.name +val Reaction.codeName get(): String = "r$indexInContainer" /* ********************************************************************************************** diff --git a/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt index c1e66f3632..de34478723 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt @@ -67,8 +67,8 @@ class CppReactionGenerator( private fun Reaction.getAllReferencedVariablesForContainer(container: Instantiation) = allVariableReferences.filter { it.container == container }.distinct() - private fun Reaction.getViewClassName(container: Instantiation) = "__lf_view_of_${name}_on_${container.name}_t" - private fun Reaction.getViewInstanceName(container: Instantiation) = "__lf_view_of_${name}_on_${container.name}" + private fun Reaction.getViewClassName(container: Instantiation) = "__lf_view_of_${codeName}_on_${container.name}_t" + private fun Reaction.getViewInstanceName(container: Instantiation) = "__lf_view_of_${codeName}_on_${container.name}" private val VarRef.cppType get() = @@ -102,20 +102,20 @@ class CppReactionGenerator( allUncontainedSources.map { it.name } + allUncontainedEffects.map { it.name } + allReferencedContainers.map { getViewInstanceName(it) } - val body = "void ${name}_body() { __lf_inner.${name}_body(${parameters.joinToString(", ")}); }" + val body = "void ${codeName}_body() { __lf_inner.${codeName}_body(${parameters.joinToString(", ")}); }" val deadlineHandler = - "void ${name}_deadline_handler() { __lf_inner.${name}_deadline_handler(${parameters.joinToString(", ")}); }" + "void ${codeName}_deadline_handler() { __lf_inner.${codeName}_deadline_handler(${parameters.joinToString(", ")}); }" return if (deadline == null) """ $body - reactor::Reaction $name{"$label", $priority, this, [this]() { ${name}_body(); }}; + reactor::Reaction $codeName{"$label", $priority, this, [this]() { ${codeName}_body(); }}; """.trimIndent() else """ $body $deadlineHandler - reactor::Reaction $name{"$label", $priority, this, [this]() { ${name}_body(); }}; + reactor::Reaction $codeName{"$label", $priority, this, [this]() { ${codeName}_body(); }}; """.trimIndent() } } @@ -123,11 +123,11 @@ class CppReactionGenerator( private fun generateFunctionDeclaration(reaction: Reaction, postfix: String): String { val params = reaction.getBodyParameters() return when (params.size) { - 0 -> "void ${reaction.name}_$postfix();" - 1 -> "void ${reaction.name}_$postfix(${params[0]});" + 0 -> "void ${reaction.codeName}_$postfix();" + 1 -> "void ${reaction.codeName}_$postfix(${params[0]});" else -> with(PrependOperator) { """ - |void ${reaction.name}_$postfix( + |void ${reaction.codeName}_$postfix( ${" | "..params.joinToString(",\n")}); """.trimMargin() } @@ -137,11 +137,11 @@ class CppReactionGenerator( private fun getFunctionDefinitionSignature(reaction: Reaction, postfix: String): String { val params = reaction.getBodyParameters() return when (params.size) { - 0 -> "void ${reactor.templateName}::Inner::${reaction.name}_$postfix()" - 1 -> "void ${reactor.templateName}::Inner::${reaction.name}_$postfix(${params[0]})" + 0 -> "void ${reactor.templateName}::Inner::${reaction.codeName}_$postfix()" + 1 -> "void ${reactor.templateName}::Inner::${reaction.codeName}_$postfix(${params[0]})" else -> with(PrependOperator) { """ - |void ${reactor.templateName}::Inner::${reaction.name}_$postfix( + |void ${reactor.templateName}::Inner::${reaction.codeName}_$postfix( ${" | "..params.joinToString(",\n")}) """.trimMargin() } diff --git a/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java index f1a095403f..a2a149f522 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java @@ -1,13 +1,13 @@ package org.lflang.generator.python; import org.lflang.lf.Action; -import org.lflang.lf.ReactorDecl; +import org.lflang.lf.Reactor; import org.lflang.generator.c.CGenerator; public class PythonActionGenerator { - public static String generateAliasTypeDef(ReactorDecl decl, Action action, + public static String generateAliasTypeDef(Reactor r, Action action, String genericActionType) { - return "typedef "+genericActionType+" "+CGenerator.variableStructType(action, decl)+";"; + return "typedef "+genericActionType+" "+CGenerator.variableStructType(action, r, false)+";"; } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 442afd79f3..24e1a4f55b 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -34,6 +34,11 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +<<<<<<< HEAD +======= + +import org.eclipse.emf.ecore.EObject; +>>>>>>> origin import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; import org.lflang.ASTUtils; @@ -52,6 +57,7 @@ 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; @@ -246,6 +252,7 @@ public String generateDirectives() { code.pr( PythonPreambleGenerator.generateCDefineDirectives( targetConfig, fileConfig.getSrcGenPath(), hasModalReactors)); +<<<<<<< HEAD code.pr( PythonPreambleGenerator.generateCIncludeStatements( targetConfig, targetLanguageIsCpp(), hasModalReactors)); @@ -411,6 +418,34 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { } System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, lfModuleName)); +======= + 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); +>>>>>>> origin } if (errorReporter.getErrorsOccurred()) { @@ -448,6 +483,7 @@ protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactio reaction, decl, reactionIndex, mainDef, errorReporter, types)); } +<<<<<<< HEAD /** * 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 @@ -537,9 +573,30 @@ protected void generateSelfStructExtension( + ";"); } reactionIndex++; +======= + /** + * 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); + } +>>>>>>> origin } } +<<<<<<< HEAD @Override protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { // NOTE: Strangely, a newline is needed at the beginning or indentation @@ -556,20 +613,246 @@ protected void setUpGeneralParameters() { super.setUpGeneralParameters(); if (hasModalReactors) { targetConfig.compileAdditionalSources.add("lib/modal_models/impl.c"); +======= + 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)); +>>>>>>> origin } } +<<<<<<< HEAD @Override protected void additionalPostProcessingForModes() { if (!hasModalReactors) { return; +======= + private void generateAuxiliaryStructsForAction(CodeBuilder builder, Reactor r, + Action action) { + builder.pr(action, PythonActionGenerator.generateAliasTypeDef(r, action, genericActionType)); +>>>>>>> origin } PythonModeGenerator.generateResetReactionsIfNeeded(reactors); } +<<<<<<< HEAD private static String setUpMainTarget( boolean hasMain, String executableName, Stream cSources) { return (""" +======= + /** + * 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; + } + 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); + } + } + + @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; + } + 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 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); + } + + private static String setUpMainTarget(boolean hasMain, String executableName, Stream cSources) { + return ( + """ +>>>>>>> origin set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_compile_definitions(_LF_GARBAGE_COLLECTED) add_subdirectory(core) diff --git a/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java index 2c7512b499..9e0919b52c 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java @@ -5,6 +5,7 @@ import org.lflang.lf.Output; import org.lflang.lf.Port; import org.lflang.lf.Action; +import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; import java.util.List; @@ -194,8 +195,8 @@ public static String generatePythonListForContainedBank(String reactorName, Port ); } - public static String generateAliasTypeDef(ReactorDecl decl, Port port, boolean isTokenType, String genericPortType) { - return "typedef "+genericPortType+" "+CGenerator.variableStructType(port, decl)+";"; + public static String generateAliasTypeDef(Reactor r, Port port, boolean isTokenType, String genericPortType) { + return "typedef "+genericPortType+" "+CGenerator.variableStructType(port, r, false)+";"; } private static String generateConvertCPortToPy(String port) { diff --git a/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java index 5fa8aff84f..e2fd7af86a 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java @@ -55,7 +55,7 @@ public static String generateCIncludeStatements( code.pr(CPreambleGenerator.generateIncludeStatements(targetConfig, CCppMode)); code.pr("#include \"pythontarget.h\""); if (hasModalReactors) { - code.pr("#include \"modal_models/definitions.h\""); + code.pr("#include \"include/modal_models/definitions.h\""); } return code.toString(); } diff --git a/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java index 77f7352f08..bcc1beeaed 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java @@ -34,49 +34,46 @@ public class PythonReactionGenerator { /** - * Generate code to call reaction numbered "reactionIndex" in reactor "decl". - * @param decl The reactor containing the reaction + * Generate code to call reaction numbered "reactionIndex" in reactor "reactor". + * @param reactor The reactor containing the reaction * @param reactionIndex The index of the reaction - * @param pyObjectDescriptor CPython related descriptors for each object in "pyObjects". * @param pyObjects CPython related objects */ - public static String generateCPythonReactionCaller(ReactorDecl decl, + public static String generateCPythonReactionCaller(Reactor reactor, int reactionIndex, List pyObjects, String inits) { String pythonFunctionName = generatePythonReactionFunctionName(reactionIndex); String cpythonFunctionName = generateCPythonReactionFunctionName(reactionIndex); - return generateCPythonFunctionCaller(decl.getName(), pythonFunctionName, cpythonFunctionName, pyObjects, inits); + return generateCPythonFunctionCaller(CUtil.getName(reactor), pythonFunctionName, cpythonFunctionName, pyObjects, inits); } /** - * Generate code to call deadline function numbered "reactionIndex" in reactor "decl". - * @param decl The reactor containing the reaction + * Generate code to call deadline function numbered "reactionIndex" in reactor "r". + * @param r The reactor containing the reaction * @param reactionIndex The index of the reaction - * @param pyObjectDescriptor CPython related descriptors for each object in "pyObjects". * @param pyObjects CPython related objects */ - public static String generateCPythonDeadlineCaller(ReactorDecl decl, + public static String generateCPythonDeadlineCaller(Reactor r, int reactionIndex, List pyObjects) { String pythonFunctionName = generatePythonDeadlineFunctionName(reactionIndex); String cpythonFunctionName = generateCPythonDeadlineFunctionName(reactionIndex); - return generateCPythonFunctionCaller(decl.getName(), pythonFunctionName, cpythonFunctionName, pyObjects, ""); + return generateCPythonFunctionCaller(CUtil.getName(r), pythonFunctionName, cpythonFunctionName, pyObjects, ""); } /** - * Generate code to call deadline function numbered "reactionIndex" in reactor "decl". - * @param decl The reactor containing the reaction + * Generate code to call deadline function numbered "reactionIndex" in reactor "r". + * @param r The reactor containing the reaction * @param reactionIndex The index of the reaction - * @param pyObjectDescriptor CPython related descriptors for each object in "pyObjects". * @param pyObjects CPython related objects */ - public static String generateCPythonSTPCaller(ReactorDecl decl, + public static String generateCPythonSTPCaller(Reactor r, int reactionIndex, List pyObjects) { String pythonFunctionName = generatePythonSTPFunctionName(reactionIndex); String cpythonFunctionName = generateCPythonSTPFunctionName(reactionIndex); - return generateCPythonFunctionCaller(decl.getName(), pythonFunctionName, cpythonFunctionName, pyObjects, ""); + return generateCPythonFunctionCaller(CUtil.getName(r), pythonFunctionName, cpythonFunctionName, pyObjects, ""); } /** @@ -122,7 +119,7 @@ private static String generateCPythonFunctionCaller(String reactorDeclName, * Generate the reaction in the .c file, which calls the Python reaction through the CPython interface. * * @param reaction The reaction to generate Python-specific initialization for. - * @param decl The reactor to which reaction belongs to. + * @param r The reactor to which reaction belongs to. * @param reactionIndex The index number of the reaction in decl. * @param mainDef The main reactor. * @param errorReporter An error reporter. @@ -130,7 +127,7 @@ private static String generateCPythonFunctionCaller(String reactorDeclName, */ public static String generateCReaction( Reaction reaction, - ReactorDecl decl, + Reactor r, int reactionIndex, Instantiation mainDef, ErrorReporter errorReporter, @@ -140,34 +137,34 @@ public static String generateCReaction( // Each input must be cast to (PyObject *) (aka their descriptors for Py_BuildValue are "O") List pyObjects = new ArrayList<>(); CodeBuilder code = new CodeBuilder(); - String cPyInit = generateCPythonInitializers(reaction, decl, pyObjects, errorReporter); + String cPyInit = generateCPythonInitializers(reaction, r, pyObjects, errorReporter); String cInit = CReactionGenerator.generateInitializationForReaction( - "", reaction, decl, reactionIndex, + "", reaction, r, reactionIndex, types, errorReporter, mainDef, Target.Python.requiresTypes); code.pr( "#include " + StringUtil.addDoubleQuotes( CCoreFilesUtils.getCTargetSetHeader())); code.pr(generateFunction( - CReactionGenerator.generateReactionFunctionHeader(decl, reactionIndex), + CReactionGenerator.generateReactionFunctionHeader(r, reactionIndex), cInit, reaction.getCode(), - generateCPythonReactionCaller(decl, reactionIndex, pyObjects, cPyInit) + generateCPythonReactionCaller(r, reactionIndex, pyObjects, cPyInit) )); // Generate code for the STP violation handler, if there is one. if (reaction.getStp() != null) { code.pr(generateFunction( - CReactionGenerator.generateStpFunctionHeader(decl, reactionIndex), + CReactionGenerator.generateStpFunctionHeader(r, reactionIndex), cInit, reaction.getStp().getCode(), - generateCPythonSTPCaller(decl, reactionIndex, pyObjects) + generateCPythonSTPCaller(r, reactionIndex, pyObjects) )); } // Generate code for the deadline violation function, if there is one. if (reaction.getDeadline() != null) { code.pr(generateFunction( - CReactionGenerator.generateDeadlineFunctionHeader(decl, reactionIndex), + CReactionGenerator.generateDeadlineFunctionHeader(r, reactionIndex), cInit, reaction.getDeadline().getCode(), - generateCPythonDeadlineCaller(decl, reactionIndex, pyObjects) + generateCPythonDeadlineCaller(r, reactionIndex, pyObjects) )); } code.pr( diff --git a/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java index 5778791f76..fd954ca956 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java @@ -34,7 +34,7 @@ public static String generatePythonClass(ReactorInstance instance, CodeBuilder pythonClasses = new CodeBuilder(); ReactorDecl decl = instance.getDefinition().getReactorClass(); Reactor reactor = ASTUtils.toDefinition(decl); - String className = PyUtil.getName(decl); + String className = PyUtil.getName(reactor); if (instantiatedClasses == null) { return ""; } @@ -113,7 +113,7 @@ public static String generatePythonClassInstantiations(ReactorInstance instance, ReactorInstance main) { CodeBuilder code = new CodeBuilder(); - String className = PyUtil.getName(instance.reactorDeclaration); + String className = PyUtil.getName(instance.reactorDefinition); if (instance.getWidth() > 0) { // For each reactor instance, create a list regardless of whether it is a bank or not. diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index 61fa3d8816..d4d34c7b08 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -455,15 +455,20 @@ public static boolean isCFile(Path path) { * @param dir The folder to search for includes to change. * @throws IOException If the given set of files cannot be relativized. */ - public static void relativeIncludeHelper(Path dir) throws IOException { + public static void relativeIncludeHelper(Path dir, Path includePath) throws IOException { System.out.println("Relativizing all includes in " + dir.toString()); - List allPaths = Files.walk(dir) + List includePaths = Files.walk(includePath) + .filter(Files::isRegularFile) + .filter(FileUtil::isCFile) + .sorted(Comparator.reverseOrder()) + .collect(Collectors.toList()); + List srcPaths = Files.walk(dir) .filter(Files::isRegularFile) .filter(FileUtil::isCFile) .sorted(Comparator.reverseOrder()) .collect(Collectors.toList()); Map fileStringToFilePath = new HashMap(); - for (Path path : allPaths) { + for (Path path : includePaths) { String fileName = path.getFileName().toString(); if (path.getFileName().toString().contains("CMakeLists.txt")) continue; if (fileStringToFilePath.put(fileName, path) != null) { @@ -471,7 +476,7 @@ public static void relativeIncludeHelper(Path dir) throws IOException { } } Pattern regexExpression = Pattern.compile("#include\s+[\"]([^\"]+)*[\"]"); - for (Path path : allPaths) { + for (Path path : srcPaths) { String fileName = path.getFileName().toString(); String fileContents = Files.readString(path); Matcher matcher = regexExpression.matcher(fileContents); diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 2d0d4f332b..928466da91 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -196,6 +196,7 @@ public void checkBracedExpression(BracedListExpression expr) { @Check(CheckType.FAST) public void checkAssignment(Assignment assignment) { +<<<<<<< HEAD // If the left-hand side is a time parameter, make sure the assignment has units typeCheck( assignment.getRhs(), @@ -209,6 +210,171 @@ public void checkAssignment(Assignment assignment) { + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", Literals.ASSIGNMENT__RHS); +======= + // 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 + ); + } + } + + 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); + } + } + } + } + + // 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); + } + } + } + } + } + + // 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); + } + } +>>>>>>> origin } } @@ -221,6 +387,348 @@ public void checkConnection(Connection connection) { for (VarRef rp : connection.getRightPorts()) { boolean leftInCycle = false; +<<<<<<< HEAD +======= + @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; + } + + // FIXME: report error if resource cannot be resolved. + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (!isUnused(reactor)) { + return; + } + } + 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(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); + } + } + + // 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 + ); + } + + // 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 + ); + } + + // 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 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); + } + } + + // 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 checkParameter(Parameter param) { + checkName(param.getName(), Literals.PARAMETER__NAME); + + 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 (this.target.requiresTypes) { + // Report missing target type. param.inferredType.undefine + if (ASTUtils.getInferredType(param).isUndefined()) { + error("Type declaration missing.", Literals.PARAMETER__TYPE); + } + } + + if (param.getType() != null) { + typeCheck(param.getInit(), ASTUtils.getInferredType(param), Literals.PARAMETER__INIT); + } + + 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 (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 (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 + ); + } + } + + @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); + } + } + } + + // // Report error if this reaction is part of a cycle. + Set> cycles = this.info.topologyCycles(); + Reactor reactor = ASTUtils.getEnclosingReactor(reaction); + boolean reactionInCycle = false; +>>>>>>> origin for (NamedInstance it : cycles) { if ((lp.getContainer() == null && it.getDefinition().equals(lp.getVariable())) || (it.getDefinition().equals(lp.getVariable()) @@ -927,12 +1435,33 @@ public void checkReactor(Reactor reactor) throws IOException { } } +<<<<<<< HEAD // 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); +======= + /** + * 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 + ); + } +>>>>>>> origin } if (reactor.getHost() != null) { @@ -1248,6 +1777,7 @@ public void checkAttributes(Attribute attr) { spec.check(this, attr); } +<<<<<<< HEAD @Check(CheckType.FAST) public void checkWidthSpec(WidthSpec widthSpec) { if (!this.target.supportsMultiports()) { @@ -1272,6 +1802,75 @@ public void checkWidthSpec(WidthSpec widthSpec) { } } else if (term.getWidth() < 0) { error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS); +======= + /** + * 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 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); + } +>>>>>>> origin } } } @@ -1666,6 +2265,7 @@ private void checkName(String name, EStructuralFeature feature) { error(RESERVED_MESSAGE + name, feature); } +<<<<<<< HEAD if (this.target == Target.TS) { // "actions" is a reserved word within a TS reaction if (name.equals("actions")) { @@ -1681,6 +2281,79 @@ private void checkName(String name, EStructuralFeature feature) { public void typeCheck(Initializer init, InferredType type, EStructuralFeature feature) { if (init == null) { return; +======= + @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)); + } + } + } + } + // 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)); + } + } + } + } + } +>>>>>>> origin } // TODO: @@ -1713,6 +2386,7 @@ private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) return; } +<<<<<<< HEAD if (init.getExprs().size() != 1) { error("Expected exactly one time value.", feature); } else { @@ -1723,6 +2397,58 @@ private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) private void checkExpressionIsTime(Expression value, EStructuralFeature feature) { if (value == null || value instanceof Time) { return; +======= + @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)); + } + } + } + } +>>>>>>> origin } if (value instanceof ParameterReference) { @@ -1953,5 +2679,223 @@ private boolean sameType(Type type1, Type type2) { "Names of objects (inputs, outputs, actions, timers, parameters, " + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; +<<<<<<< HEAD private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; +======= + if (ASTUtils.isInteger(((Literal) value).getLiteral())) { + error("Missing time unit.", feature); + return; + } + // fallthrough + } + + 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++; + } + } + 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; + } + + /** + * 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()); + } + + 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}\\$)$"; + +>>>>>>> origin } diff --git a/test/C/.gitignore b/test/C/.gitignore new file mode 100644 index 0000000000..08f514ebc5 --- /dev/null +++ b/test/C/.gitignore @@ -0,0 +1 @@ +include/ diff --git a/test/C/c/bank_multiport_to_reaction_no_inlining.c b/test/C/c/bank_multiport_to_reaction_no_inlining.c new file mode 100644 index 0000000000..ae54c84c2d --- /dev/null +++ b/test/C/c/bank_multiport_to_reaction_no_inlining.c @@ -0,0 +1,16 @@ +#include "../include/BankMultiportToReactionNoInlining/BankMultiportToReactionNoInlining.h" + +void check(bankmultiporttoreactionnoinlining_self_t* self, doublecount_out_t*** out) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + if (out[i][j]->is_present) { + lf_print("Received %d.", out[i][j]->value); + if (self->count != out[i][j]->value) { + lf_print_error_and_exit("Expected %d.", self->count); + } + self->received = true; + } + } + } + self->count++; +} diff --git a/test/C/c/bank_multiport_to_reaction_no_inlining.cmake b/test/C/c/bank_multiport_to_reaction_no_inlining.cmake new file mode 100644 index 0000000000..c433f89d7e --- /dev/null +++ b/test/C/c/bank_multiport_to_reaction_no_inlining.cmake @@ -0,0 +1 @@ +target_sources(${LF_MAIN_TARGET} PRIVATE c/bank_multiport_to_reaction_no_inlining.c) diff --git a/test/C/c/bank_to_reaction_no_inlining.c b/test/C/c/bank_to_reaction_no_inlining.c new file mode 100644 index 0000000000..9cf5b220b9 --- /dev/null +++ b/test/C/c/bank_to_reaction_no_inlining.c @@ -0,0 +1,11 @@ +#include "../include/BankToReactionNoInlining/BankToReactionNoInlining.h" + +void check(banktoreactionnoinlining_self_t* self, count_out_t** out) { + for (int i = 0; i < 2; i++) { + lf_print("Received %d.", out[i]->value); + if (self->count != out[i]->value) { + lf_print_error_and_exit("Expected %d but got %d.", self->count, out[i]->value); + } + } + self->count++; +} diff --git a/test/C/c/bank_to_reaction_no_inlining.cmake b/test/C/c/bank_to_reaction_no_inlining.cmake new file mode 100644 index 0000000000..a4f8d8dfa3 --- /dev/null +++ b/test/C/c/bank_to_reaction_no_inlining.cmake @@ -0,0 +1 @@ +target_sources(${LF_MAIN_TARGET} PRIVATE c/bank_to_reaction_no_inlining.c) diff --git a/test/C/c/count.c b/test/C/c/count.c new file mode 100644 index 0000000000..2bc5378b7d --- /dev/null +++ b/test/C/c/count.c @@ -0,0 +1,15 @@ +#include +#include "../include/Count/Count.h" + +void increment(count_self_t* self) { + printf("in increment, count=%d\n", self->count); + self->count++; +} + +void check_done(count_self_t* self) { + printf("in done, count=%d\n", self->count); + if (self->count > 10) { + printf("%s", "requesting stop\n"); + lf_request_stop(); + } +} diff --git a/test/C/c/count.cmake b/test/C/c/count.cmake new file mode 100644 index 0000000000..a9b364a871 --- /dev/null +++ b/test/C/c/count.cmake @@ -0,0 +1 @@ +target_sources(${LF_MAIN_TARGET} PRIVATE c/count.c) diff --git a/test/C/c/count_hierarchy.c b/test/C/c/count_hierarchy.c new file mode 100644 index 0000000000..999159aab8 --- /dev/null +++ b/test/C/c/count_hierarchy.c @@ -0,0 +1,15 @@ +#include +#include "../include/CountHierarchy/CountHierarchy.h" + +void increment(counthierarchy_self_t* self, timer_out_t* out) { + printf("in increment, count=%d\n", self->count); + self->count++; +} + +void check_done(counthierarchy_self_t* self, timer_out_t* out) { + printf("in done, count=%d\n", self->count); + if (self->count > 10) { + printf("%s", "requesting stop\n"); + lf_request_stop(); + } +} diff --git a/test/C/c/count_hierarchy.cmake b/test/C/c/count_hierarchy.cmake new file mode 100644 index 0000000000..955c07270a --- /dev/null +++ b/test/C/c/count_hierarchy.cmake @@ -0,0 +1 @@ +target_sources(${LF_MAIN_TARGET} PRIVATE c/count_hierarchy.c) diff --git a/test/C/c/multiport_to_reaction_no_inlining.c b/test/C/c/multiport_to_reaction_no_inlining.c new file mode 100644 index 0000000000..5036771cbd --- /dev/null +++ b/test/C/c/multiport_to_reaction_no_inlining.c @@ -0,0 +1,14 @@ +#include "../include/MultiportToReactionNoInlining/MultiportToReactionNoInlining.h" + +void check(multiporttoreactionnoinlining_self_t* self, source_out_t** out) { + int sum = 0; + for (int i = 0; i < 4; i++) { + if (out[i]->is_present) sum += out[i]->value; + } + printf("Sum of received: %d.\n", sum); + if (sum != self->s) { + printf("ERROR: Expected %d.\n", self->s); + exit(1); + } + self->s += 16; +} diff --git a/test/C/c/multiport_to_reaction_no_inlining.cmake b/test/C/c/multiport_to_reaction_no_inlining.cmake new file mode 100644 index 0000000000..8f4226aff2 --- /dev/null +++ b/test/C/c/multiport_to_reaction_no_inlining.cmake @@ -0,0 +1 @@ +target_sources(${LF_MAIN_TARGET} PRIVATE c/multiport_to_reaction_no_inlining.c) diff --git a/test/C/c/sendreceive.c b/test/C/c/sendreceive.c new file mode 100644 index 0000000000..95a08176e7 --- /dev/null +++ b/test/C/c/sendreceive.c @@ -0,0 +1,17 @@ +#include + +#include "../include/IntPrint/Print.h" +#include "../include/IntPrint/Check.h" +#include "../include/api/set.h" + +void send(print_self_t* self, print_out_t* out) { + lf_set(out, 42); +} + +void receive(check_self_t* self, check_in_t* in) { + printf("Received: %d\n", in->value); + if (in->value != self->expected) { + printf("ERROR: Expected value to be %d.\n", self->expected); + exit(1); + } +} diff --git a/test/C/c/sendreceive.cmake b/test/C/c/sendreceive.cmake new file mode 100644 index 0000000000..acd63f229c --- /dev/null +++ b/test/C/c/sendreceive.cmake @@ -0,0 +1 @@ +target_sources(${LF_MAIN_TARGET} PRIVATE c/sendreceive.c) diff --git a/test/C/src/Deadline.lf b/test/C/src/Deadline.lf index c72dc16dcf..21ea7df141 100644 --- a/test/C/src/Deadline.lf +++ b/test/C/src/Deadline.lf @@ -5,6 +5,16 @@ target C { timeout: 6 sec } +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + reactor Source(period: time = 3 sec) { output y: int timer t(0, period) @@ -14,7 +24,7 @@ reactor Source(period: time = 3 sec) { if (2 * (self->count / 2) != self->count) { // The count variable is odd. // Take time to cause a deadline violation. - lf_nanosleep(MSEC(1500)); + lf_sleep(MSEC(1500)); } printf("Source sends: %d.\n", self->count); lf_set(y, self->count); diff --git a/test/C/src/DeadlineHandledAbove.lf b/test/C/src/DeadlineHandledAbove.lf index 92d7e9dac8..3b0dc49198 100644 --- a/test/C/src/DeadlineHandledAbove.lf +++ b/test/C/src/DeadlineHandledAbove.lf @@ -2,6 +2,16 @@ // container reacts to that output. target C +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + reactor Deadline(threshold: time = 100 msec) { input x: int output deadline_violation: bool diff --git a/test/C/src/DeadlineInherited.lf b/test/C/src/DeadlineInherited.lf index eab2f56d9b..3d3c8ac96b 100644 --- a/test/C/src/DeadlineInherited.lf +++ b/test/C/src/DeadlineInherited.lf @@ -4,9 +4,10 @@ target C { threading: false } -preamble {= int global_cnt = 0; =} +preamble {= extern int global_cnt; =} reactor NoDeadline { + preamble {= int global_cnt = 0; =} timer t(0 msec, 100 msec) reaction(t) {= global_cnt++; =} diff --git a/test/C/src/DeadlinePriority.lf b/test/C/src/DeadlinePriority.lf index 723a29b754..2aa5c8136f 100644 --- a/test/C/src/DeadlinePriority.lf +++ b/test/C/src/DeadlinePriority.lf @@ -4,9 +4,10 @@ target C { threading: false } -preamble {= int global_cnt = 0; =} +preamble {= extern int global_cnt; =} reactor NoDeadline { + preamble {= int global_cnt = 0; =} timer t(0 msec, 100 msec) reaction(t) {= global_cnt++; =} diff --git a/test/C/src/DeadlineWithAfterDelay.lf b/test/C/src/DeadlineWithAfterDelay.lf index d1b87860ac..5e33e129e6 100644 --- a/test/C/src/DeadlineWithAfterDelay.lf +++ b/test/C/src/DeadlineWithAfterDelay.lf @@ -4,9 +4,11 @@ target C { threading: false } -preamble {= int global_cnt = 0; =} +preamble {= extern int global_cnt; =} reactor Source { + preamble {= int global_cnt = 0; =} + output out: int timer t(0 msec, 100 msec) diff --git a/test/C/src/DeadlineWithBanks.lf b/test/C/src/DeadlineWithBanks.lf index 18bc8c3682..7a9445ec8d 100644 --- a/test/C/src/DeadlineWithBanks.lf +++ b/test/C/src/DeadlineWithBanks.lf @@ -9,9 +9,11 @@ target C { build-type: Debug } -preamble {= volatile int global_cnt = 0; =} +preamble {= extern volatile int global_cnt; =} reactor Bank(bank_index: int = 0) { + preamble {= volatile int global_cnt = 0; =} + timer t(0, 100 msec) output out: int diff --git a/test/C/src/DelayString.lf b/test/C/src/DelayString.lf index b14e680d72..80b89020dd 100644 --- a/test/C/src/DelayString.lf +++ b/test/C/src/DelayString.lf @@ -2,6 +2,16 @@ // freed. target C +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include + #ifdef __cplusplus + } + #endif +=} + reactor DelayString2(delay: time = 100 msec) { input in: string output out: string diff --git a/test/C/src/DelayStruct.lf b/test/C/src/DelayStruct.lf index 9cd201aa11..0a92171d35 100644 --- a/test/C/src/DelayStruct.lf +++ b/test/C/src/DelayStruct.lf @@ -5,6 +5,7 @@ target C { preamble {= #include "hello.h" + #include =} reactor DelayPointer(delay: time = 100 msec) { diff --git a/test/C/src/Hello.lf b/test/C/src/Hello.lf index ea37265a23..7784e2f86f 100644 --- a/test/C/src/Hello.lf +++ b/test/C/src/Hello.lf @@ -8,6 +8,10 @@ target C { fast: true } +preamble {= + #include +=} + reactor Reschedule(period: time = 2 sec, message: string = "Hello C") { state count: int = 0 state previous_time: time = 0 diff --git a/test/C/src/ScheduleValue.lf b/test/C/src/ScheduleValue.lf index 65e36fbd06..8481d47d61 100644 --- a/test/C/src/ScheduleValue.lf +++ b/test/C/src/ScheduleValue.lf @@ -3,6 +3,17 @@ target C { timeout: 3 sec } +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include + #include + #ifdef __cplusplus + } + #endif +=} + main reactor ScheduleValue { logical action a: char* diff --git a/test/C/src/SimpleDeadline.lf b/test/C/src/SimpleDeadline.lf index 3e2d786eec..fba3e03cbc 100644 --- a/test/C/src/SimpleDeadline.lf +++ b/test/C/src/SimpleDeadline.lf @@ -3,6 +3,16 @@ // violation. target C +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + reactor Deadline(threshold: time = 100 msec) { input x: int output deadlineViolation: bool diff --git a/test/C/src/StructAsState.lf b/test/C/src/StructAsState.lf index 59ae495853..119762c14c 100644 --- a/test/C/src/StructAsState.lf +++ b/test/C/src/StructAsState.lf @@ -2,13 +2,14 @@ // value. target C +preamble {= + typedef struct hello_t { + char* name; + int value; + } hello_t; +=} + main reactor StructAsState { - preamble {= - typedef struct hello_t { - char* name; - int value; - } hello_t; - =} // Notice that target code delimiters are no longer necessary. state s: hello_t = {"Earth", 42} diff --git a/test/C/src/TestForPreviousOutput.lf b/test/C/src/TestForPreviousOutput.lf index fec3bc54da..583c6cbb74 100644 --- a/test/C/src/TestForPreviousOutput.lf +++ b/test/C/src/TestForPreviousOutput.lf @@ -2,6 +2,16 @@ // a given output. The output should always be 42. target C +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include + #ifdef __cplusplus + } + #endif +=} + reactor Source { output out: int diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index fdac093c49..16d6723f7f 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -1,21 +1,19 @@ /** - * 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. + * 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: 1100ms + timeout: 1100 ms } -reactor Watcher(timeout:time(150ms)) { - timer t(100ms, 100ms) // Offset ameliorates startup time. - output d: int // Produced if the watchdog triggers. - state alternating:bool(false) - state count:int(0) +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(); @@ -32,12 +30,12 @@ reactor Watcher(timeout:time(150ms)) { } self->alternating = !self->alternating; =} - + reaction(poodle) -> d {= lf_print("Reaction poodle was called."); lf_set(d, 1); =} - + reaction(shutdown) -> poodle {= // FIXME: There needs to be an lf_watchdog_stop() defined. _lf_watchdog_stop(poodle); @@ -49,14 +47,15 @@ reactor Watcher(timeout:time(150ms)) { main reactor { logical action a - state count:int(0) - + 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); diff --git a/test/C/src/concurrent/AsyncCallback.lf b/test/C/src/concurrent/AsyncCallback.lf index ec2beae766..58120446d4 100644 --- a/test/C/src/concurrent/AsyncCallback.lf +++ b/test/C/src/concurrent/AsyncCallback.lf @@ -9,9 +9,20 @@ */ target C { tracing: true, - timeout: 2 sec + timeout: 2 sec, + keepalive: true } +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + main reactor AsyncCallback { preamble {= void callback(void* a) { diff --git a/test/C/src/concurrent/AsyncCallbackDrop.lf b/test/C/src/concurrent/AsyncCallbackDrop.lf index f123bcdbdf..b0c5c502f2 100644 --- a/test/C/src/concurrent/AsyncCallbackDrop.lf +++ b/test/C/src/concurrent/AsyncCallbackDrop.lf @@ -7,6 +7,16 @@ target C { timeout: 2 sec } +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + main reactor { preamble {= void callback(void* a) { diff --git a/test/C/src/concurrent/AsyncCallbackReplace.lf b/test/C/src/concurrent/AsyncCallbackReplace.lf index b7afeb2363..7bac7496b5 100644 --- a/test/C/src/concurrent/AsyncCallbackReplace.lf +++ b/test/C/src/concurrent/AsyncCallbackReplace.lf @@ -7,6 +7,16 @@ target C { timeout: 2 sec } +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + main reactor { preamble {= void callback(void* a) { diff --git a/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf b/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf index 545206de91..ad14879080 100644 --- a/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf +++ b/test/C/src/concurrent/DeadlineHandledAboveThreaded.lf @@ -2,6 +2,16 @@ // container reacts to that output. target C +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + reactor Deadline(threshold: time = 100 msec) { input x: int output deadline_violation: bool @@ -20,7 +30,7 @@ main reactor { d = new Deadline(threshold = 10 msec) reaction(startup) -> d.x {= - lf_nanosleep(MSEC(200)); + lf_sleep(MSEC(200)); lf_set(d.x, 42); =} diff --git a/test/C/src/concurrent/DeadlineThreaded.lf b/test/C/src/concurrent/DeadlineThreaded.lf index ac765fe6f7..ffc1be2701 100644 --- a/test/C/src/concurrent/DeadlineThreaded.lf +++ b/test/C/src/concurrent/DeadlineThreaded.lf @@ -5,6 +5,16 @@ target C { timeout: 6 sec } +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "platform.h" + #ifdef __cplusplus + } + #endif +=} + reactor Source(period: time = 3000 msec) { output y: int timer t(0, period) @@ -14,7 +24,7 @@ reactor Source(period: time = 3000 msec) { if (2 * (self->count / 2) != self->count) { // The count variable is odd. // Take time to cause a deadline violation. - lf_nanosleep(MSEC(210)); + lf_sleep(MSEC(210)); } printf("Source sends: %d.\n", self->count); lf_set(y, self->count); diff --git a/test/C/src/concurrent/HelloThreaded.lf b/test/C/src/concurrent/HelloThreaded.lf index a8ea34dd05..285811a17d 100644 --- a/test/C/src/concurrent/HelloThreaded.lf +++ b/test/C/src/concurrent/HelloThreaded.lf @@ -8,6 +8,10 @@ target C { fast: true } +preamble {= + #include +=} + reactor Reschedule(period: time = 2 sec, message: string = "Hello C") { state count: int = 0 state previous_time: time = 0 diff --git a/test/C/src/concurrent/ScheduleAt.lf b/test/C/src/concurrent/ScheduleAt.lf index e68b3956e8..2ef6af7af6 100644 --- a/test/C/src/concurrent/ScheduleAt.lf +++ b/test/C/src/concurrent/ScheduleAt.lf @@ -10,6 +10,15 @@ target C { } reactor Scheduler { + preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "include/core/reactor_common.h" + #ifdef __cplusplus + } + #endif + =} logical action act // List of microsteps. Size = 16 state microstep_delay_list: uint32_t[] = { diff --git a/test/C/src/concurrent/Tracing.lf b/test/C/src/concurrent/Tracing.lf index 71da79c7ef..cbc4513732 100644 --- a/test/C/src/concurrent/Tracing.lf +++ b/test/C/src/concurrent/Tracing.lf @@ -7,6 +7,10 @@ target C { logging: DEBUG } +preamble {= + #include +=} + reactor Source { timer t(0, 200 msec) output out: int diff --git a/test/C/src/federated/DistributedNetworkOrder.lf b/test/C/src/federated/DistributedNetworkOrder.lf index e18ea4ac04..ef979a949d 100644 --- a/test/C/src/federated/DistributedNetworkOrder.lf +++ b/test/C/src/federated/DistributedNetworkOrder.lf @@ -13,6 +13,16 @@ target C { build-type: RelWithDebInfo // Release with debug info } +preamble {= + #ifdef __cplusplus + extern "C" { + #endif + #include "federate.h" + #ifdef __cplusplus + } + #endif +=} + reactor Sender { output out: int timer t(0, 1 msec) diff --git a/test/C/src/federated/DistributedPhysicalActionUpstream.lf b/test/C/src/federated/DistributedPhysicalActionUpstream.lf index 47280a24b8..520142b466 100644 --- a/test/C/src/federated/DistributedPhysicalActionUpstream.lf +++ b/test/C/src/federated/DistributedPhysicalActionUpstream.lf @@ -13,22 +13,28 @@ import PassThrough from "../lib/PassThrough.lf" import TestCount from "../lib/TestCount.lf" preamble {= - int _counter = 1; - void callback(void *a) { - lf_schedule_int(a, 0, _counter++); - } - // Simulate time passing before a callback occurs. - void* take_time(void* a) { - while (_counter < 15) { - instant_t sleep_time = MSEC(10); - lf_sleep(sleep_time); - callback(a); - } - return NULL; - } + extern int _counter; + void callback(void *a); + void* take_time(void* a); =} reactor WithPhysicalAction { + preamble {= + int _counter = 1; + void callback(void *a) { + lf_schedule_int(a, 0, _counter++); + } + // Simulate time passing before a callback occurs. + void* take_time(void* a) { + while (_counter < 15) { + instant_t sleep_time = MSEC(10); + lf_sleep(sleep_time); + callback(a); + } + return NULL; + } + =} + output out: int state thread_id: lf_thread_t = 0 physical action act(0): int diff --git a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf index 66bbb964e1..5cee1171cc 100644 --- a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf +++ b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf @@ -13,22 +13,27 @@ import PassThrough from "../lib/PassThrough.lf" import TestCount from "../lib/TestCount.lf" preamble {= - int _counter = 1; - void callback(void *a) { - lf_schedule_int(a, 0, _counter++); - } - // Simulate time passing before a callback occurs. - void* take_time(void* a) { - while (_counter < 20) { - instant_t sleep_time = USEC(50); - lf_sleep(sleep_time); - callback(a); - } - return NULL; - } + extern int _counter; + void callback(void *a); + void* take_time(void* a); =} reactor WithPhysicalAction { + preamble {= + int _counter = 1; + void callback(void *a) { + lf_schedule_int(a, 0, _counter++); + } + // Simulate time passing before a callback occurs. + void* take_time(void* a) { + while (_counter < 20) { + instant_t sleep_time = USEC(50); + lf_sleep(sleep_time); + callback(a); + } + return NULL; + } + =} output out: int state thread_id: lf_thread_t = 0 physical action act(0): int diff --git a/test/C/src/federated/HelloDistributed.lf b/test/C/src/federated/HelloDistributed.lf index 6841bf91bc..909ce53b43 100644 --- a/test/C/src/federated/HelloDistributed.lf +++ b/test/C/src/federated/HelloDistributed.lf @@ -7,6 +7,10 @@ */ target C +preamble {= + #include +=} + reactor Source { output out: string diff --git a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf index 44ce6e1681..b4501cd555 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf @@ -15,19 +15,23 @@ target C { preamble {= #include // Defines sleep() - bool stop = false; - // Thread to trigger an action once every second. - void* ping(void* actionref) { - while(!stop) { - lf_print("Scheduling action."); - lf_schedule(actionref, 0); - sleep(1); - } - return NULL; - } + extern bool stop; + void* ping(void* actionref); =} reactor Looper(incr: int = 1, delay: time = 0 msec) { + preamble {= + bool stop = false; + // Thread to trigger an action once every second. + void* ping(void* actionref) { + while(!stop) { + lf_print("Scheduling action."); + lf_schedule(actionref, 0); + sleep(1); + } + return NULL; + } + =} input in: int output out: int physical action a(delay) diff --git a/test/C/src/federated/LoopDistributedDecentralized.lf b/test/C/src/federated/LoopDistributedDecentralized.lf index ae64e6e71a..4350c984b3 100644 --- a/test/C/src/federated/LoopDistributedDecentralized.lf +++ b/test/C/src/federated/LoopDistributedDecentralized.lf @@ -11,19 +11,23 @@ target C { preamble {= #include // Defines sleep() - bool stop = false; - // Thread to trigger an action once every second. - void* ping(void* actionref) { - while(!stop) { - lf_print("Scheduling action."); - lf_schedule(actionref, 0); - sleep(1); - } - return NULL; - } + extern bool stop; + void* ping(void* actionref); =} reactor Looper(incr: int = 1, delay: time = 0 msec, stp_offset: time = 0) { + preamble {= + bool stop = false; + // Thread to trigger an action once every second. + void* ping(void* actionref) { + while(!stop) { + lf_print("Scheduling action."); + lf_schedule(actionref, 0); + sleep(1); + } + return NULL; + } + =} input in: int output out: int physical action a(stp_offset) diff --git a/test/C/src/federated/LoopDistributedDouble.lf b/test/C/src/federated/LoopDistributedDouble.lf index 31b7eb0f6e..28723650d7 100644 --- a/test/C/src/federated/LoopDistributedDouble.lf +++ b/test/C/src/federated/LoopDistributedDouble.lf @@ -15,19 +15,23 @@ target C { preamble {= #include // Defines sleep() - bool stop = false; - // Thread to trigger an action once every second. - void* ping(void* actionref) { - while(!stop) { - lf_print("Scheduling action."); - lf_schedule(actionref, 0); - sleep(1); - } - return NULL; - } + extern bool stop; + void* ping(void* actionref); =} reactor Looper(incr: int = 1, delay: time = 0 msec) { + preamble {= + bool stop = false; + // Thread to trigger an action once every second. + void* ping(void* actionref) { + while(!stop) { + lf_print("Scheduling action."); + lf_schedule(actionref, 0); + sleep(1); + } + return NULL; + } + =} input in: int input in2: int output out: int diff --git a/test/C/src/include/hello.h b/test/C/src/include/hello.h index 6ee45b62a7..7b290fba89 100644 --- a/test/C/src/include/hello.h +++ b/test/C/src/include/hello.h @@ -18,14 +18,14 @@ typedef struct hello_t { typedef int* int_pointer; -hello_t* hello_constructor(char* name, int value) { +static hello_t* hello_constructor(char* name, int value) { hello_t* val = (hello_t*) malloc(sizeof(hello_t)); val->name = name; val->value = value; return val; } -hello_t* hello_copy_constructor(hello_t v) { +static hello_t* hello_copy_constructor(hello_t v) { hello_t* val = (hello_t*) malloc(sizeof(hello_t)); val->name = v.name; val->value = v.value; diff --git a/test/C/src/modal_models/BanksCount3ModesComplex.lf b/test/C/src/modal_models/BanksCount3ModesComplex.lf index ad08cc7325..47805f1009 100644 --- a/test/C/src/modal_models/BanksCount3ModesComplex.lf +++ b/test/C/src/modal_models/BanksCount3ModesComplex.lf @@ -55,7 +55,7 @@ main reactor { test = new TraceTesting( // keep-format events_size = 16, trace_size = 429, - trace = ( + trace = { 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 250000000,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 250000000,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -69,7 +69,7 @@ main reactor { 250000000,1,2,1,2,1,2,1,2,0,2,0,2,0,2,0,2,1,2,1,2,1,2,1,2,0,0,0,0,0,0,0,0, 250000000,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,0,2,0,2,0,2,0,2,0,0,0,0,0,0,0,0, 250000000,1,1,1,1,1,1,1,1,0,3,0,3,0,3,0,3,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0 - ), training = false) + }, training = false) counters.always, counters.mode1, counters.mode2, counters.never -> test.events diff --git a/test/C/src/modal_models/BanksCount3ModesSimple.lf b/test/C/src/modal_models/BanksCount3ModesSimple.lf index c03fb7e562..40c2ba50a8 100644 --- a/test/C/src/modal_models/BanksCount3ModesSimple.lf +++ b/test/C/src/modal_models/BanksCount3ModesSimple.lf @@ -13,7 +13,7 @@ main reactor { test = new TraceTesting( events_size = 3, trace_size = 63, - trace = ( // keep-format + trace = { // keep-format 0,1,1,1,1,1,1, 250000000,1,2,1,2,1,2, 250000000,1,3,1,3,1,3, @@ -23,7 +23,7 @@ main reactor { 250000000,1,1,1,1,1,1, 250000000,1,2,1,2,1,2, 250000000,1,3,1,3,1,3 - ), + }, training = false ) diff --git a/test/C/src/modal_models/BanksModalStateReset.lf b/test/C/src/modal_models/BanksModalStateReset.lf index 1dde9c5b97..8e075715c8 100644 --- a/test/C/src/modal_models/BanksModalStateReset.lf +++ b/test/C/src/modal_models/BanksModalStateReset.lf @@ -16,7 +16,7 @@ main reactor { reset1 = new[2] ResetReaction() reset2 = new[2] AutoReset() - test = new TraceTesting(events_size = 16, trace_size = 627, trace = ( // keep-format + test = new TraceTesting(events_size = 16, trace_size = 627, trace = { // keep-format 0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, 250000000,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0, 0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0, 250000000,0,0,0,0,0,0,0,0,1,2,1,2,0,0,0,0, 0,0,0,0,0,0,0,0,1,2,1,2,0,0,0,0, @@ -36,7 +36,7 @@ main reactor { 250000000,0,1,0,1,0,2,0,2,0,8,0,8,1,0,1,0, 0,1,0,1,0,2,0,2,0,8,0,8,1,0,1,0, 250000000,0,1,0,1,0,2,0,2,0,8,0,8,1,1,1,1, 0,1,0,1,0,2,0,2,0,8,0,8,1,1,1,1, 250000000,1,1,1,1,1,3,1,3,0,8,0,8,1,2,1,2, 1,1,1,1,1,3,1,3,0,8,0,8,1,2,1,2 - ), training = false) + }, training = false) reset1.mode_switch, reset1.count0, diff --git a/test/C/src/modal_models/ConvertCaseTest.lf b/test/C/src/modal_models/ConvertCaseTest.lf index 5475edaa35..3f644eb133 100644 --- a/test/C/src/modal_models/ConvertCaseTest.lf +++ b/test/C/src/modal_models/ConvertCaseTest.lf @@ -50,10 +50,6 @@ reactor Converter { input raw: char output converted: int - preamble {= - #include - =} - initial mode Upper { reaction(raw) -> converted, reset(Lower) {= char c = raw->value; @@ -114,7 +110,7 @@ main reactor { test = new TraceTesting( events_size = 2, trace_size = 60, - trace = ( // keep-format + trace = { // keep-format 0,1,72,1,72, 250000000,1,69,1,69, 250000000,1,76,1,76, @@ -127,7 +123,7 @@ main reactor { 250000000,1,76,1,108, 250000000,1,68,1,100, 250000000,1,95,1,95 - ), + }, training = false ) diff --git a/test/C/src/modal_models/ModalActions.lf b/test/C/src/modal_models/ModalActions.lf index 1d8dff8cb4..e687d4bafd 100644 --- a/test/C/src/modal_models/ModalActions.lf +++ b/test/C/src/modal_models/ModalActions.lf @@ -68,7 +68,7 @@ main reactor { test = new TraceTesting( events_size = 5, trace_size = 165, - trace = ( // keep-format + trace = { // keep-format 0,0,0,1,1,0,0,0,0,0,0, 500000000,0,0,0,1,1,1,0,0,0,0, 250000000,0,0,1,1,0,1,0,0,0,0, @@ -84,7 +84,7 @@ main reactor { 500000000,0,1,0,1,0,1,0,1,1,1, 250000000,0,1,0,1,0,1,1,1,0,1, 250000000,1,1,0,1,0,1,0,1,0,1 - ), + }, training = false ) diff --git a/test/C/src/modal_models/ModalAfter.lf b/test/C/src/modal_models/ModalAfter.lf index 270fd88d05..f5cc8d229e 100644 --- a/test/C/src/modal_models/ModalAfter.lf +++ b/test/C/src/modal_models/ModalAfter.lf @@ -73,7 +73,7 @@ main reactor { test = new TraceTesting( events_size = 5, trace_size = 165, - trace = ( // keep-format + trace = { // keep-format 0,0,0,1,1,0,0,0,0,0,0, 500000000,0,0,0,1,1,1,0,0,0,0, 250000000,0,0,1,1,0,1,0,0,0,0, @@ -89,7 +89,7 @@ main reactor { 500000000,0,1,0,1,0,1,0,1,1,1, 250000000,0,1,0,1,0,1,1,1,0,1, 250000000,1,1,0,1,0,1,0,1,0,1 - ), + }, training = false ) diff --git a/test/C/src/modal_models/ModalCycleBreaker.lf b/test/C/src/modal_models/ModalCycleBreaker.lf index cee4fabfec..aae727b23d 100644 --- a/test/C/src/modal_models/ModalCycleBreaker.lf +++ b/test/C/src/modal_models/ModalCycleBreaker.lf @@ -57,7 +57,7 @@ main reactor { test = new TraceTesting( events_size = 1, trace_size = 27, - trace = ( // keep-format + trace = { // keep-format 0,1,0, 100000000,1,1, 100000000,1,2, @@ -67,7 +67,7 @@ main reactor { 100000000,1,7, 100000000,1,8, 100000000,1,9 - ), + }, training = false ) diff --git a/test/C/src/modal_models/ModalStartupShutdown.lf b/test/C/src/modal_models/ModalStartupShutdown.lf index fa1ebaacdf..d39117882a 100644 --- a/test/C/src/modal_models/ModalStartupShutdown.lf +++ b/test/C/src/modal_models/ModalStartupShutdown.lf @@ -113,7 +113,7 @@ main reactor { test = new TraceTesting( events_size = 11, trace_size = 253, - trace = ( // keep-format + trace = { // keep-format 0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 500000000,1,2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,2,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -125,7 +125,7 @@ main reactor { 500000000,1,4,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0, 0,0,4,0,1,0,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, 500000000,1,4,0,1,0,1,1,1,1,1,0,1,0,1,1,1,0,0,0,0,0,0 - ), + }, training = false ) diff --git a/test/C/src/modal_models/ModalStateReset.lf b/test/C/src/modal_models/ModalStateReset.lf index 61563162b6..c098f088d5 100644 --- a/test/C/src/modal_models/ModalStateReset.lf +++ b/test/C/src/modal_models/ModalStateReset.lf @@ -63,7 +63,7 @@ main reactor { test = new TraceTesting( events_size = 4, trace_size = 171, - trace = ( // keep-format + trace = { // keep-format 0,0,0,0,0,1,0,0,0, 250000000,0,0,0,0,1,1,0,0, 250000000,0,0,0,0,1,2,0,0, @@ -83,7 +83,7 @@ main reactor { 250000000,0,1,0,2,0,8,1,0, 250000000,0,1,0,2,0,8,1,1, 250000000,1,1,1,3,0,8,1,2 - ), + }, training = false ) diff --git a/test/C/src/modal_models/ModalStateResetAuto.lf b/test/C/src/modal_models/ModalStateResetAuto.lf index a2b6b105f0..a82fd18212 100644 --- a/test/C/src/modal_models/ModalStateResetAuto.lf +++ b/test/C/src/modal_models/ModalStateResetAuto.lf @@ -59,7 +59,7 @@ main reactor { test = new TraceTesting( events_size = 4, trace_size = 171, - trace = ( // keep-format + trace = { // keep-format 0,0,0,0,0,1,0,0,0, 250000000,0,0,0,0,1,1,0,0, 250000000,0,0,0,0,1,2,0,0, @@ -79,7 +79,7 @@ main reactor { 250000000,0,1,0,2,0,8,1,0, 250000000,0,1,0,2,0,8,1,1, 250000000,1,1,1,3,0,8,1,2 - ), + }, training = false ) diff --git a/test/C/src/modal_models/ModalTimers.lf b/test/C/src/modal_models/ModalTimers.lf index 47fcf5f9e4..954a677de4 100644 --- a/test/C/src/modal_models/ModalTimers.lf +++ b/test/C/src/modal_models/ModalTimers.lf @@ -49,7 +49,7 @@ main reactor { test = new TraceTesting( events_size = 3, trace_size = 77, - trace = ( // keep-format + trace = { // keep-format 0,0,0,1,1,0,0, 750000000,0,0,1,1,0,0, 250000000,1,1,0,1,0,0, @@ -61,7 +61,7 @@ main reactor { 0,0,1,0,1,1,1, 750000000,0,1,0,1,1,1, 250000000,1,1,0,1,0,1 - ), + }, training = false ) diff --git a/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf b/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf index 60fa76dd1e..31de4fe6bd 100644 --- a/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf +++ b/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf @@ -44,7 +44,7 @@ main reactor { test = new TraceTesting( events_size = 1, trace_size = 51, - trace = ( // keep-format + trace = { // keep-format 0,1,0, 250000000,1,1, 250000000,1,2, @@ -62,7 +62,7 @@ main reactor { 100000000,1,3, 100000000,1,4, 100000000,1,5 - ), + }, training = false ) diff --git a/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf b/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf index dff82c602f..f89073dfc1 100644 --- a/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf +++ b/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf @@ -47,7 +47,7 @@ main reactor { test = new TraceTesting( events_size = 1, trace_size = 51, - trace = ( // keep-format + trace = { // keep-format 0,1,0, 250000000,1,1, 250000000,1,2, @@ -65,7 +65,7 @@ main reactor { 100000000,1,30, 100000000,1,40, 100000000,1,50 - ), + }, training = false ) diff --git a/test/C/src/modal_models/util/TraceTesting.lf b/test/C/src/modal_models/util/TraceTesting.lf index 247b3f1def..c63de35f2a 100644 --- a/test/C/src/modal_models/util/TraceTesting.lf +++ b/test/C/src/modal_models/util/TraceTesting.lf @@ -1,10 +1,6 @@ /** Utility reactor to record and test execution traces. */ target C -preamble {= - #include -=} - reactor TraceTesting( events_size: int = 0, trace_size: int = 0, diff --git a/test/C/src/multiport/BankIndexInitializer.lf b/test/C/src/multiport/BankIndexInitializer.lf index d8a2c5766e..e6c52e8efe 100644 --- a/test/C/src/multiport/BankIndexInitializer.lf +++ b/test/C/src/multiport/BankIndexInitializer.lf @@ -1,9 +1,11 @@ // Test bank of reactors to multiport input with id parameter in the bank. target C -preamble {= int table[] = {4, 3, 2, 1}; =} +preamble {= extern int table[4]; =} reactor Source(bank_index: int = 0, value: int = 0) { + preamble {= int table[] = {4, 3, 2, 1}; =} + output out: int reaction(startup) -> out {= lf_set(out, self->value); =} diff --git a/test/C/src/no_inlining/BankMultiportToReactionNoInlining.lf b/test/C/src/no_inlining/BankMultiportToReactionNoInlining.lf new file mode 100644 index 0000000000..e69e0af0b4 --- /dev/null +++ b/test/C/src/no_inlining/BankMultiportToReactionNoInlining.lf @@ -0,0 +1,30 @@ +target C { + timeout: 5 sec, + fast: true, + cmake-include: ["../../c/bank_multiport_to_reaction_no_inlining.cmake"], + files: ["../../c"] +} + +import Count from "../lib/Count.lf" + +reactor DoubleCount { + output[2] out: int + c1 = new Count() + c2 = new Count() + c1.out, c2.out -> out +} + +main reactor { + state count: int = 1 + state received: bool = false + + s = new[2] DoubleCount() + + reaction(s.out) named check + + reaction(shutdown) {= + if (!self->received) { + lf_print_error_and_exit("No inputs present."); + } + =} +} diff --git a/test/C/src/no_inlining/BankToReactionNoInlining.lf b/test/C/src/no_inlining/BankToReactionNoInlining.lf new file mode 100644 index 0000000000..4956bf68cd --- /dev/null +++ b/test/C/src/no_inlining/BankToReactionNoInlining.lf @@ -0,0 +1,16 @@ +target C { + timeout: 5 sec, + fast: true, + cmake-include: ["../../c/bank_to_reaction_no_inlining.cmake"], + files: ["../../c"] +} + +import Count from "../lib/Count.lf" + +main reactor { + state count: int = 1 + + s = new[2] Count() + + reaction(s.out) named check +} diff --git a/test/C/src/no_inlining/Count.lf b/test/C/src/no_inlining/Count.lf new file mode 100644 index 0000000000..f8fd51bec7 --- /dev/null +++ b/test/C/src/no_inlining/Count.lf @@ -0,0 +1,16 @@ +target C { + cmake-include: ["../../c/count.cmake"], + files: ["../../c"] +} + +main reactor Count { + timer t(0, 1 msec) + + state count: int + + reaction(t) named increment + + reaction(t) named check_done + + reaction(shutdown) {= printf("%s", "shutting down\n"); =} +} diff --git a/test/C/src/no_inlining/CountHierarchy.lf b/test/C/src/no_inlining/CountHierarchy.lf new file mode 100644 index 0000000000..771222af2f --- /dev/null +++ b/test/C/src/no_inlining/CountHierarchy.lf @@ -0,0 +1,23 @@ +target C { + cmake-include: ["../../c/count_hierarchy.cmake"], + files: ["../../c"] +} + +reactor Timer(m: time = 0, n: time = 0) { + output out: int + timer t(m, n) + + reaction(t) -> out {= lf_set(out, 0); =} +} + +main reactor { + t = new Timer(m = 0, n = 1 msec) + + state count: int + + reaction(t.out) named increment + + reaction(t.out) named check_done + + reaction(shutdown) {= printf("%s", "shutting down\n"); =} +} diff --git a/test/C/src/no_inlining/IntPrint.lf b/test/C/src/no_inlining/IntPrint.lf new file mode 100644 index 0000000000..fa6ab8c757 --- /dev/null +++ b/test/C/src/no_inlining/IntPrint.lf @@ -0,0 +1,22 @@ +target C { + cmake-include: ["../../c/sendreceive.cmake"], + files: ["../../c"] +} + +reactor Print { + output out: int + + reaction(startup) -> out named send +} + +reactor Check(expected: int = 42) { // expected parameter is for testing. + input in: int + + reaction(in) named receive +} + +main reactor { + s = new Print() + p = new Check() + s.out -> p.in +} diff --git a/test/C/src/no_inlining/MultiportToReactionNoInlining.lf b/test/C/src/no_inlining/MultiportToReactionNoInlining.lf new file mode 100644 index 0000000000..3c5f18d62a --- /dev/null +++ b/test/C/src/no_inlining/MultiportToReactionNoInlining.lf @@ -0,0 +1,35 @@ +// Check reaction to multiport output of a contained reactor. +target C { + timeout: 2 sec, + fast: true, + cmake-include: ["../../c/multiport_to_reaction_no_inlining.cmake"], + files: ["../../c"] +} + +reactor Source(width: int = 1) { + timer t(0, 200 msec) + state s: int = 0 + output[width] out: int + + reaction(t) -> out {= + printf("Sending.\n"); + for(int i = 0; i < out_width; i++) { + lf_set(out[i], self->s++); + } + =} +} + +main reactor { + state s: int = 6 + b = new Source(width = 4) + + reaction(b.out) named check + + reaction(shutdown) {= + if (self->s <= 6) { + fprintf(stderr, "ERROR: Destination received no input!\n"); + exit(1); + } + printf("Success.\n"); + =} +} diff --git a/test/C/src/token/include/array.h b/test/C/src/token/include/array.h index c1fd1b4991..5ea8d5230b 100644 --- a/test/C/src/token/include/array.h +++ b/test/C/src/token/include/array.h @@ -21,7 +21,7 @@ typedef struct int_array_t { * @param length The length. * @return A pointer to the array struct. */ -int_array_t* int_array_constructor(size_t length) { +static inline int_array_t* int_array_constructor(size_t length) { int_array_t* result = (int_array_t*) malloc(sizeof(int_array_t)); result->data = (int*) calloc(length, sizeof(int)); result->length = length; @@ -36,7 +36,7 @@ int_array_t* int_array_constructor(size_t length) { * @param array The array to copy. * @return void* */ -void* int_array_copy_constructor(void* array) { +static inline void* int_array_copy_constructor(void* array) { int_array_t* source = (int_array_t*) array; int_array_t* copy = (int_array_t*) malloc(sizeof(int_array_t)); copy->data = (int*) calloc(source->length, sizeof(int)); @@ -54,7 +54,7 @@ void* int_array_copy_constructor(void* array) { * when their reference count decrements to zero. * @param array The array to free. */ -void int_array_destructor(void* array) { +static inline void int_array_destructor(void* array) { free(((int_array_t*) array)->data); free(array); } diff --git a/test/Python/.gitignore b/test/Python/.gitignore new file mode 100644 index 0000000000..08f514ebc5 --- /dev/null +++ b/test/Python/.gitignore @@ -0,0 +1 @@ +include/ diff --git a/util/tracing/.gitignore b/util/tracing/.gitignore new file mode 100644 index 0000000000..5fe4f5283d --- /dev/null +++ b/util/tracing/.gitignore @@ -0,0 +1,2 @@ +*.o +trace_to_csv diff --git a/util/tracing/trace_to_chrome.c b/util/tracing/trace_to_chrome.c index 92eeee71dd..5cf979227a 100644 --- a/util/tracing/trace_to_chrome.c +++ b/util/tracing/trace_to_chrome.c @@ -31,6 +31,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * point your chrome browser to chrome://tracing/ and the load the .json file. */ #define LF_TRACE +#include #include "reactor.h" #include "trace.h" #include "trace_util.h" @@ -92,7 +93,7 @@ size_t read_and_write_trace(FILE* trace_file, FILE* output_file) { if (reactor_name == NULL) { if (trace[i].event_type == worker_wait_starts || trace[i].event_type == worker_wait_ends) { reactor_name = "WAIT"; - } else if (trace[i].event_type == scheduler_advancing_time_starts + } else if (trace[i].event_type == scheduler_advancing_time_starts || trace[i].event_type == scheduler_advancing_time_starts) { reactor_name = "ADVANCE TIME"; } else { @@ -194,7 +195,7 @@ size_t read_and_write_trace(FILE* trace_file, FILE* output_file) { phase = "E"; break; default: - fprintf(stderr, "WARNING: Unrecognized event type %d: %s\n", + fprintf(stderr, "WARNING: Unrecognized event type %d: %s\n", trace[i].event_type, trace_event_names[trace[i].event_type]); pid = PID_FOR_UNKNOWN_EVENT; phase = "i"; @@ -275,7 +276,7 @@ void write_metadata_events(FILE* output_file) { "\"pid\": 0, " "\"tid\": 0, " "\"args\": {" - "\"name\": \"Main thread\"" + "\"name\": \"Main thread\"" "}},\n" ); @@ -328,7 +329,7 @@ void write_metadata_events(FILE* output_file) { ); } } - + // Write the reactor names for the logical timelines. for (int i = 0; i < object_table_size; i++) { if (object_table[i].type == trace_trigger) { @@ -341,10 +342,10 @@ void write_metadata_events(FILE* output_file) { "\"pid\": %d, " // the "process" to identify by reactor. "\"tid\": %d," // The "thread" to label with action or timer name. "\"args\": {" - "\"name\": \"Trigger %s\"" + "\"name\": \"Trigger %s\"" "}},\n", reactor_index + 1, // Offset of 1 prevents collision with Execution. - i, + i, object_table[i].description); } else if (object_table[i].type == trace_reactor) { fprintf(output_file, "{" @@ -352,7 +353,7 @@ void write_metadata_events(FILE* output_file) { "\"ph\": \"M\", " // mark as metadata. "\"pid\": %d, " // the "process" to label as reactor. "\"args\": {" - "\"name\": \"Reactor %s reactions, actions, and timers in logical time\"" + "\"name\": \"Reactor %s reactions, actions, and timers in logical time\"" "}},\n", i + 1, // Offset of 1 prevents collision with Execution. object_table[i].description); @@ -363,7 +364,7 @@ void write_metadata_events(FILE* output_file) { "\"pid\": %d, " // the "process" to label as reactor. "\"tid\": %d," // The "thread" to label with action or timer name. "\"args\": {" - "\"name\": \"%s\"" + "\"name\": \"%s\"" "}},\n", PID_FOR_USER_EVENT, i, // This is the index in the object table. @@ -376,7 +377,7 @@ void write_metadata_events(FILE* output_file) { "\"ph\": \"M\", " // mark as metadata. "\"pid\": 0, " // the "process" to label "Execution". "\"args\": {" - "\"name\": \"Execution of %s\"" + "\"name\": \"Execution of %s\"" "}},\n", top_level); // Name the "process" for "Worker Waiting" if the PID is not the main execution one. @@ -386,7 +387,7 @@ void write_metadata_events(FILE* output_file) { "\"ph\": \"M\", " // mark as metadata. "\"pid\": %d, " // the "process" to label "Workers waiting for reaction queue". "\"args\": {" - "\"name\": \"Workers waiting for reaction queue\"" + "\"name\": \"Workers waiting for reaction queue\"" "}},\n", PID_FOR_WORKER_WAIT); } @@ -397,7 +398,7 @@ void write_metadata_events(FILE* output_file) { "\"ph\": \"M\", " // mark as metadata. "\"pid\": %d, " // the "process" to label "Workers waiting for reaction queue". "\"args\": {" - "\"name\": \"Workers advancing time\"" + "\"name\": \"Workers advancing time\"" "}},\n", PID_FOR_WORKER_ADVANCING_TIME); } @@ -408,7 +409,7 @@ void write_metadata_events(FILE* output_file) { "\"ph\": \"M\", " // mark as metadata. "\"pid\": %d, " // the "process" to label "User events". "\"args\": {" - "\"name\": \"User events in %s, shown in physical time:\"" + "\"name\": \"User events in %s, shown in physical time:\"" "}}\n", PID_FOR_USER_EVENT, top_level); } diff --git a/util/tracing/trace_to_csv.c b/util/tracing/trace_to_csv.c index 707cc5f43b..1abf08c082 100644 --- a/util/tracing/trace_to_csv.c +++ b/util/tracing/trace_to_csv.c @@ -30,6 +30,8 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * text file. */ #define LF_TRACE +#include +#include #include "reactor.h" #include "trace.h" #include "trace_util.h" @@ -84,7 +86,7 @@ typedef struct summary_stats_t { reaction_stats_t reactions[MAX_NUM_REACTIONS]; } summary_stats_t; -/** +/** * Sumary stats array. This array has the same size as the * object table. Pointer in the array will be void if there * are no stats for the object table item. @@ -135,7 +137,7 @@ size_t read_and_write_trace() { if (trigger_instance >= 0 && summary_stats[NUM_EVENT_TYPES + trigger_instance] == NULL) { summary_stats[NUM_EVENT_TYPES + trigger_instance] = (summary_stats_t*)calloc(1, sizeof(summary_stats_t)); } - + summary_stats_t* stats = NULL; interval_t exec_time; reaction_stats_t* rstats; @@ -198,7 +200,7 @@ size_t read_and_write_trace() { // commandeer the first entry in the reactions array to track values. stats = summary_stats[NUM_EVENT_TYPES + object_instance]; stats->description = reactor_name; - rstats = &stats->reactions[0]; + rstats = &stats->reactions[0]; rstats->occurrences++; // User values are stored in the "extra_delay" field, which is an interval_t. interval_t value = trace[i].extra_delay; @@ -332,7 +334,7 @@ void write_summary_file() { first = true; for (int i = NUM_EVENT_TYPES; i < table_size; i++) { summary_stats_t* stats = summary_stats[i]; - if (stats != NULL + if (stats != NULL && (stats->event_type == user_event || stats->event_type == user_value) && stats->occurrences > 0) { if (first) { diff --git a/util/tracing/trace_to_influxdb.c b/util/tracing/trace_to_influxdb.c index a99ae003ec..aa50911e5e 100644 --- a/util/tracing/trace_to_influxdb.c +++ b/util/tracing/trace_to_influxdb.c @@ -27,13 +27,13 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @section DESCRIPTION - * + * * Standalone program to send a Lingua Franca trace file to InfluxDB. * InfluxDB is a database server to which data can be posted using HTTP * or sent as a UDP datagram. - * + * * ## Compiling this Program - * + * * To compile this program, simply do this in this source directory: * ``` * make install @@ -41,13 +41,13 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * This will place an executable program `trace_to_influxdb` in the directory `lingua-franca/bin`. * I find it convenient to have this directory in my `PATH` (this is also where the * `lfc` command-line Lingua Franca compiler is located). - * + * * ## Setting up InfluxDB - * + * * To set up InfluxDB, see: - * + * * [https://docs.influxdata.com/influxdb/v2.0/get-started/](https://docs.influxdata.com/influxdb/v2.0/get-started/) - * + * * If you have previously installed InfluxDB and you want a fresh start, do this: * ```shell * rm -rf ~/.influxdbv2/ @@ -58,13 +58,13 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * kill -9 PID * ``` * where 'PID' is replaced with whatever process ID(s) are reported by the `ps` command above. - * + * * To start an InfluxDB server on localhost with port 8087: * ```shell * influxd --http-bind-address :8087 --reporting-disabled * ``` * The 'reporting-disabled' option simply disables notifications to the InfluxDB mother ship. - * + * * You then need to set up at least one user, organization, and bucket. You can do this by pointing your browser to * ``` * http://localhost:8087 @@ -77,9 +77,9 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ``` * The UI in the browser will then give you the options Quick Start or Advanced, either of which you can select. * If you select "Data" on the left, you can browse Buckets to verify that your test bucket was created. - * + * * ## Uploading Trace Data to InfluxDB - * + * * First, generate a trace file by setting a target parameter in a Lingua Franca program: * ``` * target C { @@ -87,7 +87,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * }; * ``` * Then, when you run this program, a binary file with extension `.lft` will be created. - * + * * In your browser, in the InfluxDB UI, select Data on the left, then select the Tokens tab. * Select a token and copy the token string to clipboard. It will looks something like this: * ``` @@ -100,15 +100,16 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ``` * where 'Filename' and the token are replaced with your values. * This will upload the trace data to InfluxDB. - * + * * You can also specify the following command-line options: * * -h, --host: The host name running InfluxDB. If not given, this defaults to "localhost". * * -p, --port: The port for accessing InfluxDB. This defaults to 8086. If you used 8087, as shown above, then you have to give this option. - * + * * The data can then be viewed in the InfluxDB browser, or you can configure an external * tool such as Grafana to visualize it (see https://grafana.com/docs/grafana/latest/datasources/influxdb/). */ #define LF_TRACE +#include #include "reactor.h" #include "trace.h" #include "trace_util.h" @@ -208,7 +209,7 @@ int main(int argc, char* argv[]) { influx_v2_client.token = NULL; influx_v2_client.host = "localhost"; influx_v2_client.port = 8086; - influx_v2_client.org = "iCyPhy"; + influx_v2_client.org = "iCyPhy"; influx_v2_client.bucket = "test"; char* filename = NULL; @@ -277,4 +278,4 @@ int main(int argc, char* argv[]) { printf("***** %zu records written to InfluxDB.\n", num_records); // File closing is handled by termination function. } -} \ No newline at end of file +} diff --git a/util/tracing/trace_util.c b/util/tracing/trace_util.c index 0f97b3bdb9..551ec94473 100644 --- a/util/tracing/trace_util.c +++ b/util/tracing/trace_util.c @@ -30,6 +30,9 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * text file. */ #define LF_TRACE +#include +#include +#include #include "reactor.h" #include "trace.h" #include "trace_util.h" @@ -187,12 +190,12 @@ void print_table() { } else { type = "unknown type"; } - printf("pointer = %p, trigger = %p, type = %s: %s\n", + printf("pointer = %p, trigger = %p, type = %s: %s\n", object_table[i].pointer, object_table[i].trigger, type, object_table[i].description); - } + } printf("-------\n"); } From b95830e7ae3b0894a54dfe0aeeba62cbc2b0db95 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 20 Apr 2023 11:08:57 -0700 Subject: [PATCH 067/108] issues fixed w watchdog test, seems to be working as expected, not sure why though --- org.lflang/src/org/lflang/ASTUtils.java | 3091 +++++++++-------- .../org/lflang/generator/c/CGenerator.java | 10 +- .../generator/c/CReactionGenerator.java | 2 +- .../generator/c/CWatchdogGenerator.java | 3 +- test/C/src/Watchdog.lf | 4 +- 5 files changed, 1605 insertions(+), 1505 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 996ce1509f..9c3003007e 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,9 +21,6 @@ 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; @@ -35,6 +28,7 @@ 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; @@ -42,6 +36,7 @@ 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; @@ -62,6 +57,8 @@ 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; @@ -95,1640 +92,1740 @@ 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(), - 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()); - } + /** + * 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); + } + } + } + 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); + } + } - /** - * 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); + // 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)); + } + } + } } - } - } - 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 transform; - } + /** + * 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 + ); + } - /** - * 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; - } + /** + * 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; + } - /** 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); - } + /** + * 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; + } - /** - * 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; - } + /** + * 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); + } - /** 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; - } + /** + * 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 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); - } + /** + * 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; + } - /** - * 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; - } + /** + * 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; + } - /** - * 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; - } + //////////////////////////////// + //// 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()); + } - /** - * 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; - } + /** + * 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()); + } - //////////////////////////////// - //// 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 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()); + } - /** - * 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()); - } + /** 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 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()); - } + /** + * 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()); + } - /** - * 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 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 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 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 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 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 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, 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()); + } - /** - * 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<>()); - } + /** + * 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 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)); + /** + * 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)); + // 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); + } + } - 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; } - 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 + } - /** - * 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. + 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); + } } - } while (idx > 0); } - } + list.addAll(idx, elements); } - 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 - - // public static Code toCode(String text) { - // Code code = null; - // if (text == null) return code; - // code.setBody(text); - // return code; - // } - - /** - * 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); - } + public static Iterable allElementsOfClass( + Resource resource, + Class elementClass + ) { + //noinspection StaticPseudoFunctionalStyleMethod + return Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), elementClass); + } - /** - * 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); - } + //////////////////////////////// + //// 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); + } - /** - * 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()); - } + /** + * 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 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())); - } + /** + * 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()); + } - /** 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 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())); } - 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"); - } + /** + * 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())); + } - /** - * 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 ""; - } + /** + * 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 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; - } + /** + * 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 ""; + } - /** - * 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; - } + /** + * 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; + } - // 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; - } + /** + * 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; + } - /** - * 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; - } + // 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); + } - /** Given a single string, convert it into its AST representation. */ - public static Element toElement(String str) { - return toElement(str, true); - } + return e; + } - /** - * 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; - } + /** + * 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; - /** - * 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); - } + /** + * Given a single string, convert it into its AST representation. + */ + public static Element toElement(String str) { + return toElement(str, true); + } - public static Element toElement(int val) { - return toElement(Integer.toString(val), false); - } + /** + * 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; + } - /** - * 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"; + /** + * 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; } - 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; - } - } catch (NumberFormatException e) { - // Not an int. + public static Element toElement(boolean val) { + return toElement(Boolean.toString(val), false); } - 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; - } + public static Element toElement(int val) { + return toElement(Integer.toString(val), 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; - } + /** + * 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()); + + for (String s : convertToEmptyListIfNull(type.getStars())) { + result.append(s); + } + return result.toString(); + } + } + } + return ""; + } - /** - * 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 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; + } + } catch (NumberFormatException e) { + // Not an int. + } + return 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 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 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 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 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 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 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 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 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); - } + /** + * 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)); + } - /** If the initializer contains exactly one expression, return it. Otherwise, return null. */ - public static Expression asSingleExpr(Initializer init) { - if (init == null) { - return null; + /** + * 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; } - 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; + /** + * 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; } - var exprs = init.getExprs(); - return exprs.size() == 1; - } - public static boolean isListInitializer(Initializer init) { - return init != null && !isSingleExpr(init); - } + /** + * 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); + } - /** - * 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; + /** + * If the initializer contains exactly one expression, + * return it. Otherwise, return null. + */ + public static Expression asSingleExpr(Initializer init) { + if (init == null) { + return null; } - if (!ASTUtils.isZero(e)) { - foundNonZero = true; - } - } + var exprs = init.getExprs(); + return exprs.size() == 1 ? exprs.get(0) : null; + } - if (allValidTime && foundNonZero) { - // Conservatively, no bounds are inferred; the returned type - // is a variable-size list. - return InferredType.timeList(); - } + public static boolean isSingleExpr(Initializer init) { + // todo expand that to = initialization + if (init == null) { + return false; + } + var exprs = init.getExprs(); + return exprs.size() == 1; } - 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()); - } + public static boolean isListInitializer(Initializer init) { + return init != null && !isSingleExpr(init); + } - /** - * 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()); - } + /** + * 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(); + } - /** - * 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); - } + 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; + } + } - /** - * 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 (allValidTime && foundNonZero) { + // Conservatively, no bounds are inferred; the returned type + // is a variable-size list. + return InferredType.timeList(); + } + } + return InferredType.undefined(); + } - /** - * 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; - } + /** + * 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()); + } - /** - * 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; - } + /** + * 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()); + } - //////////////////////////////// - //// 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(); - } + /** + * 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); + } - /** 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; + /** + * 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 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); - } + + + /** + * 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 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 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; + } - /** 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; - } + //////////////////////////////// + //// 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(); + } - /** - * 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; + /** + * 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 (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() - + "."); + } + + /** + * 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); } - result.addAll( - initialValue( - ((ParameterReference) expr).getParameter(), - instantiations.subList(1, instantiations.size()))); - } else { - result.add(expr); - } } - return result; - } + return null; } - // 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 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 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 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; } - 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; + /** + * 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; + } } - } 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; - } + // 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; } - 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); + /** + * 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; + } - 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."); + /** + * 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; } - // 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; + } 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; + } } - 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; + } + 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); } - 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; + + int portWidth = width(((Port) reference.getVariable()).getWidthSpec(), extended); + if (portWidth < 0) { + // Could not determine port width. + return -1; } - // Check that portWidth divides the discrepancy. - if (discrepancy % portWidth != 0) { - // This is an error. - 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; + } + } } - bankWidth = discrepancy / portWidth; - } else { - // Could not determine the bank width. - return -1; - } + return portWidth * bankWidth; } - } - return portWidth * bankWidth; + // Argument is not a port. + return -1; } - // 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; - } + /** + * 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 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); - } + /** + * 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; - } + /** + * 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; - } + /** + * 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 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 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 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 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; - }; - } + /** + * 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); + /** + * 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; - } + /** + * 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(""); + } - /** - * 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)); - } + } else { + inst.setName(reactor.getName()); + } + return inst; + } - /** - * 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)); - } + /** + * 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)); + } - ///////////////////////////////////////////////////////// - //// Private methods + /** + * 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)); + } - /** 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<>(); - } + ///////////////////////////////////////////////////////// + //// Private methods - /** - * 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; - } + /** + * 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<>(); + } - /** - * 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); + /** + * 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); } - } - 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); + 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; + } } - } - if (leftOrRight < 0) { - return rightWidth - leftWidth; - } else if (leftOrRight > 0) { - return leftWidth - rightWidth; - } - } - // A connection was not found with the instantiation. - 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); - } -} + public static void addReactionAttribute(Reaction reaction, String name) { + var fedAttr = factory.createAttribute(); + fedAttr.setAttrName(name); + reaction.getAttributes().add(fedAttr); + } +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index bfd89bdc14..e344e9f873 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1110,7 +1110,7 @@ private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeB generateAuxiliaryStructs(header, reactor, false); generateSelfStruct(header, reactor, constructorCode); generateMethods(src, reactor); - generateWatchdogs(reactor); + generateWatchdogs(src, reactor); generateReactions(src, reactor); generateConstructor(src, header, reactor, constructorCode); } @@ -1469,10 +1469,10 @@ protected void generateReaction(CodeBuilder src, Reaction reaction, Reactor r, i * federated or not the main reactor and reactions should be * unconditionally generated. */ - public void generateWatchdogs(ReactorDecl decl) { + public void generateWatchdogs(CodeBuilder src, ReactorDecl decl) { var reactor = ASTUtils.toDefinition(decl); for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - generateWatchdog(watchdog, decl); + generateWatchdog(src, watchdog, decl); } } @@ -1483,8 +1483,8 @@ public void generateWatchdogs(ReactorDecl decl) { * @param watchdog The watchdog. * @param decl The reactor. */ - protected void generateWatchdog(Watchdog watchdog, ReactorDecl decl) { - code.pr(CWatchdogGenerator.generateWatchdog( + protected void generateWatchdog(CodeBuilder src, Watchdog watchdog, ReactorDecl decl) { + src.pr(CWatchdogGenerator.generateWatchdog( watchdog, decl )); diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index f2f9630bea..67a76eb0e2 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -1191,7 +1191,7 @@ public static String generateStpFunctionHeader(Reactor r, return generateFunctionHeader(functionName); } - private static String generateFunctionHeader(String functionName) { + public static String generateFunctionHeader(String functionName) { return "void " + functionName + "(void* instance_args)"; } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 6ad7f405eb..621077de86 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -1,6 +1,7 @@ package org.lflang.generator.c; import java.util.List; + import org.lflang.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Mode; @@ -34,7 +35,7 @@ public static String generateInitializationForWatchdog(Watchdog watchdog, Reacto CodeBuilder code = new CodeBuilder(); // Define the "self" struct. - String structType = CUtil.selfType(decl); + 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) { diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index 16d6723f7f..5d2c87b724 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -6,7 +6,8 @@ * @author Edward A. Lee */ target C { - timeout: 1100 ms + timeout: 1100 ms, + fast: false } reactor Watcher(timeout: time = 150 ms) { @@ -18,6 +19,7 @@ reactor Watcher(timeout: time = 150 ms) { 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++; =} From 3b8e0f7007f0e69c5238df6ee97ad7f57fe910ce Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Thu, 20 Apr 2023 14:02:08 -0700 Subject: [PATCH 068/108] removed fake python test --- org.lflang/src/org/lflang/ASTUtils.java | 3103 +++++++------- org.lflang/src/org/lflang/ast/IsEqual.java | 28 +- .../org/lflang/generator/ReactorInstance.java | 831 ++-- .../org/lflang/generator/c/CGenerator.java | 3815 ++++++++--------- .../generator/c/CReactionGenerator.java | 2346 +++++----- .../generator/c/CWatchdogGenerator.java | 1 - .../generator/python/PythonGenerator.java | 1083 +++-- .../org/lflang/validation/LFValidator.java | 3291 +++++++------- test/Python/src/PyWatchdog.lf | 66 - 9 files changed, 7309 insertions(+), 7255 deletions(-) delete mode 100644 test/Python/src/PyWatchdog.lf diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 9c3003007e..4606b4d35f 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -21,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; @@ -28,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; @@ -36,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; @@ -57,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; @@ -92,1740 +91,1662 @@ 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(), - 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); - } - } - } - 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); - } - } + /** 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()); + } - // 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)); - } - } - } + /** + * 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); + } } - 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(); + // 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 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 - ); - } + return transform; + } - /** - * 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; - } + /** + * 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; + } - /** - * 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 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); + } - /** - * 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); - } + /** + * 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; + } - /** - * 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; - } + /** 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 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; - } + /** 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); + } - /** - * 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; - } + /** + * 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; + } - //////////////////////////////// - //// 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()); - } + /** + * 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; + } - /** - * 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()); - } + /** + * 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; + } - /** - * 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()); - } + //////////////////////////////// + //// 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()); + } - /** 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 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 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()); - } + /** + * 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()); + } - public static Stream allNestedClasses(Reactor definition) { - return new HashSet<>(ASTUtils.allInstantiations(definition)).stream() - .map(Instantiation::getReactorClass) - .map(ASTUtils::toDefinition); - } + /** 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 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 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()); + } - /** - * 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()); - } + 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 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 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 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 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 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 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 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, 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, 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()); - } + /** + * 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()); + } - /** 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; - } + /** + * 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()); + } - /** - * 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<>()); - } + /** + * 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()); + } - /** - * 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); + /** + * 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; + } - /** - * 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 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<>()); + } - // Add elements of the current reactor. - result.addAll((EList) definition.eGet(feature)); + /** + * 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); + } - 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); - } + /** + * 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 result; + // 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); + } } - /** - * 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 - } + return result; + } - 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); - } + /** + * 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); } - list.addAll(idx, elements); + } } + list.addAll(idx, elements); + } - public static Iterable allElementsOfClass( - Resource resource, - Class elementClass - ) { - //noinspection StaticPseudoFunctionalStyleMethod - return Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), elementClass); - } + 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); - } + //////////////////////////////// + //// 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); - } + /** + * 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 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())); - } + /** + * 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())); + /** 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"); - } + /** + * 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 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; - } + /** + * 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; - } + /** + * 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); - } + // 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; + } - 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. - * {@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; + } - /** - * Given a single string, convert it into its AST representation. - */ - public static Element toElement(String str) { - return toElement(str, true); - } + /** + * 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); + } - /** - * 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)); + 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 { - var arr = LfFactory.eINSTANCE.createArray(); - for (String s : list) { - arr.getElements().add(ASTUtils.toElement(s)); - } - e.setArray(arr); - } - return e; - } + StringBuilder result = new StringBuilder(type.getId()); - /** - * 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()); + for (String s : convertToEmptyListIfNull(type.getStars())) { + result.append(s); + } + return result.toString(); } - return e; + } } + return ""; + } - public static Element toElement(boolean val) { - return toElement(Boolean.toString(val), false); + /** + * 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; + } + } catch (NumberFormatException e) { + // Not an int. } + return false; + } - public static Element toElement(int val) { - return toElement(Integer.toString(val), 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; + } - /** - * 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()); - - for (String s : convertToEmptyListIfNull(type.getStars())) { - result.append(s); - } - return result.toString(); - } - } - } - return ""; - } + /** + * 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 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; - } - } catch (NumberFormatException e) { - // Not an int. - } - return false; - } + /** + * 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 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 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 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 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 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 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 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 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 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 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); + } - /** - * 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; + /** 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; + } - /** - * 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; + public static boolean isSingleExpr(Initializer init) { + // todo expand that to = initialization + if (init == null) { + return false; } + var exprs = init.getExprs(); + return exprs.size() == 1; + } - /** - * 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); - } + public static boolean isListInitializer(Initializer init) { + return init != null && !isSingleExpr(init); + } - /** - * If the initializer contains exactly one expression, - * return it. Otherwise, return null. - */ - public static Expression asSingleExpr(Initializer init) { - if (init == null) { - return null; + /** + * 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); + if (allValidTime && foundNonZero) { + // Conservatively, no bounds are inferred; the returned type + // is a variable-size list. + return InferredType.timeList(); + } } + return InferredType.undefined(); + } - /** - * 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; - } - 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 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()); + } - /** - * 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 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); + } - /** - * 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(); + } - /** - * 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; + /** 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; } + } - /** - * 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; + /** 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; + } - //////////////////////////////// - //// 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(); - } + /** 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; + } - /** - * 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; - } - } + /** 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; + } - /** - * 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); + /** + * 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 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 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 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; - } + /** + * 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); + } - /** - * 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 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; + } - /** - * 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); + /** + * 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; + } - /** - * 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; - } + /** + * 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; } - 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; - } + } 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; + } } + 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; + /** + * 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); } - 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; + } + + 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."); } - } 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; - } + // 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; + } } - } - 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); + 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 portWidth = width(((Port) reference.getVariable()).getWidthSpec(), extended); - if (portWidth < 0) { - // Could not determine port width. - return -1; + 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; } - - // 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; - } - } + // Check that portWidth divides the discrepancy. + if (discrepancy % portWidth != 0) { + // This is an error. + return -1; } - return portWidth * bankWidth; + bankWidth = discrepancy / portWidth; + } else { + // Could not determine the bank width. + return -1; + } } - // Argument is not a port. - return -1; + } + 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; - } + /** + * 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 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); - } + /** + * 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; - } + /** + * 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; - } + /** + * 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 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 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 ret.stream(); + } - /** - * 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; - }; - } + /** 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"); + } - /** - * 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); + /** 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(""); - } + /** + * 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; + } - } 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 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)); + } - /** - * 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 - ///////////////////////////////////////////////////////// - //// 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<>(); + } - /** - * 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; + } - /** - * 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); + /** + * 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); } - 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; - } + } + 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); } - // A connection was not found with the instantiation. - return -1; - } + } + 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); - } -} \ No newline at end of file + 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/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 3a7a5fb2cd..73cd12916a 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -275,20 +275,20 @@ public Boolean caseAttrParm(AttrParm object) { .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 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) { diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 52e2a5794b..b3bf51d836 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -66,21 +66,18 @@ 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 @@ -264,45 +261,45 @@ public PortInstance getInput(String name) { 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()); - } - } - // 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 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; + } + // 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 cachedCycles; + } + /** * Override the base class to append [i_d], where d is the depth, if this reactor is in a bank of * reactors. @@ -448,51 +445,52 @@ public Expression resolveParameters(Expression e) { return LfExpressionVisitor.dispatch(e, this, ParameterInliner.INSTANCE); } - private static final class ParameterInliner extends LfExpressionVisitor.LfExpressionDeepCopyVisitor { + 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 " + 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; + 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 startup trigger or null if not used in any reaction. - // */ - // public TriggerInstance getStartupTrigger() { - // return _instantiations; - // } + // /** + // * 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. @@ -670,21 +668,21 @@ public TimeValue getTimeValue(Expression expr) { protected void createReactionInstances() { List reactions = ASTUtils.allReactions(reactorDefinition); if (reactions != null) { - int count = 0; + 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); + // 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); + } } } @@ -692,14 +690,14 @@ protected void createReactionInstances() { 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); + 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); - } + // Add the watchdog instance to the list of watchdogs for this + // reactor. + this.watchdogs.add(watchdogInstance); + } } } @@ -716,172 +714,167 @@ protected void createWatchdogInstances() { * @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."); + 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); - // 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; - } + this.recursive = foundSelfAsParent; + if (recursive) { + reporter.reportError(definition, "Recursive reactor instantiation."); + } - setInitialWidth(); + // 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; + } - // Apply overrides and instantiate parameters for this reactor instance. - for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { - this.parameters.add(new ParameterInstance(parameter, this)); - } + setInitialWidth(); - // Instantiate inputs for this reactor instance - for (Input inputDecl : ASTUtils.allInputs(reactorDefinition)) { - this.inputs.add(new PortInstance(inputDecl, this, reporter)); - } + // Apply overrides and instantiate parameters for this reactor instance. + for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { + this.parameters.add(new ParameterInstance(parameter, this)); + } - // Instantiate outputs for this reactor instance - for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { - this.outputs.add(new PortInstance(outputDecl, this, reporter)); - } + // Instantiate inputs for this reactor instance + for (Input inputDecl : ASTUtils.allInputs(reactorDefinition)) { + this.inputs.add(new PortInstance(inputDecl, this, reporter)); + } - // 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 outputs for this reactor instance + for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { + this.outputs.add(new PortInstance(outputDecl, this, reporter)); + } - // Instantiate timers for this reactor instance - for (Timer timerDecl : ASTUtils.allTimers(reactorDefinition)) { - this.timers.add(new TimerInstance(timerDecl, this)); - } + // 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 actions for this reactor instance - for (Action actionDecl : ASTUtils.allActions(reactorDefinition)) { - this.actions.add(new ActionInstance(actionDecl, this)); - } + // Instantiate timers for this reactor instance + for (Timer timerDecl : ASTUtils.allTimers(reactorDefinition)) { + this.timers.add(new TimerInstance(timerDecl, this)); + } - establishPortConnections(); + // Instantiate actions for this reactor instance + for (Action actionDecl : ASTUtils.allActions(reactorDefinition)) { + this.actions.add(new ActionInstance(actionDecl, this)); + } - // 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(); + establishPortConnections(); - // 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(); - } - } + // 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()); - } - } + /** + * 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 _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 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()); - } + /** + * 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(); - } - return false; + /** + * 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 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; + /////////////////////////////////////////////////// + //// 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. @@ -903,100 +896,100 @@ public static void connectPortInstances( dst.instance.dependsOnPorts.add(src); } - ////////////////////////////////////////////////////// - //// Protected methods. + ////////////////////////////////////////////////////// + //// 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)); - } + /** 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."); - } - return; - } else if (!dstRanges.hasNext()) { - reporter.reportWarning(connection, "No destination. Outputs will be lost."); - return; - } + /** + * 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(); - } + 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(); } + } } + } /** * If path exists from the specified port to any reaction in the specified set of reactions, then @@ -1044,93 +1037,91 @@ private boolean findPaths( */ 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); - } + 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); } - // 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); + } + } + // 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; } + return result; + } /** * If this is a bank of reactors, set the width. It will be set to -1 if it cannot be determined. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index e344e9f873..b26b3061c0 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1,22 +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; @@ -31,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; @@ -41,33 +43,26 @@ 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.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; @@ -78,7 +73,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 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; @@ -93,206 +87,171 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 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,1150 +265,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; - 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)); - } + // 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 CGenerator(LFGeneratorContext context, boolean ccppMode) { - this( - context, - ccppMode, - new CTypes(), - new CCmakeGenerator(context.getFileConfig(), List.of()), - new CDelayBodyGenerator(new CTypes()) - ); - } + public static int UNDEFINED_MIN_SPACING = -1; - /** - * 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; - } - } - } - } - } + //////////////////////////////////////////// + //// Protected fields - /** - * 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; - } + /** The main place to put generated code. */ + protected CodeBuilder code = new CodeBuilder(); - /** - * 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); - } + /** Place to collect code to initialize the trigger objects for all reactor instances. */ + protected CodeBuilder initializeTriggerObjects = new CodeBuilder(); - // 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); - } - } + protected final CFileConfig fileConfig; - // 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); - } + /** + * 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; - 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; - } + //////////////////////////////////////////// + //// Private fields + /** Extra lines that need to go into the generated CMakeLists.txt. */ + private String cMakeExtras = ""; - // 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); - } + /** Place to collect code to execute at the start of a time step. */ + private CodeBuilder startTimeStep = new CodeBuilder(); - // 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); - } + /** + * 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())); + } - // 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)); + /** + * 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; + } } + } + } + } - // In case we are in Eclipse, make sure the generated code is visible. - GeneratorUtils.refreshProject(resource, context.getMode()); + /** + * 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; + } - 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("}"); - } + /** + * 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 - // 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 - )); - } + // 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); } - @Override - public void checkModalReactorSupport(boolean __) { - // Modal reactors are currently only supported for non federated applications - super.checkModalReactorSupport(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); + } } - @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);" - ); + // 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; } - /** 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; - } - } - } + // 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); } - private boolean hasDeadlines(List reactors) { - for (Reactor reactor : reactors) { - for (Reaction reaction : allReactions(reactor)) { - if (reaction.getDeadline() != null) { - return true; - } - } + // 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); } - return false; + cleanCode.writeToFile(targetFile); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } } - private boolean hasWatchdogs() { - for (Reactor reactor : reactors) { - List watchdogs = ASTUtils.allWatchdogs(reactor); - if (watchdogs != null && !watchdogs.isEmpty()) { - return true; - } + // 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); } - return false; + System.out.println("Compiled binary is in " + fileConfig.binPath); + } else { + context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + } + + // In case we are in Eclipse, make sure the generated code is visible. + GeneratorUtils.refreshProject(resource, context.getMode()); } - /** - * 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 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)); } + } - /** - * 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); - } + @Override + public void checkModalReactorSupport(boolean __) { + // Modal reactors are currently only supported for non federated applications + super.checkModalReactorSupport(true); + } - 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 - ); - } - } + @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);"); + } - 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 - ); - } + /** 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; } + } + } + } - 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); - } + private boolean hasDeadlines(List reactors) { + for (Reactor reactor : reactors) { + for (Reaction reaction : allReactions(reactor)) { + if (reaction.getDeadline() != null) { + return true; } + } } + return 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); - } + private boolean hasWatchdogs() { + for (Reactor reactor : reactors) { + List watchdogs = ASTUtils.allWatchdogs(reactor); + if (watchdogs != null && !watchdogs.isEmpty()) { + return true; + } + } + return false; + } - if (this.mainDef != null) { - generateReactorClass(ASTUtils.toDefinition(this.mainDef.getReactorClass())); + /** + * 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()); + } + } + } - 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 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); } - /** 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); + 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); + } } - /** - * 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); - } - } + 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); + } } - /** - * 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 (!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 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); + } - /** - * 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 - ); - } + if (this.mainDef != null) { + generateReactorClass(ASTUtils.toDefinition(this.mainDef.getReactorClass())); + } - // 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 - ); + 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); } + } } + } - //////////////////////////////////////////// - //// 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); + /** 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); + } - 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); + /** + * 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); + } } + } - 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); + /** + * 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"); } + } - /** - * Generate methods for {@code reactor}. - */ - protected void generateMethods(CodeBuilder src, ReactorDecl reactor) { - CMethodGenerator.generateMethods(reactor, src, types); + /** 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); } - /** - * 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."); - } + // 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 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() - )); + //////////////////////////////////////////// + //// 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\" {"); + } + 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); + } + + 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); + } - protected void generateIncludes(Reactor r) { - code.pr("#include \"" + CUtil.getName(r) + ".h\""); + /** + * 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 - ); - - // 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 + ";"); + // 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 - } + /** + * 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 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, @@ -1457,740 +1399,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); - } + /** + * 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); + } } - /** 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. + /** + * 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 - )); + 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()); - } - } - - private void recordWatchdogs(ReactorInstance instance) { - var foundOne = false; + /** + * 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 reactorRef = CUtil.reactorRef(instance); - // temp.pr("#ifdef LF_THREADED"); - for (WatchdogInstance watchdog : instance.watchdogs) { - 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(watchdog.getTimeout())+";"); - temp.pr(" " + reactorRef+"->_lf_watchdog_"+watchdog.getName()+".thread_id;"); - watchdogCount += 1; + 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; + } } - // temp.pr("#endif"); - if (foundOne) { - initializeTriggerObjects.pr(temp.toString()); - } + 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; + private void recordWatchdogs(ReactorInstance instance) { + var foundOne = false; + var temp = new CodeBuilder(); + var reactorRef = CUtil.reactorRef(instance); + // temp.pr("#ifdef LF_THREADED"); + for (WatchdogInstance watchdog : instance.watchdogs) { + 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(watchdog.getTimeout()) + + ";"); + temp.pr(" " + reactorRef + "->_lf_watchdog_" + watchdog.getName() + ".thread_id;"); + watchdogCount += 1; + foundOne = true; + } + // temp.pr("#endif"); + if (foundOne) { + initializeTriggerObjects.pr(temp.toString()); + } + } - for (ActionInstance action : instance.actions) { - foundOne = true; + /** + * 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; + + 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; + + // 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(); + } } + 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(); - } - } + /** + * 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"; - } - - /** - * 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"; + 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 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) - ); - } - } + /** + * 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"; + } - /** - * 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(); - } + /** + * 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"; + } - // 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); + /** + * 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)); } + } - /** - * 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)); + /** + * 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(); } - /** - * 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 - ) - ); - } - } - } + // 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); + } - /** - * 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 - } + /** + * 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)); + } - /** - * 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(); - } - } + /** + * 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 + ")"; + } } - } - /** - * 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;"); - } - } + var selfStruct = CUtil.reactorRef(action.getParent()); + initializeTriggerObjects.pr( + CActionGenerator.generateTokenInitializer(selfStruct, action.getName(), payloadSize)); + } } + } - /** - * 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 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 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 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 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 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(); } + } - /** - * 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 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 + ";"); + } } + } - @Override - public TargetTypes getTargetTypes() { - return types; + /** + * 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)); } + } - /** - * - * @param context - * @return - */ - protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { - return new CDockerGenerator(context); + /** + * 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)); } + } - // ////////////////////////////////////////// - // // Protected methods. + @Override + public TargetTypes getTargetTypes() { + return types; + } - // 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; - } + /** + * @param context + * @return + */ + protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { + return new CDockerGenerator(context); + } - 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 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"); } - - protected void handleProtoFiles() { - // Handle .proto files. - for (String file : targetConfig.protoFiles) { - this.processProtoFile(file); - } + 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; } - /** - * 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(); + 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; } - - /** - * 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")); - } - builder.pr("#endif"); - return builder.toString(); + 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 boolean targetLanguageIsCpp() { - return CCppMode; + protected void handleProtoFiles() { + // Handle .proto files. + for (String file : targetConfig.protoFiles) { + this.processProtoFile(file); } + } - /** 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; - } - return null; - } + /** + * 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(); + } - //////////////////////////////////////////// - //// Private methods. - /** Returns the Target enum for this generator */ - @Override - public Target getTarget() { - return Target.C; + /** 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")); } + builder.pr("#endif"); + return builder.toString(); + } - //////////////////////////////////////////////////////////// - //// 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()) - ); - } - } - } + 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; } + return null; + } + + //////////////////////////////////////////// + //// Private methods. + /** Returns the Target enum for this generator */ + @Override + public Target getTarget() { + return Target.C; + } + + //////////////////////////////////////////////////////////// + //// Private methods - /** - * 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); + /** + * 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())); + } + } } -} \ No newline at end of file + } + + /** + * 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 67a76eb0e2..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; @@ -38,1160 +37,1347 @@ 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) + 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); + /** + * 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); - // Construct the reactionInitialization code to go into - // the body of the function before the verbatim code. - CodeBuilder reactionInitialization = new CodeBuilder(); + // Construct the reactionInitialization code to go into + // the body of the function before the verbatim code. + CodeBuilder reactionInitialization = new CodeBuilder(); - CodeBuilder code = new CodeBuilder(); + CodeBuilder code = 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);" - )); - } + // 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);")); + } - // 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(); - } + // 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(); + } - // 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<>(); + // 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<>(); - // 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<>(); + // 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<>(); - // 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)); - } + // 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()); } - // 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()); - } - } - - // 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." - ); - } - } - } - } - // 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 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(); + /** + * 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); - return String.join("\n", List.of( - "watchdog_t* "+watchdogName+" = &(self->_lf_watchdog_"+watchdogName+");" - )); + // 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(); + } - /** - * 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 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;"); + } + } + } - // 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()); - } - } + /** + * 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(); - var deadlineFunctionPointer = "NULL"; - if (reaction.getDeadline() != null) { - // The following has to match the name chosen in generateReactions - var deadlineFunctionName = generateDeadlineFunctionName(reactor, reactionCount); - deadlineFunctionPointer = "&" + deadlineFunctionName; - } + return String.join( + "\n", + List.of("watchdog_t* " + watchdogName + " = &(self->_lf_watchdog_" + watchdogName + ");")); + } - // 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 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 + ";"); - // 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++; + // 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; + } } - - // 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};")); + } + // 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()); } + } - // Handle builtin triggers. - if (startupReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(startupReactions, "startup", body, constructorCode); - } - // Handle shutdown triggers. - if (shutdownReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(shutdownReactions, "shutdown", body, constructorCode); - } - if (resetReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(resetReactions, "reset", body, constructorCode); - } + var deadlineFunctionPointer = "NULL"; + if (reaction.getDeadline() != null) { + // The following has to match the name chosen in generateReactions + var deadlineFunctionName = generateDeadlineFunctionName(reactor, reactionCount); + deadlineFunctionPointer = "&" + deadlineFunctionName; + } - // 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 + ")"; - } + // 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; + } - // 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 + ";" - )); - } + // 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++; + } - // Next handle inputs. - for (Input input : ASTUtils.allInputs(reactor)) { - createTriggerT(body, input, triggerMap, constructorCode, types); - } + // 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};")); + } - // Next handle watchdogs. - for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - createTriggerT(body, watchdog, triggerMap, constructorCode, types); - } + // Handle builtin triggers. + if (startupReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(startupReactions, "startup", body, constructorCode); + } + // Handle shutdown triggers. + if (shutdownReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(shutdownReactions, "shutdown", body, constructorCode); + } + if (resetReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(resetReactions, "reset", body, constructorCode); } - /** - * 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};")); + // 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 + ")"; + } - // 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+";" - )); + // 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 + ";")); + } - // 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+")"; + // Next handle inputs. + for (Input input : ASTUtils.allInputs(reactor)) { + createTriggerT(body, input, triggerMap, constructorCode, types); + } - constructorCode.pr("self->_lf__"+varName+".tmplt.type.element_size = "+size+";"); - body.pr( - CExtensionUtils.surroundWithIfFederated( - CExtensionUtils.createPortStatusFieldForInput((Input) variable) - ) - ); - } + // Next handle watchdogs. + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + createTriggerT(body, watchdog, triggerMap, constructorCode, types); } + } - 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;" - )); + /** + * 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};")); + + // 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 + ")"; - 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+";" - )); + constructorCode.pr("self->_lf__" + varName + ".tmplt.type.element_size = " + size + ";"); + body.pr( + CExtensionUtils.surroundWithIfFederated( + CExtensionUtils.createPortStatusFieldForInput((Input) variable))); } + } - /** - * 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(); + 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;")); + } - /** - * 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(); + 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 + ";")); + } + + /** 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(); + } - /** - * 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(); + /** 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(); + } - /** 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); + /** 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(); + } - code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetHeader())); + /** + * 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); - 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())); - } + code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader())); - // 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(); + 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 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; + // 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(); + } - 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(); - } + 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; + } - /** - * 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 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(); + } - /** - * 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 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; + } - /** - * 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 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; + } - /** 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); - } + /** + * 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 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); - } + /** + * 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); + } - public static String generateStpFunctionHeader(Reactor r, - int reactionIndex) { - String functionName = generateStpFunctionName(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 generateFunctionHeader(String functionName) { - return "void " + functionName + "(void* instance_args)"; - } -} \ No newline at end of file + 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 index 621077de86..5a6f1dc3ba 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -1,7 +1,6 @@ package org.lflang.generator.c; import java.util.List; - import org.lflang.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Mode; diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 2cf96e0d68..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 - ); - } - -} \ No newline at end of file + """) + .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/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 99e0a5bab4..b0555dc4e0 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1,6 +1,3 @@ - - - /* Validation checks for Lingua Franca code. */ /************* @@ -27,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; @@ -44,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; @@ -71,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; @@ -115,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 @@ -131,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) { - - // 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; - } - } + @Check(CheckType.FAST) + public void checkConnection(Connection connection) { - 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); - } - } - } - } - } + // 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; - // 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( + 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( - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.PARAMETER__INIT); + 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())); + } -} \ No newline at end of file + // 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/Python/src/PyWatchdog.lf b/test/Python/src/PyWatchdog.lf deleted file mode 100644 index a89cf0d7de..0000000000 --- a/test/Python/src/PyWatchdog.lf +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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 Python { - timeout: 1100ms, - threading: true -} - -reactor Watcher(timeout(150 ms)) { - timer t(100 ms, 100 ms) // Offset ameliorates startup time. - output d // Produced if watchdog triggers. - state alternating(False) - state count(0) - - watchdog poodle(timeout) {= - p = lf_time_physical_elapsed() - lf_time_logical_elapsed() - lf_print("Watchdog timed out! Lag: "+p+" (too late by "+(p - self.timeout)+" ns)") - self.count += 1 - =} - - reaction(t) -> poodle, d {= - lf_watchdog_start(poodle, 0) - lf_print("Watchdog started at physical time "+lf_time_physical_elapsed()) - lf_print("Will expire at "+lf_time_logical_elapsed() + self.timeout) - if (self.alternating) { - lf_sleep(160 ms) - } - self.alternating = not self.alternating - =} - - reaction(poodle) -> d {= - lf_print("Reaction poodle was called.") - d.set(1) - =} - - reaction(shutdown) -> poodle {= - _lf_watchdog_stop(poodle) - if (self.count != 5) { - lf_print_error_and_exit("Watchdog expired "+self.count+" times. Expected 5.") - } - =} -} - -main reactor { - logical action a - state count(0) - w = new Watcher() - - reaction(w.d) {= - lf_print("*** Watcher reactor produced an output.") - self.count += 1 - =} - - reaction(shutdown) {= - if (self.count != 5) { - lf_print_error_and_exit("Watchdog produced output "+self.count+" times. Expected 5.") - } - =} -} From 1f86008ea9b321e057af51b9aa5256590c578515 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 21 Apr 2023 10:36:36 -0700 Subject: [PATCH 069/108] updating reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index ff3c84c36b..7dcdb7dfdb 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ff3c84c36b2d7fbcbe09fafa577ec94f75194e83 +Subproject commit 7dcdb7dfdbc065c59fe1372779d5e0606dccda30 From 1493800e22e4aa007e6ffadb25706696bd08d7d3 Mon Sep 17 00:00:00 2001 From: Benjamin Asch Date: Fri, 21 Apr 2023 10:38:51 -0700 Subject: [PATCH 070/108] fixed invocation of watchdog_stop in watchdog test --- test/C/src/Watchdog.lf | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index 5d2c87b724..e40157a9e6 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -6,8 +6,7 @@ * @author Edward A. Lee */ target C { - timeout: 1100 ms, - fast: false + timeout: 1100 ms } reactor Watcher(timeout: time = 150 ms) { @@ -39,8 +38,7 @@ reactor Watcher(timeout: time = 150 ms) { =} reaction(shutdown) -> poodle {= - // FIXME: There needs to be an lf_watchdog_stop() defined. - _lf_watchdog_stop(poodle); + lf_watchdog_stop(poodle); if (self->count != 5) { lf_print_error_and_exit("Watchdog expired %d times. Expected 5.", self->count); } From 2e986dce1e756be4e58793b57bdaaede847abee6 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 27 Apr 2023 08:37:37 +0200 Subject: [PATCH 071/108] Aligned to reactor-c/watchdogs --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 7dcdb7dfdb..abe2485cca 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 7dcdb7dfdbc065c59fe1372779d5e0606dccda30 +Subproject commit abe2485cca3299d9e056cc7f9a55d306295f00ee From e32c9ff4f0b620eee7d2831a1511862ed0881a2e Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 27 Apr 2023 13:29:24 +0200 Subject: [PATCH 072/108] First pass a refactoring --- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/c/CGenerator.java | 6 +---- .../generator/c/CWatchdogGenerator.java | 23 ------------------- 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index abe2485cca..5c0ba3b435 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit abe2485cca3299d9e056cc7f9a55d306295f00ee +Subproject commit 5c0ba3b43545758a8f96dc168e6947afc767626b diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index b26b3061c0..7dbabf8aee 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -670,9 +670,6 @@ private void generateCodeFor(String lfModuleName) throws IOException { // 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 @@ -1175,8 +1172,7 @@ private void generateSelfStruct( // 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. + // The first field has to always be a pointer to be struct_base_t. // This means that the struct can be safely cast to self_base_t. builder.pr("typedef struct {"); builder.indent(); diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 5a6f1dc3ba..50428910f5 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -212,27 +212,4 @@ public static String generateBuiltinTriggersTable(int count, String name) { " 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(); - } } From 6f9873967b1827cb60dd6299ef06b63fb403e44f Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 27 Apr 2023 17:23:25 +0200 Subject: [PATCH 073/108] Align reactor-c-py to main --- org.lflang/src/lib/py/reactor-c-py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/py/reactor-c-py b/org.lflang/src/lib/py/reactor-c-py index 14146b2f7b..80c24aa2e5 160000 --- a/org.lflang/src/lib/py/reactor-c-py +++ b/org.lflang/src/lib/py/reactor-c-py @@ -1 +1 @@ -Subproject commit 14146b2f7be6db8261b6a45724e18e0693f1f822 +Subproject commit 80c24aa2e5bc814434893e9b6e4b183b4827c36a From b6ef20dba5d3f8fa3433d1a97e6cd79324d8a057 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 27 Apr 2023 17:57:47 +0200 Subject: [PATCH 074/108] Aligned reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 5c0ba3b435..4aef0ef54c 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 5c0ba3b43545758a8f96dc168e6947afc767626b +Subproject commit 4aef0ef54ca233b3cd9ff43863d0ffa715b91e8b From 5c3114e46b3cbf29fde1ef0b621dd8b3b066321c Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Fri, 28 Apr 2023 11:51:57 +0200 Subject: [PATCH 075/108] Continued refactoring --- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/c/CGenerator.java | 49 +++---------- .../generator/c/CWatchdogGenerator.java | 72 ++++++++++++------- 3 files changed, 54 insertions(+), 69 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 4aef0ef54c..5d58c62cb1 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 4aef0ef54ca233b3cd9ff43863d0ffa715b91e8b +Subproject commit 5d58c62cb141e13c423cdadd11dbf97c8edd2d75 diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 7dbabf8aee..44691e773b 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -644,7 +644,7 @@ private void generateCodeFor(String lfModuleName) throws IOException { code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); // If there are watchdogs, create a table of triggers. - code.pr(CWatchdogGenerator.generateBuiltinTriggersTable(watchdogCount, "watchdog")); + code.pr(CWatchdogGenerator.generateWatchdogTable(watchdogCount)); // If there are modes, create a table of mode state to be checked for transitions. code.pr( @@ -750,16 +750,6 @@ private boolean hasDeadlines(List reactors) { return false; } - private boolean hasWatchdogs() { - for (Reactor reactor : reactors) { - List watchdogs = ASTUtils.allWatchdogs(reactor); - if (watchdogs != null && !watchdogs.isEmpty()) { - return true; - } - } - 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 @@ -1045,9 +1035,10 @@ private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeB // go into the constructor. Collect those lines of code here: var constructorCode = new CodeBuilder(); generateAuxiliaryStructs(header, reactor, false); + // The following must go before the self struct so the #include watchdog.h ends up in the header. + CWatchdogGenerator.generateWatchdogs(src, header, reactor); generateSelfStruct(header, reactor, constructorCode); generateMethods(src, reactor); - generateWatchdogs(src, reactor); generateReactions(src, reactor); generateConstructor(src, header, reactor, constructorCode); } @@ -1074,6 +1065,8 @@ protected void generateUserPreamblesForReactor(Reactor reactor, CodeBuilder src) /** * Generate a constructor for the specified reactor in the specified federate. * + * @param src Where to put the assembled code. + * @param header Where to put header code. * @param reactor The parsed reactor data structure. * @param constructorCode Lines of code previously generated that need to go into the constructor. */ @@ -1398,33 +1391,6 @@ protected void generateReaction( 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); - } - } - - /** - * 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. * @@ -1480,7 +1446,8 @@ private void recordWatchdogs(ReactorInstance instance) { var temp = new CodeBuilder(); var reactorRef = CUtil.reactorRef(instance); // temp.pr("#ifdef LF_THREADED"); - for (WatchdogInstance watchdog : instance.watchdogs) { + for (Watchdog watchdog + : ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) { temp.pr( " _lf_watchdogs[_lf_watchdog_number_count++] = &" + reactorRef @@ -1493,7 +1460,7 @@ private void recordWatchdogs(ReactorInstance instance) { + "->_lf_watchdog_" + watchdog.getName() + ".min_expiration = " - + CTypes.getInstance().getTargetTimeExpr(watchdog.getTimeout()) + + CTypes.getInstance().getTargetTimeExpr(instance.getTimeValue(watchdog.getTimeout())) + ";"); temp.pr(" " + reactorRef + "->_lf_watchdog_" + watchdog.getName() + ".thread_id;"); watchdogCount += 1; diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 50428910f5..5bd0fe648d 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -18,6 +18,36 @@ */ public class CWatchdogGenerator { + /** + * 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 src The place to put the code. + * @param header The place to put header code. + * @param decl The reactor declaration. + */ + public static void generateWatchdogs(CodeBuilder src, CodeBuilder header, ReactorDecl decl) { + var reactor = ASTUtils.toDefinition(decl); + if (hasWatchdogs(reactor)) { + header.pr("#include \"core/threaded/watchdog.h\""); + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + src.pr(generateWatchdogFunction(watchdog, decl)); + } + } + } + + /** + * Return true if the given reactor has one or more watchdogs. + * @param reactor The reactor. + * @return True if the given reactor has watchdogs. + */ + public static boolean hasWatchdogs(Reactor reactor) { + List watchdogs = ASTUtils.allWatchdogs(reactor); + if (watchdogs != null && !watchdogs.isEmpty()) return true; + return false; + } + /** * Generate necessary initialization code inside the body of the watchdog that belongs to reactor * decl. @@ -147,7 +177,7 @@ public static String generateFunction(String header, String init, Watchdog watch return function.toString(); } - /** Generate watchdog definition in parent struct. */ + /** Generate watchdog definition in the reactor's self struct. */ public static void generateWatchdogStruct( CodeBuilder body, ReactorDecl decl, CodeBuilder constructorCode) { var reactor = ASTUtils.toDefinition(decl); @@ -161,7 +191,6 @@ public static void generateWatchdogStruct( 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( @@ -169,7 +198,6 @@ public static void generateWatchdogStruct( "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 = " @@ -179,37 +207,27 @@ public static void generateWatchdogStruct( + 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. + * Generate a global table of watchdog structs. + * @param count The number of watchdogs found. + * @return The code that defines the table or a comment if count is 0. */ - 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) { + public static String generateWatchdogTable(int count) { + if (count == 0) { + return "// No watchdogs found."; + } 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")); + "// Array of pointers to watchdog structs.", + " watchdog_t* _lf_watchdogs[" + count + "];", + " int _lf_watchdog_number = " + count + ";" + )); } } From af103df9737ec9d98efc28d79c88d7f91b1b8ebe Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Fri, 28 Apr 2023 13:51:48 +0200 Subject: [PATCH 076/108] Attempt to exclude FilePkgReader --- util/RunZephyrTests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/RunZephyrTests.sh b/util/RunZephyrTests.sh index cabbbec0b2..25f5c12c58 100755 --- a/util/RunZephyrTests.sh +++ b/util/RunZephyrTests.sh @@ -9,7 +9,7 @@ num_failures=0 failed_tests="" # Skip -skip=("FileReader") +skip=("FileReader", "FilePkgReader") find_kconfig_folders() { if [ -f "$folder/CMakeLists.txt" ]; then From 97e53b6c9dea50ea0785d75c81a569f6365d150c Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 29 Apr 2023 06:23:01 +0200 Subject: [PATCH 077/108] Another attempt to exclude FileReader --- util/RunZephyrTests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/RunZephyrTests.sh b/util/RunZephyrTests.sh index 25f5c12c58..742a23ef18 100755 --- a/util/RunZephyrTests.sh +++ b/util/RunZephyrTests.sh @@ -9,7 +9,7 @@ num_failures=0 failed_tests="" # Skip -skip=("FileReader", "FilePkgReader") +skip=("FileReader" "FilePkgReader") find_kconfig_folders() { if [ -f "$folder/CMakeLists.txt" ]; then From 49d3ba9fd15de7bcb354cb0db93f0bc67b843cdf Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 29 Apr 2023 11:28:53 +0200 Subject: [PATCH 078/108] Watcher cannot watch its own reactions --- test/C/src/Watchdog.lf | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index e40157a9e6..a836c93c3f 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -11,14 +11,13 @@ target C { reactor Watcher(timeout: time = 150 ms) { timer t(100 ms, 100 ms) // Offset ameliorates startup time. + // Period has to be smaller than watchdog timeout. 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()); + instant_t p = lf_time_physical_elapsed(); + lf_print("******** Watchdog timed out at elapsed physical time: " PRINTF_TIME, p); self->count++; =} @@ -26,10 +25,7 @@ reactor Watcher(timeout: time = 150 ms) { 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; + lf_set(d, 42); =} reaction(poodle) -> d {= @@ -39,8 +35,8 @@ reactor Watcher(timeout: time = 150 ms) { reaction(shutdown) -> poodle {= lf_watchdog_stop(poodle); - if (self->count != 5) { - lf_print_error_and_exit("Watchdog expired %d times. Expected 5.", self->count); + if (self->count != 2) { + lf_print_error_and_exit("Watchdog expired %d times. Expected 2.", self->count); } =} } @@ -52,13 +48,17 @@ main reactor { w = new Watcher() reaction(w.d) {= - lf_print("*** Watcher reactor produced an output."); + lf_print("Watcher reactor produced an output. %d", self->count % 2); self->count++; + if (self->count % 4 == 0) { + lf_print(">>>>>> Taking a long time to process that output!"); + lf_sleep(MSEC(160)); + } =} reaction(shutdown) {= - if (self->count != 5) { - lf_print_error_and_exit("Watchdog produced output %d times. Expected 5.", self->count); + if (self->count != 12) { + lf_print_error_and_exit("Watchdog produced output %d times. Expected 12.", self->count); } =} } From 661df8802900db14666b8b5134495b681768852a Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 29 Apr 2023 11:29:34 +0200 Subject: [PATCH 079/108] Continued refactoring and docs --- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/c/CGenerator.java | 35 +-- .../generator/c/CReactionGenerator.java | 6 + .../generator/c/CWatchdogGenerator.java | 272 +++++++++++------- 4 files changed, 177 insertions(+), 138 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 5d58c62cb1..68e2c14fab 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 5d58c62cb141e13c423cdadd11dbf97c8edd2d75 +Subproject commit 68e2c14fabf124f280e656e23f1954df1fd80657 diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 44691e773b..3a3352aa03 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -72,7 +72,6 @@ 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; @@ -86,7 +85,6 @@ 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; @@ -1441,37 +1439,6 @@ private void recordBuiltinTriggers(ReactorInstance instance) { } } - 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. @@ -1746,7 +1713,7 @@ public void generateReactorInstance(ReactorInstance instance) { initializeOutputMultiports(instance); initializeInputMultiports(instance); recordBuiltinTriggers(instance); - recordWatchdogs(instance); + watchdogCount += CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, instance); // Next, initialize the "self" struct with state variables. // These values may be expressions that refer to the parameter values defined above. diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index dae1f01230..a203f9ee3c 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -1377,6 +1377,12 @@ public static String generateStpFunctionHeader(Reactor r, int reactionIndex) { return generateFunctionHeader(functionName); } + /** + * Return the start of a function declaration for a function that takes + * a `void*` argument and returns void. + * @param functionName + * @return + */ 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 index 5bd0fe648d..695bd5eefa 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -1,8 +1,17 @@ +/** + * @file + * @author Benjamin Asch + * @author Edward A. Lee + * @copyright (c) 2023, The University of California at Berkeley. + * License: BSD 2-clause + * @brief Code generation methods for watchdogs in C. + */ package org.lflang.generator.c; import java.util.List; import org.lflang.ASTUtils; import org.lflang.generator.CodeBuilder; +import org.lflang.generator.ReactorInstance; import org.lflang.lf.Mode; import org.lflang.lf.ModeTransition; import org.lflang.lf.Reactor; @@ -12,22 +21,77 @@ import org.lflang.lf.Watchdog; /** - * Generates necessary C code for watchdogs. + * @brief Generate C code for watchdogs. + * This class contains a collection of static methods supporting code generation in C + * for watchdogs. These methods are protected because they are intended to be used + * only within the same package. * - * @author{Benjamin Asch } + * @author Benjamin Asch + * @author Edward A. Lee */ public class CWatchdogGenerator { + /** + * Return true if the given reactor has one or more watchdogs. + * @param reactor The reactor. + * @return True if the given reactor has watchdogs. + */ + public static boolean hasWatchdogs(Reactor reactor) { + List watchdogs = ASTUtils.allWatchdogs(reactor); + if (watchdogs != null && !watchdogs.isEmpty()) return true; + return false; + } + + ///////////////////////////////////////////////////////////////// + // Protected methods + + /** + * For the specified reactor instance, generate initialization code for each watchdog + * in the reactor. This code initializes the watchdog-related fields on the self struct + * of the reactor instance. + * @param code The place to put the code + * @param instance The reactor instance + * @return The count of watchdogs found in the reactor + */ + protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstance instance) { + var foundOne = false; + var temp = new CodeBuilder(); + var reactorRef = CUtil.reactorRef(instance); + int watchdogCount = 0; + for (Watchdog watchdog + : ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) { + var watchdogField = reactorRef + "->_lf_watchdog_" + watchdog.getName(); + temp.pr(String.join("\n", + "_lf_watchdogs[_lf_watchdog_number_count++] = &" + watchdogField + ";", + watchdogField + ".min_expiration = " + + CTypes.getInstance().getTargetTimeExpr(instance.getTimeValue(watchdog.getTimeout())) + + ";", + watchdogField + ".thread_active = false;", + "if (" + watchdogField + ".base->reactor_mutex == NULL) {", + " " + watchdogField + ".base->reactor_mutex = (lf_mutex_t*)calloc(1, sizeof(lf_mutex_t));", + "}" + )); + watchdogCount += 1; + foundOne = true; + } + // temp.pr("#endif"); + if (foundOne) { + code.pr(temp.toString()); + } + code.pr("SUPPRESS_UNUSED_WARNING(_lf_watchdog_number);"); + return watchdogCount; + } + /** * 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. + * that is a void* pointing to the self struct of the reactor, which contains parameters, state + * variables, inputs (triggering or not), actions (triggering or produced), and outputs. * - * @param src The place to put the code. - * @param header The place to put header code. - * @param decl The reactor declaration. + * @param src The place to put the code + * @param header The place to put header code + * @param decl The reactor declaration */ - public static void generateWatchdogs(CodeBuilder src, CodeBuilder header, ReactorDecl decl) { + protected static void generateWatchdogs(CodeBuilder src, CodeBuilder header, ReactorDecl decl) { var reactor = ASTUtils.toDefinition(decl); if (hasWatchdogs(reactor)) { header.pr("#include \"core/threaded/watchdog.h\""); @@ -38,23 +102,78 @@ public static void generateWatchdogs(CodeBuilder src, CodeBuilder header, Reacto } /** - * Return true if the given reactor has one or more watchdogs. - * @param reactor The reactor. - * @return True if the given reactor has watchdogs. + * Generate watchdog definitions in the reactor's self struct. + * @param body The place to put the definitions + * @param decl The reactor declaration + * @param constructorCode The place to put initialization code. */ - public static boolean hasWatchdogs(Reactor reactor) { - List watchdogs = ASTUtils.allWatchdogs(reactor); - if (watchdogs != null && !watchdogs.isEmpty()) return true; - return false; + protected 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 = watchdogFunctionName(watchdog, decl); + // Set values of watchdog_t struct in the reactor's constructor. + 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 + + ".watchdog_function = " + + watchdogFunctionName + + ";", + "self->_lf_watchdog_" + + watchdogName + + ".trigger = &(self->_lf__" + + watchdogName + + ");" + ) + ); + } } /** - * Generate necessary initialization code inside the body of the watchdog that belongs to reactor - * decl. + * Generate a global table of watchdog structs. + * @param count The number of watchdogs found. + * @return The code that defines the table or a comment if count is 0. + */ + protected static String generateWatchdogTable(int count) { + if (count == 0) { + return String.join("\n", + "// No watchdogs found.", + "watchdog_t* _lf_watchdogs = NULL;", + "int _lf_watchdog_number = 0;" + ); + } + return String.join( + "\n", + List.of( + "// Array of pointers to watchdog structs.", + "watchdog_t* _lf_watchdogs[" + count + "];", + "int _lf_watchdog_number = " + count + ";" + ) + ); + } + + ///////////////////////////////////////////////////////////////// + // Private methods + + /** + * Generate necessary initialization code inside the body of a watchdog handler. * - * @param decl The reactor that has the watchdog + * @param watchdog The wotchdog + * @param decl The declaration for the reactor that has the watchdog */ - public static String generateInitializationForWatchdog(Watchdog watchdog, ReactorDecl decl) { + private static String generateInitializationForWatchdog(Watchdog watchdog, ReactorDecl decl) { Reactor reactor = ASTUtils.toDefinition(decl); // Construct the reactionInitialization code to go into @@ -96,8 +215,8 @@ public static String generateInitializationForWatchdog(Watchdog watchdog, Reacto + name + "_change_type = " + (effect.getTransition() == ModeTransition.HISTORY - ? "history_transition" - : "reset_transition") + ? "history_transition" + : "reset_transition") + ";"); } // FIXME: include error reporter @@ -124,47 +243,13 @@ public static String generateInitializationForWatchdog(Watchdog watchdog, Reacto } /** - * 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 + * Do heavy lifting to generate the watchdog handler 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) { + private static String generateFunction(String header, String init, Watchdog watchdog) { var function = new CodeBuilder(); function.pr(header + " {"); function.indent(); @@ -177,57 +262,38 @@ public static String generateFunction(String header, String init, Watchdog watch return function.toString(); } - /** Generate watchdog definition in the reactor's self 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 + ";"); + /** Generate the watchdog handler function. */ + private static String generateWatchdogFunction(Watchdog watchdog, ReactorDecl decl) { + return generateFunction( + generateWatchdogFunctionHeader(watchdog, decl), + generateInitializationForWatchdog(watchdog, decl), + watchdog); + } - // watchdog function name - var watchdogFunctionName = generateWatchdogFunctionName(watchdog, decl); - // Set values of watchdog_t struct in the reactor's constructor - // FIXME: update parameters - 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 - + ".watchdog_function = " - + watchdogFunctionName - + ";", - "self->_lf_watchdog_" - + watchdogName - + ".trigger = &(self->_lf__" - + watchdogName - + ");" - ) - ); - } + /** + * Return the start of a C function definition for a watchdog. + * + * @param watchdog The watchdog + * @param decl The reactor declaration + * @return The function name for the watchdog function. + */ + private static String generateWatchdogFunctionHeader(Watchdog watchdog, ReactorDecl decl) { + String functionName = watchdogFunctionName(watchdog, decl); + return CReactionGenerator.generateFunctionHeader(functionName); } /** - * Generate a global table of watchdog structs. - * @param count The number of watchdogs found. - * @return The code that defines the table or a comment if count is 0. + * Return the name of the watchdog expiration handler function. + * + * @param watchdog The watchdog + * @param decl The reactor with the watchdog + * @return Name of the watchdog handler function */ - public static String generateWatchdogTable(int count) { - if (count == 0) { - return "// No watchdogs found."; - } - return String.join( - "\n", - List.of( - "// Array of pointers to watchdog structs.", - " watchdog_t* _lf_watchdogs[" + count + "];", - " int _lf_watchdog_number = " + count + ";" - )); + private static String watchdogFunctionName(Watchdog watchdog, ReactorDecl decl) { + return decl.getName().toLowerCase() + + "_" + + watchdog.getName().toLowerCase() + + "_watchdog_function"; } } From 95a7116ee548a0781efdd384b871cd564f679eff Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Apr 2023 09:37:12 +0200 Subject: [PATCH 080/108] Aligned reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 68e2c14fab..97466da394 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 68e2c14fabf124f280e656e23f1954df1fd80657 +Subproject commit 97466da394d9572e4ba905b2706759bef73c0af0 From 4a5582524869a886a76fcde0159bcd37283eb1e2 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Apr 2023 10:45:32 +0200 Subject: [PATCH 081/108] Manually reverted formatting changes. --- org.lflang/src/org/lflang/ASTUtils.java | 3312 ++++++++++++----------- 1 file changed, 1696 insertions(+), 1616 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 4606b4d35f..f8c436727c 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -1,12 +1,16 @@ /* 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 @@ -21,9 +25,6 @@ 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; @@ -31,6 +32,7 @@ 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; @@ -38,6 +40,7 @@ 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; @@ -58,6 +61,8 @@ 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,1662 +96,1737 @@ 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(), - 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); + /** + * 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)); + } + } + } } - } } - 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 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<>()); + } - // 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); - } + /** + * 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)); + } } - // 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)); + } + + // 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 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 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 } - } - } - - // 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. + + 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); + } } - } while (idx > 0); } - } - } - 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"; + 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 { - StringBuilder result = new StringBuilder(type.getId()); + String v = elementToSingleString(value); + if (!v.isEmpty()) { + elements.add(v); + } + } + return elements; + } - for (String s : convertToEmptyListIfNull(type.getStars())) { - result.append(s); - } - return result.toString(); + /** + * 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 ""; - } - - /** - * 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; - } - } 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; + 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); } - if (!ASTUtils.isZero(e)) { - foundNonZero = true; + + 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); } - } - - 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; + 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()); } - } - 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() - + "."); + 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()); + + for (String s : convertToEmptyListIfNull(type.getStars())) { + result.append(s); + } + return result.toString(); + } } - 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 ""; + } + + /** + * 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; + } + } 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; - } - } - 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; + } + + /** + * 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; } - } 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 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 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 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; + } - 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."); + /** + * 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; } - // 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; + if (!ASTUtils.isZero(e)) { + foundNonZero = true; } - leftWidth += otherWidth; - } } - for (VarRef rightPort : connection.getRightPorts()) { - if (rightPort == reference) { - if (leftOrRight != 0) { - throw new InvalidSourceException( - "Multiple ports with variable width on a connection."); + + 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; } - // 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; + } + 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); + } } - rightWidth += otherWidth; - } + return result; } - 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; + } + // 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; } - // Check that portWidth divides the discrepancy. - if (discrepancy % portWidth != 0) { - // This is an error. - return -1; + } + 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; } - bankWidth = discrepancy / portWidth; - } else { - // Could not determine the bank width. - return -1; - } } - } - 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 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; } - } - } - 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(); + if (spec.isOfVariableLength() && spec.eContainer() instanceof Instantiation) { + return inferWidthFromConnections(spec, instantiations); } - } - 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); + 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); + } + } + + 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. + 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); } - } - 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; + } + + /** + * 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 { - rightWidth += inferPortWidth(rightPort, c, instantiations); + inst.setName(reactor.getName()); } - } - 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); - } + 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); + } } From c25c8b7f01017437780596a174a8c609c76a1f03 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Apr 2023 10:54:44 +0200 Subject: [PATCH 082/108] Removed unused imports --- org.lflang/src/org/lflang/ASTUtils.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index f8c436727c..23b799dd53 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -32,7 +32,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; @@ -61,8 +60,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; @@ -263,7 +260,6 @@ public static Target getTarget(EObject object) { /** * 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 From 592e2c0180b88ff809595145580d2bfde0bc41bb Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Apr 2023 10:58:28 +0200 Subject: [PATCH 083/108] Revert formatting changes --- org.lflang/src/org/lflang/ast/IsEqual.java | 1202 ++++++++++---------- 1 file changed, 610 insertions(+), 592 deletions(-) diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 73cd12916a..af0274ea2a 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -6,7 +6,9 @@ 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; @@ -64,604 +66,620 @@ 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, 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( + 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 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; + .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; } - } - 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; - } - } + } } From b7e902b4918a8ccb489037c7fc8ed3e2798831c2 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Apr 2023 11:17:06 +0200 Subject: [PATCH 084/108] Merge master changes in --- org.lflang/src/org/lflang/TargetConfig.java | 16 +-- org.lflang/src/org/lflang/util/FileUtil.java | 111 ++++++++++++++++++- 2 files changed, 109 insertions(+), 18 deletions(-) diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index 23e99541da..845f214ec8 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -172,13 +172,6 @@ public TargetConfig( */ public List cmakeIncludes = new ArrayList<>(); - /** - * List of cmake-includes from the cmake-include target property with no path info. - * Useful for copying them to remote machines. This is needed because - * target cmake-includes can be resources with resource paths. - */ - public List cmakeIncludesWithoutPath = new ArrayList<>(); - /** * The compiler to invoke, unless a build command has been specified. */ @@ -237,14 +230,7 @@ public TargetConfig( /** * List of files to be copied to src-gen. */ - public List fileNames = new ArrayList<>(); - - /** - * List of file names from the files target property with no path info. - * Useful for copying them to remote machines. This is needed because - * target files can be resources with resource paths. - */ - public List filesNamesWithoutPath = new ArrayList<>(); + public List files = new ArrayList<>(); /** * If true, configure the execution environment to keep executing if there diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index d4d34c7b08..a2cfb81e4d 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -36,7 +36,9 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.util.RuntimeIOException; +import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.generator.LFGeneratorContext; public class FileUtil { @@ -150,7 +152,7 @@ public static java.net.URI locateFile(String path, Resource resource) { sourceURI = iFile != null ? iFile.getRawLocation().toFile().getParentFile().toURI() : null; } if (sourceURI != null) { - return sourceURI.resolve(path.toString()); + return sourceURI.resolve(path); } } catch (Exception e) { // nothing @@ -239,6 +241,68 @@ public static void copyFile(Path source, Path destination) throws IOException { copyFile(source, destination, false); } + /** + * Given a list of files or directories, attempt to find them based on the given generator + * context, and copy then to the destination. Files are searched for in the file system first. + * Files that cannot be found in the file system are looked for on the class path. + * + * @param filesOrDirectories The files or directories to copy. + * @param destination The location to copy them to. + * @param fileConfig The file configuration that specifies where the files must be found. + * @param errorReporter An error reporter to report problems. + */ + public static void copyFiles( + List filesOrDirectories, + Path destination, + FileConfig fileConfig, + ErrorReporter errorReporter + ) { + for (String fileOrDirectory : filesOrDirectories) { + var path = Paths.get(fileOrDirectory); + var found = FileUtil.findInPackage(path, fileConfig); + if (found != null) { + try { + FileUtil.copyFileOrDirectory(found, destination.resolve(found.getFileName())); + } catch (IOException e) { + errorReporter.reportError( + "Unable to copy '" + fileOrDirectory + "' from the file system." + ); + } + } else { + // Attempt to copy from the classpath instead. + // If the filename is not a directory, it will + // just be copied without further recursion. + try { + FileUtil.copyDirectoryFromClassPath( + fileOrDirectory, + destination, + false + ); + } catch (IOException e) { + errorReporter.reportError( + "Unable to copy '" + fileOrDirectory + "' from the class path." + ); + } + } + } + } + + /** + * If the source is a directory, then copy the contents of the directory to the destination. + * If the source is a file, then copy the file to the destination. + * @param source A file or directory to copy to the destination. + * @param destination A directory to copy the file(s) at the source to. + * @throws IOException + */ + public static void copyFileOrDirectory(Path source, Path destination) throws IOException { + if (Files.isDirectory(source)) { + copyDirectory(source, destination); + } else if (Files.isRegularFile(source)) { + copyFile(source, destination); + } else { + throw new IllegalArgumentException("Source is neither a directory nor a regular file."); + } + } /** * Copy a given input stream to a destination file. * @@ -359,6 +423,7 @@ private static boolean copyDirectoryFromJar(JarURLConnection connection, final P final String connectionEntryName = connection.getEntryName(); boolean copiedFiles = false; + // Iterate all entries in the jar file. for (Enumeration e = jar.entries(); e.hasMoreElements(); ) { final JarEntry entry = e.nextElement(); @@ -366,9 +431,10 @@ private static boolean copyDirectoryFromJar(JarURLConnection connection, final P // Extract files only if they match the given source path. if (entryName.startsWith(connectionEntryName)) { - String filename = entry.getName().substring(connectionEntryName.length() + 1); + String filename = entryName.equals(connectionEntryName) ? + connectionEntryName : + entryName.substring(connectionEntryName.length() + 1); Path currentFile = destination.resolve(filename); - if (entry.isDirectory()) { Files.createDirectories(currentFile); } else { @@ -511,6 +577,45 @@ public static void deleteDirectory(Path dir) throws IOException { } } + /** + * Return an absolute path to the given file or directory if it can be found within the package. + * Otherwise, return null. + * + * NOTE: If the given file or directory is given as an absolute path but cannot be found, it is + * interpreted as a relative path with respect to the project root. + * + * @param fileOrDirectory The file or directory to look for. + * @param fileConfig A file configuration that determines where the package is located. + * @return An absolute path of the file or directory was found; null otherwise. + */ + public static Path findInPackage(Path fileOrDirectory, FileConfig fileConfig) { + if (fileOrDirectory.isAbsolute() && Files.exists(fileOrDirectory)) { + return fileOrDirectory; + } else { + Path relPath; + // Disregard root and interpret as relative path + if (fileOrDirectory.isAbsolute()) { + relPath = Paths.get( + String.valueOf(fileOrDirectory).replaceFirst( + String.valueOf(fileOrDirectory.getRoot()), + "") + ); + } else { + relPath = fileOrDirectory; + } + + // Look relative to the source file and relative to the package root. + var locations = List.of(fileConfig.srcPath, fileConfig.srcPkgPath); + var found = locations.stream().filter( + loc -> Files.exists(loc.resolve(relPath)) + ).findFirst(); + if (found.isPresent()) { + return found.get().resolve(relPath); + } + } + return null; + } + /** * Get the iResource corresponding to the provided resource if it can be * found. From 5159e0a7cc0aa96b7ad64fe595f50b6f6f3bf3ae Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Apr 2023 11:17:15 +0200 Subject: [PATCH 085/108] Undo formatting changes --- .../org/lflang/generator/GeneratorBase.java | 1144 +++++++++-------- 1 file changed, 576 insertions(+), 568 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index 9ae46ad357..4cae3f7cf1 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -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.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedHashMap; 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,12 +53,18 @@ 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.util.FileUtil; 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 @@ -68,595 +74,597 @@ */ 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; - - /** 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); - } + //////////////////////////////////////////// + //// 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()); } - } - - /** - * 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); - } + + /** + * 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); } - // Configure the command factory - commandFactory.setVerbose(); - if (Objects.equal(context.getMode(), LFGeneratorContext.Mode.STANDALONE) - && context.getArgs().containsKey("quiet")) { - commandFactory.setQuiet(); + // ////////////////////////////////////////// + // // 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); + } + } } - // 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); + /** + * 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); + + // Check for the existence and support of watchdogs + hasWatchdogs = IterableExtensions.exists(reactors, it -> !it.getWatchdogs().isEmpty()); + checkWatchdogSupport(targetConfig.threading && getTarget() == Target.C); + additionalPostProcessingForModes(); } - // 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"); - } + /** + * 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"); + } + } } - } - - /** - * 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); + + /** + * 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); + } + } } - } } - } - - /** - * 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<>(); + + /** + * 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) { + FileUtil.copyFiles(targetConfig.files, this.context.getFileConfig().getSrcGenPath(), fileConfig, errorReporter); + } + + /** + * 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(); } - 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; + + /* + * 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); } - if (reactionBankIndices == null) { - reactionBankIndices = new LinkedHashMap<>(); + + /** + * 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; + } + if (reactionBankIndices == null) { + reactionBankIndices = new LinkedHashMap<>(); + } + reactionBankIndices.put(reaction, bankIndex); } - 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!"); + + /** + * 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); } - } - - /** - * 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."); + + // ////////////////////////////////////////// + // // 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!"); + } } - } - - /** - * 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) { + + /** + * Check 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( - 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); - } + "Watchdogs are currently only supported for threaded programs in the C target."); } - } - } - } - /** - * 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; } - } - - /** - * 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(originalPath, 1, "Error in imported file: " + path); - } else { - errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); + + /** + * 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 (parsed.isError) { - severity = IMarker.SEVERITY_ERROR; - } else { - severity = IMarker.SEVERITY_WARNING; + } + + /** + * 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; } + } + + /** + * 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; + } - // 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; + /** + * 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); + } } - // 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; - } + 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); + } + } } - message.append(line); - } } - 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); - } - } + + // ////////////////////////////////////////////////// + // // 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()); } - } - - // ////////////////////////////////////////////////// - // // 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(); + + /** + * Get the buffer type used for network messages + */ + public String getNetworkBufferType() { return ""; } + + /** + * Return the Targets enum for the current target + */ + public abstract Target getTarget(); } From 6463813cf99c97f0e534bec59e8321aa1c6e6f65 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 07:33:50 +0200 Subject: [PATCH 086/108] Undid formatting --- .../org/lflang/generator/c/CGenerator.java | 3764 ++++++++--------- 1 file changed, 1876 insertions(+), 1888 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 3a3352aa03..7bc579ac0f 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1,22 +1,26 @@ /************* - * 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; @@ -31,11 +35,8 @@ 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; import java.nio.file.Path; import java.util.HashSet; import java.util.LinkedHashSet; @@ -43,26 +44,33 @@ 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.ast.DelayedConnectionTransformation; + import org.lflang.federated.extensions.CExtensionUtils; + +import org.lflang.ast.DelayedConnectionTransformation; + 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; @@ -88,168 +96,201 @@ import org.lflang.util.ArduinoUtil; import org.lflang.util.FileUtil; +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 @@ -263,1122 +304,1102 @@ */ @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; - 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; - } - } - } + + // 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)); } - } - - /** - * 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; - } + + public CGenerator(LFGeneratorContext context, boolean ccppMode) { + this( + context, + ccppMode, + new CTypes(), + new CCmakeGenerator(context.getFileConfig(), List.of()), + new CDelayBodyGenerator(new CTypes()) + ); } - 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 - // 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); + /** + * 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; + } + } + } + } } - // 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); - } + /** + * 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; } - // 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); - } + /** + * 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); + } - 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; - } + // 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); + } + } - // 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 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; + } - // 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(); + // 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 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 + ); + } + if (!errorsOccurred()){ + System.out.println("Compiled binary is in " + fileConfig.binPath); + } } else { - context.finish(GeneratorResult.Status.COMPILED, null); + context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); } - cleanCode.writeToFile(targetFile); - } 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); - } else { - context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + // In case we are in Eclipse, make sure the generated code is visible. + GeneratorUtils.refreshProject(resource, context.getMode()); } - // 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("}"); - } + 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.generateWatchdogTable(watchdogCount)); - - // 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( - """ + // 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.generateWatchdogTable(watchdogCount)); + + // 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(""" #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)); - } - } - - @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; + #endif""" + ); + + + // Generate functions for modes + code.pr(CModesGenerator.generateLfInitializeModes( + hasModalReactors + )); + code.pr(CModesGenerator.generateLfHandleModeChanges( + hasModalReactors, + modalStateResetCount + )); + code.pr(CReactionGenerator.generateLfModeTriggeredReactions( + startupReactionCount, + resetReactionCount, + hasModalReactors + )); } - } } - } - private boolean hasDeadlines(List reactors) { - for (Reactor reactor : reactors) { - for (Reaction reaction : allReactions(reactor)) { - if (reaction.getDeadline() != null) { - return true; - } - } - } - 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()); - } + @Override + public void checkModalReactorSupport(boolean __) { + // Modal reactors are currently only supported for non federated applications + super.checkModalReactorSupport(true); } - } - - /** - * 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); + + @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);" + ); } - 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); - } + /** 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; + } + } + } } - 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); - } + private boolean hasDeadlines(List reactors) { + for (Reactor reactor : reactors) { + for (Reaction reaction : allReactions(reactor)) { + if (reaction.getDeadline() != null) { + return true; + } + } + } + return false; } - 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); - } + /** + * 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; + } + } + if (lfResource != null) { + // Copy the user files and cmake-includes to the src-gen path of the main .lf file + copyUserFiles(lfResource.getTargetConfig(), lfResource.getFileConfig()); + // Merge the CMake includes from the imported file into the target config + lfResource.getTargetConfig().cmakeIncludes.forEach(incl -> { + if (!this.targetConfig.cmakeIncludes.contains(incl)) { + this.targetConfig.cmakeIncludes.add(incl); + } + }); + } + } } - } - - /** - * 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); + + /** + * 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 + protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { + super.copyUserFiles(targetConfig, fileConfig); + // Must use class variable to determine destination! + var destination = this.fileConfig.getSrcGenPath(); + + FileUtil.copyFiles(targetConfig.cmakeIncludes, destination, fileConfig, errorReporter); + + // FIXME: Unclear what the following does, but it does not appear to belong here. + if (!StringExtensions.isNullOrEmpty(targetConfig.fedSetupPreamble)) { + try { + FileUtil.copyFile(fileConfig.srcFile.getParent().resolve(targetConfig.fedSetupPreamble), + destination.resolve(targetConfig.fedSetupPreamble)); + } catch (IOException e) { + errorReporter.reportError("Failed to find _fed_setup file " + targetConfig.fedSetupPreamble); + } + } } - if (this.mainDef != null) { - generateReactorClass(ASTUtils.toDefinition(this.mainDef.getReactorClass())); + /** + * 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 (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); + /** 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 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))); - }); + + /** + * 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); } - }, - this::generateTopLevelPreambles); + } + } + + /** + * 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"); + } } - 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); + + /** + * 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 + ); + } + } + + //////////////////////////////////////////// + //// 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); } - } - - /** - * 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"); + + 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); } - } - - /** 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); + + 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); + // The following must go before the self struct so the #include watchdog.h ends up in the header. + CWatchdogGenerator.generateWatchdogs(src, header, reactor); + generateSelfStruct(header, reactor, constructorCode); + generateMethods(src, reactor); + generateReactions(src, reactor); + generateConstructor(src, header, reactor, constructorCode); } - // 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 methods for {@code reactor}. + */ + protected void generateMethods(CodeBuilder src, ReactorDecl reactor) { + CMethodGenerator.generateMethods(reactor, src, types); } - } - - //////////////////////////////////////////// - //// 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\" {"); + + /** + * 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."); + } } - 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("}"); + + /** + * 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() + )); } - 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); - // The following must go before the self struct so the #include watchdog.h ends up in the header. - CWatchdogGenerator.generateWatchdogs(src, header, reactor); - generateSelfStruct(header, reactor, constructorCode); - generateMethods(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."); + + protected void generateIncludes(Reactor r) { + code.pr("#include \"" + CUtil.getName(r) + ".h\""); } - } - - /** - * Generate a constructor for the specified reactor in the specified federate. - * - * @param src Where to put the assembled code. - * @param header Where to put header code. - * @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( - """ + + /** + * 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)); + """.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 + )); + } } - // 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 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 + ";"); } - } - - /** - * 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 be struct_base_t. - // 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 - + ";"); + + /** + * 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 + "]"; } - 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("}"); - } + // 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("}"); + } + } + } + body.unindent(); + body.pr(String.join("\n", + "} _lf_"+containedReactor.getName()+array+";", + "int _lf_"+containedReactor.getName()+"_width;" + )); } - } - 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, @@ -1386,726 +1407,693 @@ protected void generateReaction( errorReporter, types, targetConfig, - getTarget().requiresTypes)); - } - - /** - * 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()); + getTarget().requiresTypes + )); } - } - - /** - * 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"); - } + + /** + * 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()); } - } } - 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; - - // 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); + /** + * 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; - var channelCount = 0; - for (PortInstance output : child.outputs) { - if (!output.getDependsOnReactions().isEmpty()) { + for (ActionInstance action : instance.actions) { 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.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 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); - } + 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(); } - startTimeStepIsPresentCount += channelCount * child.getTotalWidth(); - temp.endScopedBlock(); - 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(); + } + } + if (foundOne) startTimeStep.pr(temp.toString()); } - 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(); - } + + /** + * 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; + } + 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 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)); + + /** + * 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"; } - } - - /** - * 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); - watchdogCount += CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, 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(); + + /** + * 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"; } - // 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 + ")"; - } + /** + * 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) + ); } + } - var selfStruct = CUtil.reactorRef(action.getParent()); + /** + * 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( - CActionGenerator.generateTokenInitializer(selfStruct, action.getName(), payloadSize)); - } + "// ***** 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); + watchdogCount += CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, 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); } - } - - /** - * 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(); + + /** + * 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+")"; + } + } + + var selfStruct = CUtil.reactorRef(action.getParent()); + initializeTriggerObjects.pr( + CActionGenerator.generateTokenInitializer( + selfStruct, action.getName(), payloadSize + ) + ); + } } - } } - } - - /** - * 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 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 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 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 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 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 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 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(); + } } - } - - /** - * 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 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+";"); + } + } } - } - - @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"); + + /** + * 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 + )); + } } - 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; + + /** + * 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.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; + @Override + public TargetTypes getTargetTypes() { + return types; } - 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)); + + /** + * Get the Docker generator. + * @param context + * @return + */ + protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { + return new CDockerGenerator(context); } - pickCompilePlatform(); - } - protected void handleProtoFiles() { - // Handle .proto files. - for (String file : targetConfig.protoFiles) { - this.processProtoFile(file); + // ////////////////////////////////////////// + // // 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(); } - } - - /** - * 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")); + + protected void handleProtoFiles() { + // Handle .proto files. + for (String file : targetConfig.protoFiles) { + this.processProtoFile(file); + } } - 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 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(); } - 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; + + /** + * 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")); } - if (hasDeadlines) { - this.main.assignDeadlines(); + 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; } - // 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())); + 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()) + ); + } + } } - } } - } - - /** - * 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); + } } - } } From e3d967bc1daf29089aa8a4ab7234c57cd0dc1945 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 08:28:41 +0200 Subject: [PATCH 087/108] Merge single file with master --- org.lflang/src/org/lflang/TargetProperty.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index 1bacb1f75e..61cda14431 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -276,16 +276,16 @@ public enum TargetProperty { * processed by the code generator. */ FILES("files", UnionType.FILE_OR_FILE_ARRAY, List.of(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.fileNames), + (config) -> ASTUtils.toElement(config.files), (config, value, err) -> { - config.fileNames = ASTUtils.elementToListOfStrings(value); + config.files = ASTUtils.elementToListOfStrings(value); }, // FIXME: This merging of lists is potentially dangerous since // the incoming list of files can belong to a .lf file that is // located in a different location, and keeping just filename // strings like this without absolute paths is incorrect. (config, value, err) -> { - config.fileNames.addAll(ASTUtils.elementToListOfStrings(value)); + config.files.addAll(ASTUtils.elementToListOfStrings(value)); }), /** From eae9ebda5927974068e1641ad93d4a7642b21b37 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 08:29:20 +0200 Subject: [PATCH 088/108] Merge single file with master --- .../federated/generator/FedFileConfig.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java b/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java index 42f5c60dc7..e434501825 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java +++ b/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -110,7 +111,7 @@ public void doClean() throws IOException { */ public void relativizePaths(FedTargetConfig targetConfig) { relativizePathList(targetConfig.protoFiles); - relativizePathList(targetConfig.fileNames); + relativizePathList(targetConfig.files); relativizePathList(targetConfig.cmakeIncludes); } @@ -120,17 +121,22 @@ public void relativizePaths(FedTargetConfig targetConfig) { */ private void relativizePathList(List paths) { List tempList = new ArrayList<>(); - paths.forEach(f -> tempList.add(relativizePath(f))); + paths.forEach(f -> tempList.add(relativizePath(Paths.get(f)))); paths.clear(); paths.addAll(tempList); } /** - * Relativize a single path. + * Relativize a single path, but only if it points to a local resource in the project (i.e., not + * on the class path). * @param path The path to relativize. */ - private String relativizePath(String path) { - Path resolvedPath = this.srcPath.resolve(path).toAbsolutePath(); - return this.getSrcPath().relativize(resolvedPath).toString(); + private String relativizePath(Path path) { + if (FileUtil.findInPackage(path, this) == null) { + return String.valueOf(path); + } else { + Path resolvedPath = this.srcPath.resolve(path).toAbsolutePath(); + return this.getSrcPath().relativize(resolvedPath).toString(); + } } } From 92ce015a0f99c4768bca01f27cdcbce27aae149a Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 08:29:55 +0200 Subject: [PATCH 089/108] Merge single file with master --- org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java index 3f5eb4a5b7..d158f7491b 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java @@ -25,6 +25,7 @@ package org.lflang.generator.c; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -313,8 +314,8 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); // Add the include file - for (String includeFile : targetConfig.cmakeIncludesWithoutPath) { - cMakeCode.pr("include(\""+includeFile+"\")"); + for (String includeFile : targetConfig.cmakeIncludes) { + cMakeCode.pr("include(\""+ Path.of(includeFile).getFileName()+"\")"); } cMakeCode.newLine(); From a4365c867db012be21fab28336231195846ed246 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 08:30:36 +0200 Subject: [PATCH 090/108] Remove use of Google Objects class --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 7bc579ac0f..590cdb167c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -398,7 +398,7 @@ public void accommodatePhysicalActionsIfPresent() { // 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 (ActionOrigin.PHYSICAL.equals(action.getOrigin())) { // 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)) { @@ -1484,7 +1484,7 @@ private void generateStartTimeStep(ReactorInstance instance) { temp.pr("// Add port "+port.getFullName()+" to array of is_present fields."); - if (!Objects.equal(port.getParent(), instance)) { + if (!instance.equals(port.getParent())) { // The port belongs to contained reactor, so we also have // iterate over the instance bank members. temp.startScopedBlock(); @@ -1507,7 +1507,7 @@ private void generateStartTimeStep(ReactorInstance instance) { startTimeStepIsPresentCount += port.getWidth() * port.getParent().getTotalWidth(); - if (!Objects.equal(port.getParent(), instance)) { + if (!instance.equals(port.getParent())) { temp.pr("count++;"); temp.endScopedBlock(); temp.endScopedBlock(); From e71ffdf9bc350781777aba85b193f4dc15e79c57 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 08:45:20 +0200 Subject: [PATCH 091/108] Restored accidentally removed watchdog line --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 590cdb167c..b7ce5ff1fb 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1208,6 +1208,9 @@ private void generateSelfStruct(CodeBuilder builder, ReactorDecl decl, CodeBuild types ); + // Generate the fields needed for each watchdog. + CWatchdogGenerator.generateWatchdogStruct(body, decl, constructorCode); + // Next, generate fields for modes CModesGenerator.generateDeclarations(reactor, body, constructorCode); From 45f728620d6930515c874ed19bbec4b4c69904eb Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 08:50:17 +0200 Subject: [PATCH 092/108] Merge single file with master --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 274bffab2c..3ccd9f3a7f 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.git + url = https://github.com/lf-lang/reactor-cpp [submodule "org.lflang/src/lib/rs/reactor-rs"] path = org.lflang/src/lib/rs/reactor-rs - url = https://github.com/lf-lang/reactor-rs.git + url = https://github.com/lf-lang/reactor-rs From 442f80869c7d7f486aa2be52e8addb211573f694 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 09:06:15 +0200 Subject: [PATCH 093/108] Restored file to unformatted version --- .../federated/generator/FederateInstance.java | 1127 +++++++++-------- 1 file changed, 587 insertions(+), 540 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index 349ff0fff7..7ca5a19bca 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. - * - *

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. +/** Instance of a federate specification. * - *

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 com.google.common.base.Objects; +import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -35,16 +35,22 @@ 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; @@ -65,557 +71,598 @@ 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; - } - } - } - - ///////////////////////////////////////////// - //// 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; + /** + * 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; + } + } } - 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); - } + ///////////////////////////////////////////// + //// 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; } - 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; - } - } + /** + * 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? } - // Look in sources - for (VarRef source : convertToEmptyListIfNull(react.getSources())) { - if (Objects.equal(source.getVariable(), action)) { + 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; - } } - // Look in effects - for (VarRef effect : convertToEmptyListIfNull(react.getEffects())) { - if (Objects.equal(effect.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); + } } - } - } - 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 instantiationsCheck; } - int reactionBankIndex = FedASTUtils.getReactionBankIndex(reaction); - if (reactionBankIndex >= 0 && this.bankIndex >= 0 && reactionBankIndex != this.bankIndex) { - return false; + /** + * 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; } - // 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 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; } - 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 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; + } + } } - } } - } + + return false; } - 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 - } - // 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 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; - } - - /** - * 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."); + + /** + * 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; } - 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 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 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"); + + /** + * 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); + } } - 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 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; } - 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; - } + + /** + * 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; + } - } 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 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"); } - return minDelay; - } - // TODO: Put this function into a utils file instead - private List convertToEmptyListIfNull(List list) { - return list == null ? new ArrayList<>() : list; - } + ///////////////////////////////////////////// + //// 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; + } + } + } + } + return minDelay; + } + + // TODO: Put this function into a utils file instead + private List convertToEmptyListIfNull(List list) { + return list == null ? new ArrayList<>() : list; + } } From 17f5071b8b351979178be7750d8d6f5bccd258ff Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 10:05:09 +0200 Subject: [PATCH 094/108] Added file docs --- org.lflang/src/org/lflang/generator/WatchdogInstance.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index ca5cb3d192..78c0c9bc81 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -1,3 +1,11 @@ +/** + * @file + * @author Benjamin Asch + * @author Edward A. Lee + * @copyright (c) 2023, The University of California at Berkeley + * License in [BSD 2-clause](https://github.com/lf-lang/lingua-franca/blob/master/LICENSE) + * @brief Instance of a watchdog + */ package org.lflang.generator; import org.lflang.TimeValue; From 0867ec87dbf44e5eb31e3d9ce3f4d0b1c803f802 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 10:05:30 +0200 Subject: [PATCH 095/108] Revert formatting --- .../org/lflang/generator/ReactorInstance.java | 2105 +++++++-------- .../generator/c/CReactionGenerator.java | 2345 ++++++++--------- 2 files changed, 2140 insertions(+), 2310 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index b3bf51d836..0d6154c1bb 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -1,31 +1,32 @@ /** 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; @@ -37,6 +38,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; + import org.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; @@ -51,6 +53,7 @@ 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; @@ -66,1097 +69,1117 @@ 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 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. + * @param reactor The top-level reactor. + * @param reporter The error reporter. + */ + public ReactorInstance(Reactor reactor, ErrorReporter reporter) { + this(ASTUtils.createInstantiation(reactor), null, reporter, -1); } - 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 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); } - 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; - } + + /** + * 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); } - 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); + + ////////////////////////////////////////////////////// + //// 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); + } + return cachedReactionLoopGraph; } - for (PortInstance port : inputs) { - port.clearCaches(); + + /** + * 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 : outputs) { - 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 (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. + */ + public void clearCaches() { + clearCaches(true); } - 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; - } + + /** + * 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; } - 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 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); } - } - // 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 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; + } } - } - cachedCycles.addAll(reactions); - cachedCycles.addAll(ports); + return null; } - 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"; + /** + * 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 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; - } + + /** + * @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 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 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 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 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 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); + + /** + * Return the startup trigger or null if not used in any reaction. + */ + public TriggerInstance getStartupTrigger() { + return builtinTriggers.get(BuiltinTrigger.STARTUP); } - 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 the shutdown trigger or null if not used in any reaction. + */ + public TriggerInstance getShutdownTrigger() { + return builtinTriggers.get(BuiltinTrigger.SHUTDOWN); } - 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(); - @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); + /** + * 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 defaultValue; - } + return result; } - } - - // /** - // * 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) 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 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 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; } - for (PortInstance portInstance : ports) { - if (portInstance.definition == port) { - return portInstance; - } + + /** + * Return true if the top-level parent of this reactor has causality cycles. + */ + public boolean hasCycles() { + return assignLevels().nodeCount() != 0; } - 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; + + /** + * 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()); } - 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()); + + public Expression resolveParameters(Expression e) { + return LfExpressionVisitor.dispatch(e, this, ParameterInliner.INSTANCE); } - } - - /** - * 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; - } + + + 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 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; - } + + /** + * 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 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 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; - } - - /** - * 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; - } + + /** + * 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; - } - - /** 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 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(); } - // 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); - } + return false; } - } - - /** 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); - } + + /////////////////////////////////////////////////// + //// 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 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; + + /** + * 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; + } } - } - } while (currentParent != null); + return null; + } - this.recursive = foundSelfAsParent; - if (recursive) { - reporter.reportError(definition, "Recursive reactor instantiation."); + /** + * 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; } - // 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; + /** + * 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()); + } else { + // Handle hierarchical reference + var containerInstance = getChildReactorInstance(reference.getContainer()); + if (containerInstance == null) return null; + return containerInstance.lookupPortInstance((Port) reference.getVariable()); + } } - setInitialWidth(); + /** + * 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; + } - // Apply overrides and instantiate parameters for this reactor instance. - for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { - this.parameters.add(new ParameterInstance(parameter, this)); + /** + * 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; } - // Instantiate inputs for this reactor instance - for (Input inputDecl : ASTUtils.allInputs(reactorDefinition)) { - this.inputs.add(new PortInstance(inputDecl, this, reporter)); + /** + * 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; } - // Instantiate outputs for this reactor instance - for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { - this.outputs.add(new PortInstance(outputDecl, this, reporter)); + /** 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; } - // 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 descriptive string. + */ + @Override + public String toString() { + return "ReactorInstance " + getFullName(); } - } - - /** - * 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()); + + /** + * 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); + } + // 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); + } } - } } - 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(); + + /** + * 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 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; - } + + /** 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); + } + } } - 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."); + + //////////////////////////////////////// + //// 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; - } 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."); + + // 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); } - 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; + + // Instantiate timers for this reactor instance + for (Timer timerDecl : ASTUtils.allTimers(reactorDefinition)) { + this.timers.add(new TimerInstance(timerDecl, this)); } - } - 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; + + // 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(); } - } - src = srcRanges.next(); } - } } - } - - /** - * 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; + + ////////////////////////////////////////////////////// + //// 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); } - // 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); + + /** + * 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(); + } + } } - } } - 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); + + /** + * 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; } - 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); - } + // 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); + } + } } - result.add(range); - } + return result; } - // 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); - } + + /** + * 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); + } + } + // 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; } - result.add(tail); - } - tails = moreTails; + return result; } - 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()); + + /** + * 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; + + ////////////////////////////////////////////////////// + //// 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 false; - } } diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index a203f9ee3c..3dd1aef157 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; @@ -37,1353 +38,1159 @@ 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) + 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); + /** + * 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); - // Construct the reactionInitialization code to go into - // the body of the function before the verbatim code. - CodeBuilder reactionInitialization = new CodeBuilder(); + // Construct the reactionInitialization code to go into + // the body of the function before the verbatim code. + CodeBuilder reactionInitialization = new CodeBuilder(); - CodeBuilder code = new CodeBuilder(); + CodeBuilder code = 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);")); - } + // 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);" + )); + } - // 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(); - } + // 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(); + } - // 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<>(); + // 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<>(); - // 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<>(); + // 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<>(); - // 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()); + // 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()); + } } - } - } - 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()); - } - } - // 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."); - } + // 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 not an input, output, or watchdog." + ); + } + } + } } - } - } - // 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 + ";")); + // 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(); } - // Next generate all the collected setup code. - code.pr(reactionInitialization.toString()); - return code.toString(); - } - /** - * 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); + /** + * 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(); + } + return result; } - LinkedList nestedBreadcrumbs = breadcrumbs; - if (nestedBreadcrumbs == null) { - nestedBreadcrumbs = new LinkedList<>(); - nestedBreadcrumbs.add(mainDef); + + /** + * 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."; } - 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); + + 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);"; } - // 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; + + /** + * 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); } - } 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; + 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+";" + )); + } } - } - nestedBreadcrumbs.remove(); } - 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."; - } + /** + * 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); - 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);"; - } + 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+";" + )); + } - /** - * 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); - } - 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 + ";")); - } + // 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 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); - 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 + ";")); - } + /** 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(); - // 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 - + ";"); + 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 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(); + /** 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); - 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("}"); + // 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(); } - return builder.toString(); - } - /** - * 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 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;" + ); - // 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(); - } - /** - * 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. + /** + * 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. + */ + public static String generateWatchdogVariablesInReaction(VarRef effect, ReactorDecl decl) { + Watchdog watchdog = (Watchdog) effect.getVariable(); + String watchdogName = watchdog.getName(); return String.join( "\n", - "int " - + outputWidth - + " = self->_lf_" - + outputWidth - + "; SUPPRESS_UNUSED_WARNING(" - + outputWidth - + ");", - outputStructType + "** " + outputName + " = self->_lf_" + outputName + "_pointers;"); - } + List.of("watchdog_t* " + watchdogName + " = &(self->_lf_watchdog_" + watchdogName + ");") + ); } - } - /** - * 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(); + /** + * 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+";"); - return String.join( - "\n", - List.of("watchdog_t* " + watchdogName + " = &(self->_lf_watchdog_" + watchdogName + ");")); - } + // 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()); + } + } - /** - * 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 + ";"); + var deadlineFunctionPointer = "NULL"; + if (reaction.getDeadline() != null) { + // The following has to match the name chosen in generateReactions + var deadlineFunctionName = generateDeadlineFunctionName(reactor, reactionCount); + deadlineFunctionPointer = "&" + deadlineFunctionName; + } - // 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; - } + // 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; + } + + // 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++; } - } - // 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, 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};")); } - } - var deadlineFunctionPointer = "NULL"; - if (reaction.getDeadline() != null) { - // The following has to match the name chosen in generateReactions - var deadlineFunctionName = generateDeadlineFunctionName(reactor, reactionCount); - deadlineFunctionPointer = "&" + deadlineFunctionName; - } + // Handle builtin triggers. + if (startupReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(startupReactions, "startup", body, constructorCode); + } + // Handle shutdown triggers. + if (shutdownReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(shutdownReactions, "shutdown", body, constructorCode); + } + if (resetReactions.size() > 0) { + generateBuiltinTriggeredReactionsArray(resetReactions, "reset", body, constructorCode); + } - // 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 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 + ")"; + } - // 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++; - } + // 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 + ";" + )); + } - // 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};")); - } + // Next handle inputs. + for (Input input : ASTUtils.allInputs(reactor)) { + createTriggerT(body, input, triggerMap, constructorCode, types); + } - // Handle builtin triggers. - if (startupReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(startupReactions, "startup", body, constructorCode); - } - // Handle shutdown triggers. - if (shutdownReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(shutdownReactions, "shutdown", body, constructorCode); - } - if (resetReactions.size() > 0) { - generateBuiltinTriggeredReactionsArray(resetReactions, "reset", body, constructorCode); + // Next handle watchdogs. + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + createTriggerT(body, watchdog, triggerMap, constructorCode, types); + } } - // 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 + ")"; - } + /** + * 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};")); - // 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 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+";" + )); - // Next handle inputs. - for (Input input : ASTUtils.allInputs(reactor)) { - createTriggerT(body, input, triggerMap, constructorCode, types); - } + // 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+")"; - // Next handle watchdogs. - for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - createTriggerT(body, watchdog, triggerMap, constructorCode, types); + constructorCode.pr("self->_lf__"+varName+".tmplt.type.element_size = "+size+";"); + body.pr( + CExtensionUtils.surroundWithIfFederated( + CExtensionUtils.createPortStatusFieldForInput((Input) variable) + ) + ); + } } - } - - /** - * 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};")); - - // 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;")); + 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;" + )); } - 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))); + 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+";" + )); } - } - 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 - + ";"); + /** + * 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(); } - 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;")); - } - - 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 + ";")); - } - /** 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"); + /** + * 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(); } - s.append("}\n"); - return s.toString(); - } - /** 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"); + /** + * 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(); } - s.append("}\n"); - return s.toString(); - } - /** 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(); - } + /** 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); - /** - * 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())); - code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader())); + 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())); + } - 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())); + // 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(); } - // 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())); + 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; } - 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(); - } + 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; - } + /** + * 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; - } + /** + * 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; - } + /** + * 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 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); - } + /** 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 generateStpFunctionHeader(Reactor r, + int reactionIndex) { + String functionName = generateStpFunctionName(r, reactionIndex); + return generateFunctionHeader(functionName); + } - /** - * Return the start of a function declaration for a function that takes - * a `void*` argument and returns void. - * @param functionName - * @return - */ - public static String generateFunctionHeader(String functionName) { - return "void " + functionName + "(void* instance_args)"; - } + /** + * Return the start of a function declaration for a function that takes + * a `void*` argument and returns void. + * + * @param functionName + * @return + */ + public static String generateFunctionHeader(String functionName) { + return "void " + functionName + "(void* instance_args)"; + } } From e6a0ed03a3a8087b377bb41bf02f641bbf8a6daa Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 10:09:39 +0200 Subject: [PATCH 096/108] Revert to master version (reverse formatting) --- .../generator/python/PythonGenerator.java | 1081 +++++++++-------- 1 file changed, 558 insertions(+), 523 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index f1f0e9b59d..020954be80 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -34,14 +34,18 @@ 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; @@ -52,6 +56,7 @@ 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; @@ -63,505 +68,525 @@ 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; - } - - 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."); - } + // 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 + ) + ); } - 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()); + + + 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; } - // Add the main reactor if it is defined - if (this.mainDef != null) { - models.add((Model) ASTUtils.toDefinition(this.mainDef.getReactorClass()).eContainer()); + + /** + * 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; } - for (Model m : models) { - pythonPreamble.pr(PythonPreambleGenerator.generatePythonPreambles(m.getPreambles())); + + private final Set protoNames = new HashSet<>(); + + // ////////////////////////////////////////// + // // Public methods + @Override + public TargetTypes getTargetTypes() { + return types; } - 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); + + // ////////////////////////////////////////// + // // 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() + ); } - } - - /** - * 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; + + /** + * 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() + ); } - int returnCode = protoc.run(); - if (returnCode == 0) { - pythonRequiredModules.add("google-api-python-client"); - } else { - errorReporter.reportError("protoc returns error code " + returnCode); + + /** + * 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( + 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); + } + + @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); + } + } + + /** + * 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); + } + } + + /** + * 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); + } } - } - - /** - * 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); + + 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)); } - for (Output output : ASTUtils.allOutputs(r)) { - generateAuxiliaryStructsForPort(builder, r, output); + + private void generateAuxiliaryStructsForAction(CodeBuilder builder, Reactor r, + Action action) { + builder.pr(action, PythonActionGenerator.generateAliasTypeDef(r, action, genericActionType)); } - for (Action action : ASTUtils.allActions(r)) { - generateAuxiliaryStructsForAction(builder, r, action); + + /** + * Return true if the host operating system is compatible and + * otherwise report an error and return false. + */ + @Override + public boolean isOSCompatible() { + return true; } - } - - 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 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); + } } - 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; + + @Override + protected PythonDockerGenerator getDockerGenerator(LFGeneratorContext context) { + return new PythonDockerGenerator(context); } - 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)); + /** 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; } - } catch (Exception e) { - //noinspection ConstantConditions - throw Exceptions.sneakyThrow(e); - } + src.pr(PythonReactionGenerator.generateCReaction(reaction, reactor, reactionIndex, mainDef, errorReporter, types)); + } - System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, lfModuleName)); + /** + * 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 } - if (errorReporter.getErrorsOccurred()) { - context.unsuccessfulFinish(); - } else { - context.finish(GeneratorResult.Status.COMPILED, codeMaps); + /** + * 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 } - } - - @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; + + /** + * 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 } - 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++; + + /** + * 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)); } - } - - @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"); + + /** + * 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 additionalPostProcessingForModes() { - if (!hasModalReactors) { - 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" + ); } - PythonModeGenerator.generateResetReactionsIfNeeded(reactors); - } - private static String setUpMainTarget( - boolean hasMain, String executableName, Stream cSources) { - return (""" + @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); + } + + 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) @@ -586,61 +611,71 @@ private static String setUpMainTarget( 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 + ); + } + } From e52a3bc220212b19d23cfd31cbc124242415bc02 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 10:16:13 +0200 Subject: [PATCH 097/108] Revert formatting --- .../lflang/scoping/LFScopeProviderImpl.java | 425 +-- .../org/lflang/validation/LFValidator.java | 3290 ++++++++--------- 2 files changed, 1823 insertions(+), 1892 deletions(-) diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java index 32cd8d4a97..70a05a40f7 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,13 +29,16 @@ 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; @@ -60,220 +63,220 @@ */ 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, - WATCHDOG, - 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); + /** + * 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); } - 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); + /** + * 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()); } - 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()); + 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()); } - 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: + 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)); + candidates.addAll(allWatchdogs(reactor)); + return Scopes.scopeFor(candidates); + } case SOURCE: - case CLEFT: - return Scopes.scopeFor(allOutputs(defn)); - case EFFECT: + 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(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)); - candidates.addAll(allWatchdogs(reactor)); - return Scopes.scopeFor(candidates); + return Scopes.scopeFor(allOutputs(reactor)); + default: + return Scopes.scopeFor(emptyList()); + } } - case SOURCE: + } else { // Resolve instance 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()); } - } - } 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; - } + 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; } - return RefType.NULL; - } } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index b0555dc4e0..7f1aa5c808 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -2,13 +2,17 @@ /************* * 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,10 +28,12 @@ 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; @@ -39,6 +45,7 @@ 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; @@ -65,6 +72,7 @@ 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; @@ -108,10 +116,12 @@ 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 @@ -122,1832 +132,1750 @@ */ 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); + ////////////////////////////////////////////////////////////// + //// 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); } - 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 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 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 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 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 ((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); + } + } + } + } } - 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); + } + } + } } - } } - } - } - // 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 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; } - } } - } - 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); + 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 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 + ); + } + } - 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); - } - } + 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); + } + } + } + } - 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); - } + // 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); + } + } + } + } } - } - } - // 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); + // 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 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 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 - 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; + @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 + ); + } } - // FIXME: report error if resource cannot be resolved. - for (ImportedReactor reactor : imp.getReactorClasses()) { - if (!isUnused(reactor)) { - return; - } - } - warning("Unused import.", Literals.IMPORT__IMPORT_URI); - } + @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; + } - @Check - public void checkImportedReactor(ImportedReactor reactor) { - if (isUnused(reactor)) { - warning("Unused reactor class.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); + // FIXME: report error if resource cannot be resolved. + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (!isUnused(reactor)) { + return; + } + } + warning("Unused import.", Literals.IMPORT__IMPORT_URI); } - 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 + public void checkImportedReactor(ImportedReactor reactor) { + if (isUnused(reactor)) { + warning("Unused reactor class.", + Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } - @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); - } + 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); + } + } } - // 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); - } + @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); + } + } - // 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); + // 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 + ); + } + + // 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); + } } - // 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()); - } + @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 + ); + } - error( - "Instantiation is part of a cycle: " + String.join(", ", names) + ".", - Literals.INSTANTIATION__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 + ); + } } - } - } - // 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); - } } - // 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 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); + } - @Check(CheckType.FAST) - public void checkParameter(Parameter param) { - checkName(param.getName(), Literals.PARAMETER__NAME); + // Report problem with the assigned value. + prop.type.check(param.getValue(), param.getName(), this); + } - 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; + 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(); + } } - if (this.target.requiresTypes) { - // Report missing target type. param.inferredType.undefine - if (ASTUtils.getInferredType(param).isUndefined()) { - error("Type declaration missing.", Literals.PARAMETER__TYPE); - } + @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); + } } - if (param.getType() != null) { - typeCheck(param.getInit(), ASTUtils.getInferredType(param), Literals.PARAMETER__INIT); + @Check(CheckType.NORMAL) + public void updateModelInfo(Model model) { + info.update(model, errorReporter); } - 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); + @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); + } } - } - } - 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); - } - } + // 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 (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); - } + @Check(CheckType.FAST) + public void checkParameter(Parameter param) { + checkName(param.getName(), Literals.PARAMETER__NAME); + + 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 (this.target.requiresTypes) { + // Report missing target type. param.inferredType.undefine + if (ASTUtils.getInferredType(param).isUndefined()) { + error("Type declaration missing.", Literals.PARAMETER__TYPE); + } } - } - } 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); - } - } - @Check(CheckType.FAST) - public void checkReaction(Reaction reaction) { + if (param.getType() != null) { + typeCheck(param.getInit(), ASTUtils.getInferredType(param), Literals.PARAMETER__INIT); + } - 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) { + 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 (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 (isCBasedTarget() && + this.info.overflowingParameters.contains(param)) { error( - String.format( - "Cannot have an output of this reactor as a trigger: %s", - triggerVarRef.getVariable().getName()), - Literals.REACTION__TRIGGERS); - } + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.PARAMETER__INIT); } - } - } - // 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); - } - } + @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 + ); + } } - // // 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; - } + @Check(CheckType.FAST) + public void checkReaction(Reaction reaction) { + + if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { + warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS); } - 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; - } + 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); + } + } + } } - 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; - } + + // 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); + } + } + } + + // // 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; + } + } + 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 (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<>(); - } - 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; + + @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 + ); + } + } + 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); - } + FedValidator.validateFederatedReactor(reactor, this.errorReporter); + } } - // Check for illegal names. - checkName(reactor.getName(), 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; + } + } - // 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 (!isValidSerializer) { + error( + "Serializer can be " + Arrays.asList(SupportedSerializers.values()), + Literals.SERIALIZER__TYPE + ); + } } - if (reactor.getHost() != null) { - if (!reactor.isFederated()) { - error( - "Cannot assign a host to reactor '" - + reactor.getName() - + "' because it is not federated.", - Literals.REACTOR__HOST); - } - } + @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); + } - 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 (this.target.requiresTypes && ASTUtils.getInferredType(stateVar).isUndefined()) { + // Report if a type is missing + error("State must have a type.", Literals.STATE_VAR__TYPE); + } - 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 (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 (!isValidSerializer) { - error( - "Serializer can be " + Arrays.asList(SupportedSerializers.values()), - Literals.SERIALIZER__TYPE); } - } - @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 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); + } } - 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(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 (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() + /** + * 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; - } + 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; + } + } + } + } + } - private void validateFastTargetProperty(KeyValuePairs targetProperties) { - KeyValuePair fastTargetProperty = getKeyValuePair(targetProperties, TargetProperty.FAST); + private void validateClockSyncTargetProperties(KeyValuePairs targetProperties) { + KeyValuePair clockSyncTargetProperty = getKeyValuePair(targetProperties, TargetProperty.CLOCK_SYNC); - 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; - } + 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 validateClockSyncTargetProperties(KeyValuePairs targetProperties) { - KeyValuePair clockSyncTargetProperty = - getKeyValuePair(targetProperties, TargetProperty.CLOCK_SYNC); + 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 + } + } + } - 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 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); + } } - } - - 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))) { + + 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( - "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); - } + "Ignoring ros2-dependencies as ros2 compilation is disabled", + ros2Dependencies, + Literals.KEY_VALUE_PAIR__NAME + ); } - } catch (IllegalArgumentException e) { - // the given scheduler is invalid, but this is already checked by - // checkTargetProperties - } - } - } - - 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); } - } - - 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 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 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(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(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 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 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 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 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.", + + @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 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 { + 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); + } + } } - } } - } - - @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 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 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 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 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 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 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 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 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()); + } } - } } - } - 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(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 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); + 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)); + } + } + } } - } - // continue with inner - for (var innerInstance : check.getInstantiations()) { - var next = (Reactor) innerInstance.getReactorClass(); - if (!checked.contains(next)) { - toCheck.push(next); + // 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)); + } + } } - } - } - 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); - } + + @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)); + } } - } } - } - 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); } - if (this.target.isReservedIdent(name)) { - error(RESERVED_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 == 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; + if ((match != null && superVar.getType() != match.getType()) || hasNameConflict(superVar, rest)) { + conflicts.add(superVar); + } else { + allOwn.add(superVar); + } + } } - // 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 + /** + * 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) { - 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; + // 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 (exprs.size() == 1 && exprs.get(0) instanceof BracedListExpression) { - exprs = ((BracedListExpression) exprs.get(0)).getItems(); + + if (this.target.isReservedIdent(name)) { + error(RESERVED_MESSAGE + name, feature); } - for (var component : exprs) { - checkExpressionIsTime(component, feature); + + if (this.target == Target.TS) { + // "actions" is a reserved word within a TS reaction + if (name.equals("actions")) { + error(ACTIONS_MESSAGE + name, feature); + } } - } else { - checkExpressionIsTime(init, feature); - } } - } - 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); - } - } + /** + * 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; + } - private void checkExpressionIsTime(Expression value, EStructuralFeature feature) { - if (value == null || value instanceof Time) { - 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); + } + } } - 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 - } + private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) { + if (init == null) { + return; + } - 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++; - } - } - 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; - } - - /** 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()); + if (init.getExprs().size() != 1) { + error("Expected exactly one time value.", feature); + } else { + checkExpressionIsTime(ASTUtils.asSingleExpr(init), feature); + } } - 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()); - } + 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); } - 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; + + /** + * 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 (type2 == null) { - return type1 == null; + + /** + * 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); } - // 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())); + + /** + * 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; } - // 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; + /** + * 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()); } - // 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}\\$)$"; + + ////////////////////////////////////////////////////////////// + //// 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}\\$)$"; + } From a42a6f079a5b7c1e0a4a4e464f131e6dd17bb84d Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 10:25:22 +0200 Subject: [PATCH 098/108] Formatted test --- test/C/src/Watchdog.lf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/C/src/Watchdog.lf b/test/C/src/Watchdog.lf index a836c93c3f..3ee48afe9c 100644 --- a/test/C/src/Watchdog.lf +++ b/test/C/src/Watchdog.lf @@ -11,8 +11,9 @@ target C { reactor Watcher(timeout: time = 150 ms) { timer t(100 ms, 100 ms) // Offset ameliorates startup time. - // Period has to be smaller than watchdog timeout. - output d: int // Produced if the watchdog triggers. + // Period has to be smaller than watchdog timeout. Produced if the watchdog + // triggers. + output d: int state count: int = 0 watchdog poodle(timeout) {= From 47e99f78c12fda332aa0a0de918a49e983937119 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 10:31:15 +0200 Subject: [PATCH 099/108] Placeholder for when there are no watchdogs --- org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 695bd5eefa..311fe365fd 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -150,6 +150,7 @@ protected static String generateWatchdogTable(int count) { if (count == 0) { return String.join("\n", "// No watchdogs found.", + "typedef void watchdog_t;", "watchdog_t* _lf_watchdogs = NULL;", "int _lf_watchdog_number = 0;" ); From 2687e42d96fb71d7f5c1d40e6dc4cba26dd652f5 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 1 May 2023 11:03:09 +0200 Subject: [PATCH 100/108] Align reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 97466da394..ffe9a6a2e2 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 97466da394d9572e4ba905b2706759bef73c0af0 +Subproject commit ffe9a6a2e2bf79f45c1019d48f17b1b92eaf01a5 From 7547acf9da075a52eedf98dddab568161776db7b Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Fri, 5 May 2023 10:36:35 -0700 Subject: [PATCH 101/108] Move watchdog test into concurrent --- test/C/src/{ => concurrent}/Watchdog.lf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/C/src/{ => concurrent}/Watchdog.lf (100%) diff --git a/test/C/src/Watchdog.lf b/test/C/src/concurrent/Watchdog.lf similarity index 100% rename from test/C/src/Watchdog.lf rename to test/C/src/concurrent/Watchdog.lf From c54def7e9e162f0f78df813dd806a6138b1b04e1 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Fri, 5 May 2023 12:21:48 -0700 Subject: [PATCH 102/108] More lenient test --- test/C/src/concurrent/Watchdog.lf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/C/src/concurrent/Watchdog.lf b/test/C/src/concurrent/Watchdog.lf index 3ee48afe9c..18250f89dc 100644 --- a/test/C/src/concurrent/Watchdog.lf +++ b/test/C/src/concurrent/Watchdog.lf @@ -36,7 +36,8 @@ reactor Watcher(timeout: time = 150 ms) { reaction(shutdown) -> poodle {= lf_watchdog_stop(poodle); - if (self->count != 2) { + // Watchdog may expire in tests even without the sleep, but it should at least expire twice. + if (self->count < 2) { lf_print_error_and_exit("Watchdog expired %d times. Expected 2.", self->count); } =} From 54fa3d00383f9158e35fa58d3cdc443df16616e9 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 6 May 2023 06:10:44 -0700 Subject: [PATCH 103/108] Aligned reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index ffe9a6a2e2..618fa07480 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ffe9a6a2e2bf79f45c1019d48f17b1b92eaf01a5 +Subproject commit 618fa07480964d82ebd88e28378bbfd335adb27c From 836bb3ee41e07762f4409a50bc296dd06037883c Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 6 May 2023 07:00:34 -0700 Subject: [PATCH 104/108] Made test timing lenient. --- test/C/src/concurrent/Watchdog.lf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/C/src/concurrent/Watchdog.lf b/test/C/src/concurrent/Watchdog.lf index 18250f89dc..bc8ab1387b 100644 --- a/test/C/src/concurrent/Watchdog.lf +++ b/test/C/src/concurrent/Watchdog.lf @@ -38,7 +38,7 @@ reactor Watcher(timeout: time = 150 ms) { lf_watchdog_stop(poodle); // Watchdog may expire in tests even without the sleep, but it should at least expire twice. if (self->count < 2) { - lf_print_error_and_exit("Watchdog expired %d times. Expected 2.", self->count); + lf_print_error_and_exit("Watchdog expired %d times. Expected at least 2.", self->count); } =} } @@ -59,8 +59,8 @@ main reactor { =} reaction(shutdown) {= - if (self->count != 12) { - lf_print_error_and_exit("Watchdog produced output %d times. Expected 12.", self->count); + if (self->count < 12) { + lf_print_error_and_exit("Watchdog produced output %d times. Expected at least 12.", self->count); } =} } From f8d38f0413d2aa324b3f1522e96fe480cfcb390e Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 15 May 2023 07:32:00 -0700 Subject: [PATCH 105/108] Align to watchdogs-eal2 of reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 618fa07480..cc3795695b 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 618fa07480964d82ebd88e28378bbfd335adb27c +Subproject commit cc3795695b18638512d5fe3c8ae8c021f044de9a From a84070a0a127e8b16471ee9434eb0dfe214019e1 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Tue, 16 May 2023 13:43:20 -0700 Subject: [PATCH 106/108] In response to review, renamed variables --- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/org/lflang/generator/c/CGenerator.java | 4 ++-- .../src/org/lflang/generator/c/CWatchdogGenerator.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index cc3795695b..c2c6a54fa0 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit cc3795695b18638512d5fe3c8ae8c021f044de9a +Subproject commit c2c6a54fa023c638122a73a7c7f311cf381cea1e diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 4a7f29dabe..be6307943c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -642,8 +642,8 @@ private void generateCodeFor( "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);")); + "int watchdog_number = 0;", + "SUPPRESS_UNUSED_WARNING(watchdog_number);")); // Add counters for modal initialization initializeTriggerObjects.pr(CModesGenerator.generateModalInitalizationCounters(hasModalReactors)); diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 311fe365fd..28d9223ea8 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -62,7 +62,7 @@ protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstan : ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) { var watchdogField = reactorRef + "->_lf_watchdog_" + watchdog.getName(); temp.pr(String.join("\n", - "_lf_watchdogs[_lf_watchdog_number_count++] = &" + watchdogField + ";", + "_lf_watchdogs[watchdog_number++] = &" + watchdogField + ";", watchdogField + ".min_expiration = " + CTypes.getInstance().getTargetTimeExpr(instance.getTimeValue(watchdog.getTimeout())) + ";", @@ -78,7 +78,7 @@ protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstan if (foundOne) { code.pr(temp.toString()); } - code.pr("SUPPRESS_UNUSED_WARNING(_lf_watchdog_number);"); + code.pr("SUPPRESS_UNUSED_WARNING(_lf_watchdog_count);"); return watchdogCount; } @@ -152,7 +152,7 @@ protected static String generateWatchdogTable(int count) { "// No watchdogs found.", "typedef void watchdog_t;", "watchdog_t* _lf_watchdogs = NULL;", - "int _lf_watchdog_number = 0;" + "int _lf_watchdog_count = 0;" ); } return String.join( @@ -160,7 +160,7 @@ protected static String generateWatchdogTable(int count) { List.of( "// Array of pointers to watchdog structs.", "watchdog_t* _lf_watchdogs[" + count + "];", - "int _lf_watchdog_number = " + count + ";" + "int _lf_watchdog_count = " + count + ";" ) ); } From 68afbc24bed2f11995ff32de8e550a08b19c3feb Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 18 May 2023 16:51:13 -0700 Subject: [PATCH 107/108] Address remaining FIXME to include errorReporter --- .../org/lflang/generator/c/CGenerator.java | 2 +- .../generator/c/CWatchdogGenerator.java | 31 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index e5e27c952a..32174be22c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1067,7 +1067,7 @@ private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeB var constructorCode = new CodeBuilder(); generateAuxiliaryStructs(header, reactor, false); // The following must go before the self struct so the #include watchdog.h ends up in the header. - CWatchdogGenerator.generateWatchdogs(src, header, reactor); + CWatchdogGenerator.generateWatchdogs(src, header, reactor, errorReporter); generateSelfStruct(header, reactor, constructorCode); generateMethods(src, reactor); generateReactions(src, reactor); diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index 28d9223ea8..9ab15ea98e 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -10,6 +10,7 @@ import java.util.List; import org.lflang.ASTUtils; +import org.lflang.ErrorReporter; import org.lflang.generator.CodeBuilder; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Mode; @@ -91,12 +92,14 @@ protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstan * @param header The place to put header code * @param decl The reactor declaration */ - protected static void generateWatchdogs(CodeBuilder src, CodeBuilder header, ReactorDecl decl) { + protected static void generateWatchdogs( + CodeBuilder src, CodeBuilder header, ReactorDecl decl, ErrorReporter errorReporter + ) { var reactor = ASTUtils.toDefinition(decl); if (hasWatchdogs(reactor)) { header.pr("#include \"core/threaded/watchdog.h\""); for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { - src.pr(generateWatchdogFunction(watchdog, decl)); + src.pr(generateWatchdogFunction(watchdog, decl, errorReporter)); } } } @@ -174,7 +177,11 @@ protected static String generateWatchdogTable(int count) { * @param watchdog The wotchdog * @param decl The declaration for the reactor that has the watchdog */ - private static String generateInitializationForWatchdog(Watchdog watchdog, ReactorDecl decl) { + private static String generateInitializationForWatchdog( + Watchdog watchdog, + ReactorDecl decl, + ErrorReporter errorReporter + ) { Reactor reactor = ASTUtils.toDefinition(decl); // Construct the reactionInitialization code to go into @@ -219,14 +226,12 @@ private static String generateInitializationForWatchdog(Watchdog watchdog, React ? "history_transition" : "reset_transition") + ";"); + } else { + errorReporter.reportError( + watchdog, + "In generateInitializationForWatchdog(): " + name + " not a valid mode of this reactor." + ); } - // FIXME: include error reporter - // else { - // errorReporter.reportError( - // watchdog, - // "In generateWatchdog(): " + name + " not a valid mode of this reactor." - // ); - // } } } } @@ -265,10 +270,12 @@ private static String generateFunction(String header, String init, Watchdog watc /** Generate the watchdog handler function. */ - private static String generateWatchdogFunction(Watchdog watchdog, ReactorDecl decl) { + private static String generateWatchdogFunction( + Watchdog watchdog, ReactorDecl decl, ErrorReporter errorReporter + ) { return generateFunction( generateWatchdogFunctionHeader(watchdog, decl), - generateInitializationForWatchdog(watchdog, decl), + generateInitializationForWatchdog(watchdog, decl, errorReporter), watchdog); } From 31d4b4dd2b3f86b8f51971b5a742d942a2134556 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 18 May 2023 16:53:48 -0700 Subject: [PATCH 108/108] Align to reactor-c main --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index c2c6a54fa0..843e147128 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit c2c6a54fa023c638122a73a7c7f311cf381cea1e +Subproject commit 843e147128315ffa5216fffd6cba36396c3cd084